541 lines
12 KiB
Markdown
541 lines
12 KiB
Markdown
# Oracle of Secrets: 65816 ASM Style Guide
|
|
|
|
**Version**: 1.0
|
|
**Purpose**: Reduce AI errors and ensure consistent code quality across the codebase.
|
|
|
|
---
|
|
|
|
## Quick Reference Card
|
|
|
|
### Label Naming
|
|
|
|
| Type | Pattern | Example |
|
|
|------|---------|---------|
|
|
| Sprite function | `Sprite_{Name}_{Type}` | `Sprite_Booki_Main`, `Sprite_Darknut_Draw` |
|
|
| Menu function | `Menu_{Purpose}` | `Menu_InitGraphics`, `Menu_DrawBackground` |
|
|
| Link/Player | `Link_{Action}` | `Link_ConsumeMagicBagItem`, `Link_HandleYItem` |
|
|
| Oracle namespace | `Oracle_{Function}` | `Oracle_CheckIfNight`, `Oracle_MainEntry` |
|
|
| Local labels | `.lowercase_with_underscores` | `.not_being_pushed`, `.nextTile` |
|
|
| Constants (macro) | `!UPPERCASE` | `!SPRID`, `!Health`, `!Damage` |
|
|
| Memory addresses | `CamelCase` | `SprAction`, `LinkState`, `OOSPROG` |
|
|
|
|
### Processor State Checklist
|
|
|
|
- [ ] Always use size suffixes (`.b`, `.w`, `.l`) for ambiguous operations
|
|
- [ ] Use `PHP`/`PLP` for functions called from unknown context
|
|
- [ ] `REP #$30` = 16-bit A and X/Y, `SEP #$30` = 8-bit
|
|
- [ ] `REP #$20` / `SEP #$20` = A only
|
|
- [ ] `REP #$10` / `SEP #$10` = X/Y only
|
|
|
|
### Call Convention Checklist
|
|
|
|
- [ ] `JSL`/`RTL` for cross-bank calls (3-byte return address)
|
|
- [ ] `JSR`/`RTS` for same-bank calls (2-byte return address)
|
|
- [ ] **NEVER MIX** - mismatch causes crashes
|
|
- [ ] External hooks use `JSL`, internal helpers use `JSR`
|
|
|
|
---
|
|
|
|
## 1. File Structure
|
|
|
|
### 1.1 File Header (Required for new files)
|
|
|
|
```asm
|
|
; =========================================================
|
|
; File: sprites/enemies/my_enemy.asm
|
|
; Purpose: [Brief description of what this file implements]
|
|
; Author: [Your name or handle]
|
|
; =========================================================
|
|
```
|
|
|
|
### 1.2 Section Organization
|
|
|
|
```asm
|
|
; =========================================================
|
|
; Sprite Properties
|
|
; =========================================================
|
|
!SPRID = $XX
|
|
!NbrTiles = 02
|
|
; ... (30 properties in standard order)
|
|
|
|
; =========================================================
|
|
; Entry Points
|
|
; =========================================================
|
|
Sprite_MyEnemy_Long:
|
|
{ ... }
|
|
|
|
Sprite_MyEnemy_Prep:
|
|
{ ... }
|
|
|
|
; =========================================================
|
|
; Main Logic
|
|
; =========================================================
|
|
Sprite_MyEnemy_Main:
|
|
{ ... }
|
|
|
|
; =========================================================
|
|
; Drawing
|
|
; =========================================================
|
|
Sprite_MyEnemy_Draw:
|
|
{ ... }
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Naming Conventions
|
|
|
|
### 2.1 Labels
|
|
|
|
**PascalCase with underscores for hierarchy:**
|
|
|
|
```asm
|
|
; Good
|
|
Sprite_Booki_Main
|
|
Menu_InitGraphics
|
|
Link_ConsumeMagicBagItem
|
|
Oracle_CheckIfNight
|
|
|
|
; Bad
|
|
spriteBookiMain ; No underscores
|
|
sprite_booki_main ; All lowercase
|
|
SPRITE_BOOKI_MAIN ; All uppercase
|
|
```
|
|
|
|
### 2.2 Local Labels
|
|
|
|
**Lowercase with dot prefix:**
|
|
|
|
```asm
|
|
Sprite_Move:
|
|
{
|
|
LDA.w SprAction, X : BNE .already_moving
|
|
; Start movement
|
|
INC.w SprAction, X
|
|
.already_moving
|
|
RTS
|
|
}
|
|
```
|
|
|
|
### 2.3 Constants and Macros
|
|
|
|
```asm
|
|
; Macro parameters: !UPPERCASE
|
|
!SPRID = $D5
|
|
!Health = 08
|
|
!Damage = 02
|
|
|
|
; Debug flags: !UPPERCASE with LOG prefix
|
|
!DEBUG = 1
|
|
!LOG_MUSIC = 1
|
|
!LOG_SPRITES = 0
|
|
|
|
; Memory addresses: CamelCase (matching vanilla convention)
|
|
SprAction = $0D80
|
|
LinkState = $5D
|
|
OOSPROG = $7EF3D6
|
|
```
|
|
|
|
### 2.4 Sprite Property Standard Order
|
|
|
|
All sprites MUST define properties in this order:
|
|
|
|
```asm
|
|
!SPRID = Sprite_MyEnemyID
|
|
!NbrTiles = 02
|
|
!Harmless = 00
|
|
!Health = 08
|
|
!Damage = 02
|
|
!DeathAnimation = 00
|
|
!ImperviousArrow = 00
|
|
!ImperviousSword = 00
|
|
!Boss = 00
|
|
!Shadow = 01
|
|
!Palette = 00
|
|
!Hitbox = $00
|
|
!Persist = 00
|
|
!Statis = 00
|
|
!CollisionLayer = 00
|
|
!CanFall = 01
|
|
!DeflectProjectiles = 00
|
|
!WaterSprite = 00
|
|
!Blockable = 00
|
|
!Prize = 00
|
|
!Sound = $00
|
|
!Interaction = $00
|
|
!Subtype2 = 00
|
|
%Set_Sprite_Properties(Sprite_MyEnemy_Prep, Sprite_MyEnemy_Long)
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Scoping and Indentation
|
|
|
|
### 3.1 Bracket Scoping
|
|
|
|
**ALL functions use `{ }` brackets** (NOT `subroutine`/`endsubroutine`):
|
|
|
|
```asm
|
|
Sprite_Booki_Main:
|
|
{
|
|
LDA.w SprAction, X
|
|
JSL JumpTableLocal
|
|
dw StalkPlayer
|
|
dw HideFromPlayer
|
|
dw ApproachPlayer
|
|
|
|
StalkPlayer:
|
|
{
|
|
%PlayAnimation(0,1,16)
|
|
JSR Sprite_Booki_Move
|
|
RTS
|
|
}
|
|
|
|
HideFromPlayer:
|
|
{
|
|
; Nested content indented 2 spaces
|
|
LDA.b #$00
|
|
STA.w SprAction, X
|
|
RTS
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.2 Indentation Rules
|
|
|
|
- **2-space indentation** for all nested content
|
|
- Local labels at same indentation as containing block
|
|
- Conditional code indented under branch:
|
|
|
|
```asm
|
|
JSL Sprite_CheckActive : BCC .inactive
|
|
; Active sprite code (indented)
|
|
JSR DoActiveStuff
|
|
.inactive
|
|
PLB
|
|
RTL
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Processor State Management
|
|
|
|
### 4.1 Size Suffixes (REQUIRED)
|
|
|
|
Always use explicit size suffixes when processor state matters:
|
|
|
|
```asm
|
|
; Good - explicit sizes
|
|
LDA.w #$1234 ; 16-bit load
|
|
LDA.b #$12 ; 8-bit load
|
|
STA.l $7E0000 ; Long address
|
|
|
|
; Bad - ambiguous
|
|
LDA #$12 ; Is this 8-bit or 16-bit?
|
|
```
|
|
|
|
### 4.2 State Preservation
|
|
|
|
```asm
|
|
; Functions called from unknown context
|
|
SomePublicFunction:
|
|
{
|
|
PHP ; Save caller's state
|
|
SEP #$30 ; Set known state (8-bit A, X/Y)
|
|
|
|
; ... function body ...
|
|
|
|
PLP ; Restore caller's state
|
|
RTL
|
|
}
|
|
|
|
; Internal helpers can assume state from caller
|
|
.helper:
|
|
; Assume 8-bit A from caller
|
|
LDA.b #$00
|
|
RTS
|
|
```
|
|
|
|
### 4.3 Processor Mode Macros
|
|
|
|
Use these macros for clarity:
|
|
|
|
```asm
|
|
%m8() ; SEP #$20 - 8-bit accumulator
|
|
%m16() ; REP #$20 - 16-bit accumulator
|
|
%a8() ; SEP #$20 - 8-bit A (alias)
|
|
%a16() ; REP #$20 - 16-bit A (alias)
|
|
%index8() ; SEP #$10 - 8-bit X/Y
|
|
%index16() ; REP #$10 - 16-bit X/Y
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Hook and Patch Patterns
|
|
|
|
### 5.1 Small Patches (pushpc/pullpc)
|
|
|
|
```asm
|
|
pushpc
|
|
org $1EF27D
|
|
ShopItem_Banana:
|
|
{
|
|
JSR $F4CE ; SpriteDraw_ShopItem
|
|
; Custom code here
|
|
RTS
|
|
}
|
|
assert pc() <= $1EF2AB ; Ensure we fit
|
|
pullpc
|
|
```
|
|
|
|
### 5.2 Hook Pattern
|
|
|
|
```asm
|
|
; In patches.asm or near related code
|
|
pushpc
|
|
org $02XXXX ; Vanilla address
|
|
JSL MyCustomHook ; 4-byte JSL
|
|
NOP ; Pad if needed
|
|
pullpc
|
|
|
|
; The hook implementation
|
|
MyCustomHook:
|
|
{
|
|
; Preserve any clobbered code
|
|
JSL OriginalRoutine
|
|
|
|
; Add custom logic
|
|
LDA.w CustomFlag : BEQ .skip
|
|
JSL DoCustomThing
|
|
.skip
|
|
|
|
RTL
|
|
}
|
|
```
|
|
|
|
### 5.3 Hook Documentation
|
|
|
|
```asm
|
|
; =========================================================
|
|
; Hook: $02XXXX - Link's Y-Button Handler
|
|
; Purpose: Add custom item handling for Magic Bag
|
|
; Vanilla Code Replaced: JSL $07F44C
|
|
; Side Effects: Clobbers A
|
|
; =========================================================
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Comments and Documentation
|
|
|
|
### 6.1 Section Dividers
|
|
|
|
Use exactly 57 equal signs:
|
|
|
|
```asm
|
|
; =========================================================
|
|
; Section Name
|
|
; =========================================================
|
|
```
|
|
|
|
### 6.2 Bitfield Documentation
|
|
|
|
```asm
|
|
; Bitfield: hmwo oooo
|
|
; o - OAM slot count (bits 0-4)
|
|
; w - Wall-seeking behavior
|
|
; m - Master sword ceremony flag
|
|
; h - Harmless (no contact damage)
|
|
SprNbrOAM = $0E40
|
|
```
|
|
|
|
### 6.3 TODO Comments
|
|
|
|
```asm
|
|
; TODO: Add chase animation when player is detected
|
|
; TODO(scawful): Refactor this to use lookup table
|
|
```
|
|
|
|
### 6.4 Magic Number Documentation
|
|
|
|
```asm
|
|
LDA.b #$08 ; 8-frame animation delay
|
|
CMP.w #$0100 ; Check if past screen boundary (256px)
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Memory and Data Structures
|
|
|
|
### 7.1 Struct Definitions
|
|
|
|
```asm
|
|
struct TimeState $7EE000
|
|
{
|
|
.Hours: skip 1
|
|
.Minutes: skip 1
|
|
.Speed: skip 1
|
|
.Padding: skip 13
|
|
.BlueVal: skip 2
|
|
.GreenVal: skip 2
|
|
.RedVal: skip 2
|
|
}
|
|
endstruct
|
|
```
|
|
|
|
### 7.2 Inline Data Tables
|
|
|
|
```asm
|
|
Sprite_Draw:
|
|
{
|
|
; ... draw code ...
|
|
RTS
|
|
|
|
; Data tables use dot notation
|
|
.start_index
|
|
db $00, $04, $08, $0C
|
|
.nbr_of_tiles
|
|
db 3, 3, 3, 3
|
|
.x_offsets
|
|
dw 4, -4, 4, -4
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Error Prevention Checklist
|
|
|
|
### Before Submitting Code
|
|
|
|
- [ ] All labels use correct PascalCase_With_Underscores
|
|
- [ ] All local labels use .dot_notation
|
|
- [ ] Size suffixes on all ambiguous loads/stores
|
|
- [ ] JSL/RTL and JSR/RTS pairs matched correctly
|
|
- [ ] Hooks have `assert` statements to prevent overflow
|
|
- [ ] Magic numbers have inline comments explaining purpose
|
|
- [ ] Sprite properties in standard order
|
|
- [ ] Section dividers between major code blocks
|
|
|
|
### Common Crash Causes
|
|
|
|
1. **JSL/JSR mismatch** - Using RTL with JSR or RTS with JSL
|
|
2. **Bank crossing** - Forgetting to set DBR with PHK:PLB
|
|
3. **Processor state** - Assuming 8-bit when 16-bit or vice versa
|
|
4. **Hook overflow** - Patch exceeds available space
|
|
5. **Missing pullpc** - Stack imbalance from pushpc
|
|
|
|
---
|
|
|
|
## 9. Oracle of Secrets Specific Patterns
|
|
|
|
### 9.1 Sprite Entry Point Pattern
|
|
|
|
```asm
|
|
Sprite_MyEnemy_Long:
|
|
{
|
|
PHB : PHK : PLB ; Set data bank to current bank
|
|
JSR Sprite_MyEnemy_Draw
|
|
JSL Sprite_DrawShadow
|
|
JSL Sprite_CheckActive : BCC .inactive
|
|
JSR Sprite_MyEnemy_Main
|
|
.inactive
|
|
PLB
|
|
RTL
|
|
}
|
|
```
|
|
|
|
### 9.2 State Machine Pattern
|
|
|
|
```asm
|
|
Sprite_MyEnemy_Main:
|
|
{
|
|
LDA.w SprAction, X
|
|
JSL JumpTableLocal
|
|
dw State_Idle
|
|
dw State_Chase
|
|
dw State_Attack
|
|
dw State_Retreat
|
|
|
|
State_Idle:
|
|
{
|
|
%PlayAnimation(0,1,16)
|
|
; Check for player proximity
|
|
JSL Sprite_CheckDamageToLink
|
|
BCC .stay_idle
|
|
INC.w SprAction, X ; Transition to Chase
|
|
.stay_idle
|
|
RTS
|
|
}
|
|
; ... more states ...
|
|
}
|
|
```
|
|
|
|
### 9.3 SRAM Flag Checking
|
|
|
|
```asm
|
|
; Check Oracle progression flag
|
|
LDA.l OOSPROG : AND.b #$01 : BEQ .not_complete
|
|
; Player has completed this milestone
|
|
.not_complete
|
|
|
|
; Bitfield reference for OOSPROG ($7EF3D6):
|
|
; .fmp h.i.
|
|
; i = Intro complete
|
|
; h = Hall of Secrets visited
|
|
; p = Pendant progress
|
|
; m = Master Sword acquired
|
|
; f = Fortress of Secrets
|
|
```
|
|
|
|
---
|
|
|
|
## 10. AI Agent Instructions
|
|
|
|
### 10.1 Before Writing Code
|
|
|
|
1. **Read existing patterns** - Search for similar implementations
|
|
2. **Check memory map** - Verify address usage won't conflict
|
|
3. **Identify hook points** - Use Hyrule Historian to find vanilla code
|
|
4. **Verify bank space** - Check MemoryMap.md for free space
|
|
|
|
### 10.2 When Modifying Code
|
|
|
|
1. **Always read the file first** - Never assume structure
|
|
2. **Match existing style** - Follow patterns in the same file
|
|
3. **Use explicit sizes** - Never rely on assumed processor state
|
|
4. **Add assert statements** - Prevent silent overflow errors
|
|
|
|
### 10.3 After Writing Code
|
|
|
|
1. **Run build** - Use `mcp__book-of-mudora__run_build()`
|
|
2. **Run lint** - Use `mcp__book-of-mudora__lint_asm()`
|
|
3. **Verify in emulator** - Test before marking complete
|
|
|
|
---
|
|
|
|
## Appendix: Common Macro Reference
|
|
|
|
### Animation and Drawing
|
|
- `%PlayAnimation(start, end, speed)` - Animate sprite frames
|
|
- `%DrawSprite(...)` - Draw sprite OAM
|
|
- `%SetFrame(n)` - Set current animation frame
|
|
|
|
### Sprite Control
|
|
- `%GotoAction(n)` - Change sprite state
|
|
- `%SetTimer*(n)` - Set various timers
|
|
- `%SetSpriteSpeed*(n)` - Set movement speed
|
|
- `%SetHarmless()` / `%SetImpervious()` - Damage flags
|
|
|
|
### Player
|
|
- `%PreventPlayerMovement()` / `%AllowPlayerMovement()`
|
|
- `%GetPlayerRupees()` - Return rupee count
|
|
- `%ShowUnconditionalMessage(id)` - Display dialogue
|
|
- `%ShowSolicitedMessage(id)` - Display on interaction
|
|
|
|
### Audio
|
|
- `%PlaySFX1(id)` / `%PlaySFX2(id)` - Sound effects
|
|
- `%PlayMusic(id)` - Change music
|
|
|
|
### Debugging
|
|
- `%print_debug(msg)` - Build-time debug output
|
|
- `%log_section(name, flag)` - Conditional section logging
|