Files
oracle-of-secrets/Docs/Core/StyleGuide.md

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