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

12 KiB

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)

; =========================================================
; File: sprites/enemies/my_enemy.asm
; Purpose: [Brief description of what this file implements]
; Author: [Your name or handle]
; =========================================================

1.2 Section Organization

; =========================================================
; 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:

; 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:

Sprite_Move:
{
  LDA.w SprAction, X : BNE .already_moving
    ; Start movement
    INC.w SprAction, X
  .already_moving
  RTS
}

2.3 Constants and Macros

; 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:

!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):

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:
  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:

; 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

; 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:

%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)

pushpc
org $1EF27D
ShopItem_Banana:
{
  JSR $F4CE             ; SpriteDraw_ShopItem
  ; Custom code here
  RTS
}
assert pc() <= $1EF2AB  ; Ensure we fit
pullpc

5.2 Hook Pattern

; 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

; =========================================================
; 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:

; =========================================================
; Section Name
; =========================================================

6.2 Bitfield Documentation

; 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

; TODO: Add chase animation when player is detected
; TODO(scawful): Refactor this to use lookup table

6.4 Magic Number Documentation

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

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

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

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

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

; 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