Files
oracle-of-secrets/Docs/General/Troubleshooting.md
scawful 26d35364af Add advanced technical documentation for ZScream custom overworld
- Introduced comprehensive documentation covering internal hook architecture, memory management, graphics loading pipeline, sprite loading system, cross-namespace integration, performance considerations, and debugging strategies.
- Detailed sections on adding custom features and modifying existing behaviors, including examples and code snippets.
- Included a complete hook list and memory map quick reference for developers.
2025-10-03 15:50:34 -04:00

1347 lines
30 KiB
Markdown

# Oracle of Secrets - Troubleshooting Guide
**Version:** 1.0
**Last Updated:** October 3, 2025
**Audience:** ALTTP ROM hack developers, ZScream users, Oracle of Secrets contributors
---
## Table of Contents
1. [Introduction](#introduction)
2. [BRK Crashes](#brk-crashes)
3. [Stack Corruption](#stack-corruption)
4. [Processor Status Register Issues](#processor-status-register-issues)
5. [Cross-Namespace Calling](#cross-namespace-calling)
6. [Memory Conflicts](#memory-conflicts)
7. [Graphics and DMA Issues](#graphics-and-dma-issues)
8. [ZScream-Specific Issues](#zscream-specific-issues)
9. [Build Errors](#build-errors)
10. [Debugging Tools and Techniques](#debugging-tools-and-techniques)
---
## Introduction
This guide covers common issues encountered when developing ALTTP ROM hacks, particularly for the Oracle of Secrets project which uses:
- **65816 Assembly** (SNES processor)
- **Asar v1.9+** (assembler)
- **ZScream Custom Overworld System**
- **Mixed namespace architecture** (Oracle{} and ZScream)
Understanding the root causes of these issues will help you debug faster and write more stable code.
---
## BRK Crashes
### What is a BRK Crash?
A **BRK crash** occurs when the SNES executes a `BRK` instruction (`$00` opcode), triggering a software interrupt. In ALTTP, this is used as an error handler that:
1. Halts game execution
2. Displays a debug screen (if enabled)
3. Shows the crash location in ROM
### Common Causes
#### 1. **Jumping to Invalid Memory**
```asm
; BAD: $00 is typically uninitialized ROM space
JMP $0000
; GOOD: Jump to known valid code
JMP $008000
```
#### 2. **RTL/RTS Without Matching JSL/JSR**
```asm
SomeFunction:
{
; ... code ...
RTL ; ← Pops garbage from stack, jumps to random location
}
; Later, execution hits $00 (BRK) in uninitialized space
```
**Solution:** Always ensure subroutines are called before returning:
```asm
MainCode:
JSL SomeFunction ; ← Must call it!
SomeFunction:
; ... code ...
RTL ; ← Now safe
```
#### 3. **Incorrect Bank Byte**
```asm
; BAD: Bank $00 doesn't contain code at $C000
JML $00C000
; GOOD: Use correct bank
JML $02C000 ; Bank $02 contains overworld code
```
#### 4. **Data Corruption**
If RAM locations `$00-$01` (used for indirect addressing) get corrupted:
```asm
LDA.b ($00) ; If $00/$01 = $0000, reads from $00:0000 (likely BRK)
```
### How to Find the Crash Location
#### In Mesen-S (Recommended):
1. **Enable "Break on BRK"**: Debugger → Settings → Break on BRK
2. When crash occurs, debugger shows:
- **PC (Program Counter)**: Address where BRK was executed
- **Stack contents**: Shows return addresses (where you came from)
3. **Read the stack** to trace back:
```
Stack at $01F0: 82 9A 02 ← Came from $029A82
Stack at $01ED: 45 C4 09 ← Came from $09C445
```
#### In BSNES-Plus:
1. Tools → Debugger
2. Set breakpoint on `$000000` (BRK opcode)
3. When triggered, examine:
- S register (Stack Pointer)
- Memory viewer at `$7E0100 + S` to see stack contents
#### Manual Method:
1. Note the **last known good action** (e.g., "crashed when entering area $2B")
2. Search codebase for hooks in that area:
```bash
grep -r "0283EE\|02A9C4" Overworld/
```
3. Add temporary tracking code:
```asm
LDA.b #$42 : STA.l $7F5000 ; Breadcrumb marker
```
### Prevention Strategies
1. **Initialize jump tables properly**:
```asm
JumpTable:
dw Function1 ; ← Must be valid addresses
dw Function2
dw Function3
```
2. **Validate indices before using jump tables**:
```asm
LDA.b $00 ; Index
CMP.b #$06 ; Max entries
BCC .valid
LDA.b #$00 ; Default to 0
.valid
ASL A ; * 2 for word table
TAX
JMP (JumpTable, X)
```
3. **Use assertions** (Asar):
```asm
assert pc() <= $09C50D ; Ensure code doesn't overflow
```
---
## Stack Corruption
### Understanding the 65816 Stack
The stack on SNES:
- Lives at `$7E0100-$7E01FF` (256 bytes)
- **Grows downward** (S register decrements)
- Used for:
- JSR/JSL (pushes return address)
- PHA/PHX/PHY (pushes registers)
- Interrupts (NMI pushes PC, Status)
### Common Issues
#### 1. **Unbalanced Push/Pop**
```asm
; BAD: 3 pushes, 2 pops
PHX
PHY
PHA
PLA
PLY
; ← Missing PLX! Stack is now corrupted (+1 byte)
RTL ; Returns to wrong address
```
**Solution:** Always balance:
```asm
PHX
PHY
PHA
; ... code ...
PLA
PLY
PLX ; ← Now balanced
RTL
```
#### 2. **JSR/JSL vs RTS/RTL Mismatch**
```asm
; BAD: JSL pushes 3 bytes, RTS pops 2 bytes
MainFunction:
JSL SubFunction ; Pushes $02 $C4 $09 (3 bytes)
SubFunction:
; ... code ...
RTS ; ← Pops only 2 bytes! Stack corrupted
```
**Solution:** Match call/return types:
```asm
MainFunction:
JSL SubFunction ; JSL (long call)
SubFunction:
; ... code ...
RTL ; ← RTL (long return) - matches!
```
**Memory Map:**
- **JSR**: Pushes 2 bytes (PC - 1), within same bank
- **JSL**: Pushes 3 bytes (PBR, PC - 1), cross-bank
- **RTS**: Pops 2 bytes, increments, continues
- **RTL**: Pops 3 bytes (bank + address), increments, continues
#### 3. **Stack Overflow**
If S register goes below `$0100`, stack wraps to page $00 (Direct Page), corrupting variables:
```asm
; Example: Deep recursion
RecursiveFunction:
JSL RecursiveFunction ; ← Each call uses 3 bytes
RTL
; Eventually: S < $0100, corrupts $7E0000+ variables
```
**Solution:**
- Limit recursion depth
- Use iteration instead of recursion when possible
- Check S register if suspicious:
```asm
TSC ; Transfer Stack to C (16-bit accumulator)
CMP.w #$01C0 ; Check if below safe threshold
BCS .safe
; Handle stack overflow
.safe
```
### Debugging Stack Issues
1. **Set watchpoint on stack pointer**:
- Mesen-S: Debugger → Add Breakpoint → Type: Execute → Address: `S < $01C0`
2. **Track stack manually**:
```asm
; Add to suspicious functions:
PHP : PLA : STA.l $7F5000 ; Save processor status
TSC : STA.l $7F5002 ; Save stack pointer
```
3. **Check stack balance** in emulator:
- Note S register before JSL: `S = $01F3`
- After RTL, S should be: `S = $01F3` (same value)
- If different, you have unbalanced stack operations
---
## Processor Status Register Issues
### Understanding the P Register
The **Processor Status Register (P)** controls 65816 behavior:
```
P = [N V M X D I Z C]
│ │ │ │ │ │ │ └─ Carry flag
│ │ │ │ │ │ └─── Zero flag
│ │ │ │ │ └───── IRQ disable
│ │ │ │ └─────── Decimal mode
│ │ │ └───────── Index register size (X/Y): 0=16-bit, 1=8-bit
│ │ └─────────── Memory/Accumulator size (A): 0=16-bit, 1=8-bit
│ └───────────── Overflow flag
└─────────────── Negative flag
```
**Most critical:** **M flag** (bit 5) and **X flag** (bit 4)
### Common Issues
#### 1. **Wrong Accumulator Size**
```asm
REP #$20 ; A = 16-bit (M flag = 0)
LDA.w #$1234 ; A = $1234
SEP #$20 ; A = 8-bit (M flag = 1)
LDA.w #$1234 ; ← ERROR: Assembler generates LDA #$34, $12 becomes opcode!
```
**Correct:**
```asm
SEP #$20 ; A = 8-bit
LDA.b #$12 ; Load 8-bit value only
```
#### 2. **Mismatched Index Sizes**
```asm
REP #$30 ; A=16-bit, X/Y=16-bit
LDX.w #$0120 ; X = $0120
SEP #$10 ; X/Y now 8-bit!
LDX.b #$20 ; X = $0020 (high byte cleared!)
LDA $7E0000, X ; ← Reads from $7E0020, not $7E0120!
```
**Solution:** Be explicit about sizes:
```asm
REP #$30 ; Both A and X/Y are 16-bit
LDX.w #$0120
LDA.l $7E0000, X ; Explicit long addressing
SEP #$30 ; Back to 8-bit for safety
```
#### 3. **Missing SEP/REP After JSL**
```asm
MainFunction:
REP #$30 ; Set 16-bit mode
JSL SubFunction ; Call other function
; What mode are we in now? We don't know!
LDA.w $1234 ; ← May fail if SubFunction left us in 8-bit mode
```
**Best Practice:**
```asm
MainFunction:
REP #$30 ; Set 16-bit mode
JSL SubFunction
SEP #$30 ; ← Always reset to known state!
; Now we know we're in 8-bit mode
```
Or use calling conventions:
```asm
SubFunction:
; Save processor status on entry
PHP
; ... do work ...
PLP ; Restore processor status on exit
RTL
```
#### 4. **Cross-Bank Processor State**
ZScream operates in different contexts than Oracle code:
```asm
namespace Oracle
{
MainCode:
REP #$20 ; Oracle code uses 16-bit
JSL ZScreamFunction ; ← Crosses namespace!
; ZScream may leave us in 8-bit mode
LDA.w $1234 ; ← Potential error
}
; In ZScream (no namespace)
ZScreamFunction:
SEP #$20 ; ZScream uses 8-bit
; ... work ...
RTL ; ← Doesn't restore Oracle's 16-bit mode
```
**Solution:** Use PHP/PLP or establish conventions:
```asm
namespace Oracle
{
MainCode:
PHP ; Save current mode
JSL ZScreamFunction
PLP ; Restore Oracle's mode
}
```
### Prevention
1. **Always initialize at function start**:
```asm
MyFunction:
PHP ; Save caller's state
SEP #$30 ; Set to known 8-bit state
; ... code ...
PLP ; Restore caller's state
RTL
```
2. **Use Asar's rep/sep warnings**:
```asm
rep #$20 ; Lowercase = warning if you use 8-bit operations next
lda.w #$1234
```
3. **Document function register sizes** in comments:
```asm
; Function: CalculateOffset
; Inputs: A=16-bit (offset), X=8-bit (index)
; Outputs: A=16-bit (result)
; Clobbers: None
; Status: Returns with P unchanged (uses PHP/PLP)
```
---
## Cross-Namespace Calling
### Oracle of Secrets Namespace Architecture
```asm
namespace Oracle ; Most custom code
{
; Oracle code here
}
; ZScream code is NOT in a namespace (vanilla bank space)
```
### The Visibility Problem
**From inside `Oracle{}` namespace:**
- ✅ Can call Oracle labels directly: `JSL OracleFunction`
- ❌ **Cannot** call ZScream labels directly: `JSL ZScreamFunction` fails
- ✅ **Must use** `Oracle_` prefix: `JSL Oracle_ZScreamFunction`
**Why?** Asar namespaces create separate symbol tables. ZScream labels must be explicitly exported to Oracle namespace.
### Common Errors
#### 1. **Calling ZScream from Oracle Without Prefix**
```asm
namespace Oracle
{
MainCode:
JSL LoadOverworldSprites_Interupt ; ← ERROR: Label not found
}
```
**Error Message:**
```
error: (MainCode.asm:45): label "LoadOverworldSprites_Interupt" not found
```
**Solution:**
```asm
namespace Oracle
{
MainCode:
JSL Oracle_LoadOverworldSprites_Interupt ; ← Use Oracle_ prefix
}
```
#### 2. **Forgetting to Export ZScream Label**
In `ZSCustomOverworld.asm`, labels must be exported:
```asm
; Define ZScream function
LoadOverworldSprites_Interupt:
{
; ... code ...
RTL
}
; Export to Oracle namespace
namespace Oracle
{
Oracle_LoadOverworldSprites_Interupt = LoadOverworldSprites_Interupt
}
```
If missing the export, Oracle code can't call it!
#### 3. **Wrong Call Direction**
```asm
; ZScream code (no namespace)
ZScreamFunction:
JSL Oracle_SomeFunction ; ← ERROR: ZScream isn't in Oracle namespace!
```
**Solution:** Exit Oracle namespace first:
```asm
namespace Oracle
{
SomeFunction:
; ... code ...
RTL
}
; Export Oracle function for ZScream
Oracle_SomeFunction = Oracle_SomeFunction ; Make visible outside namespace
; Now ZScream can call it
ZScreamFunction:
JSL Oracle_SomeFunction ; ← Works!
```
### The Bridge Function Pattern
When you need ZScream to call Oracle code (e.g., day/night check for sprite loading):
```asm
namespace Oracle
{
CheckIfNight:
; Main implementation (uses Oracle WRAM variables)
LDA.l $7EE000 ; Custom time system
CMP.w #$0012
BCS .night
; ... logic ...
RTL
}
; Bridge function (no namespace = accessible to ZScream)
ZSO_CheckIfNight:
{
PHB
PHK
PLB
; Call Oracle function
JSL Oracle_CheckIfNight ; Can call INTO Oracle namespace
PLB
RTL
}
; Now ZScream hooks can use it
org $09C4C7
LoadOverworldSprites_Interupt:
{
; ... code ...
JSL Oracle_ZSO_CheckIfNight ; ← Bridge function name uses Oracle_ for exports
; ... code ...
}
```
### Best Practices
1. **Naming Convention:**
- `Oracle_` prefix = Exported from any namespace for Oracle to use
- `ZSO_` prefix = Bridge function for ZScream-to-Oracle calls
- Combine: `Oracle_ZSO_CheckIfNight` = Bridge exported to Oracle
2. **Export Block Pattern:**
```asm
namespace Oracle
{
; All Oracle code here
}
; Export block at end of file
namespace Oracle
{
Oracle_ExportedFunction1 = ExportedFunction1
Oracle_ExportedFunction2 = ExportedFunction2
Oracle_ZSO_BridgeFunction = ZSO_BridgeFunction
}
```
3. **Check Build Order:**
In `Oracle_main.asm` or `Meadow_main.asm`:
```asm
incsrc "Core/symbols.asm" ; Define symbols first
incsrc "Overworld/ZSCustomOverworld.asm" ; ZScream next
incsrc "Items/all_items.asm" ; Oracle code after
```
If Oracle code is included before ZScream defines exports, you'll get "label not found" errors!
---
## Memory Conflicts
### Bank Collisions
#### Understanding LoROM Mapping
ALTTP uses **LoROM** memory mapping:
- `$008000-$00FFFF` → ROM $000000-$007FFF (Bank $00)
- `$018000-$01FFFF` → ROM $008000-$00FFFF (Bank $01)
- `$028000-$02FFFF` → ROM $010000-$017FFF (Bank $02)
- ...
- `$208000-$20FFFF` → ROM $100000-$107FFF (Bank $20 / Custom)
**Oracle of Secrets uses banks $20-$41** for custom code (33 banks).
#### Common Collision Errors
##### 1. **Overlapping ORG Statements**
```asm
; File1.asm
org $288000
CustomFunction1:
; 200 bytes of code
; File2.asm
org $288000 ; ← ERROR: Same address!
CustomFunction2:
; Overwrites CustomFunction1!
```
**Asar Error:**
```
warning: (File2.asm:10): overwrote some code here with org/pushpc command
```
**Solution:** Use `assert` to protect boundaries:
```asm
org $288000
CustomFunction1:
; ... code ...
assert pc() <= $288100 ; Reserve space
org $288100 ; Start next function safely
CustomFunction2:
; ... code ...
```
##### 2. **Freespace Conflicts**
```asm
; File1.asm
freecode ; Asar auto-allocates at $208000
; File2.asm
freecode ; ← May allocate at same location if not careful!
```
**Solution:** Use explicit banks:
```asm
; File1.asm
org $208000 ; Explicit bank $20
; File2.asm
org $218000 ; Explicit bank $21
```
##### 3. **Data Pool Overflow**
ZScream uses `$288000-$289938` for data pool (6456 bytes). If you add too much data:
```asm
org $288000
Pool_SpritePointers:
dw SpritesArea00, SpritesArea01, ... ; 160 areas * 6 states * 2 bytes = 1920 bytes
Pool_MapData:
; 5000 bytes
; Total: 6920 bytes - OVERFLOW! Crosses into $289938
```
**Check with:**
```asm
org $288000
; ... all pool data ...
assert pc() <= $289938 ; Verify within bounds
```
### WRAM Conflicts
#### Custom WRAM Region ($7E0730+)
Oracle of Secrets uses `$7E0730-$7E078F` (96 bytes in MAP16OVERFLOW region).
##### Common Issue: Variable Overlap
```asm
; Core/ram.asm
Oracle_FairyCounter = $7E0730 ; 1 byte
; Items/all_items.asm
Oracle_ItemEffect = $7E0730 ; ← ERROR: Same address!
```
**Solution:** Maintain central registry in `Core/ram.asm`:
```asm
; Custom WRAM Block
Oracle_FairyCounter = $7E0730 ; 1 byte - Fairy spawn counter
Oracle_ItemEffect = $7E0731 ; 1 byte - Current item effect
Oracle_BossHealth = $7E0732 ; 2 bytes - Boss HP (16-bit)
; ... continue registry ...
; Always check before adding new:
; Last used: $7E0733
; Available: $7E0734-$7E078F (91 bytes free)
```
### SRAM Conflicts
#### Repurposed Blocks
Oracle of Secrets repurposes vanilla SRAM:
- `$7EF38A-$7EF3C4`: Collectibles (59 bytes)
- `$7EF410-$7EF41F`: Dreams system (16 bytes)
##### Issue: Vanilla Code Still Writes
Some vanilla functions may write to repurposed SRAM. Example:
```asm
; Vanilla function writes to $7EF38A (old value)
; Now you use $7EF38A for masks collected
; Vanilla write corrupts your data!
```
**Solution:** Hook and redirect vanilla writes:
```asm
; Find vanilla write
org $01D234 ; Example address
JSL Oracle_RedirectedSave
NOP
; Your redirect
namespace Oracle
{
RedirectedSave:
; Don't write to $7EF38A anymore
; Or write to new location $7EF500
STA.l $7EF500
RTL
}
```
### Build Errors from Memory Issues
#### "No freespace large enough"
```
error: no freespace large enough found
```
**Cause:** Tried to use `freecode` but all banks are full.
**Solution:**
1. Check bank usage: `grep -r "org \$2[0-9]8000" .`
2. Add new bank range in Asar (if using freespace manager)
3. Use explicit `org` in unused bank
#### "PC out of bounds"
```
error: (file.asm:45): PC $29A000 is out of bounds
```
**Cause:** Code exceeded bank boundary ($xx8000-$xxFFFF in LoROM).
**Solution:**
```asm
; Check PC before running out of space
org $298000
LargeFunction:
; ... lots of code ...
assert pc() < $2A0000 ; Would fail and show where overflow happens
; Split across banks instead:
org $298000
LargeFunction_Part1:
; ... code ...
JML LargeFunction_Part2
org $2A8000
LargeFunction_Part2:
; ... more code ...
```
---
## Graphics and DMA Issues
### Blank Screen
#### Symptoms
- Game boots, music plays, but screen is black
- Or screen is garbled/corrupted
#### Common Causes
##### 1. **Force Blank Not Released**
```asm
LDA.b #$80
STA $2100 ; Force blank (screen off)
; ... forgot to turn it back on ...
```
**Solution:** Always release force blank:
```asm
LDA.b #$80
STA $2100 ; Force blank ON
; ... do VRAM updates ...
LDA.b #$0F
STA $2100 ; Force blank OFF, brightness 15
```
##### 2. **VRAM Upload During Active Display**
```asm
; BAD: Writing to VRAM while screen is on
LDA.b #$80
STA $2115 ; VRAM port control
LDA.b #$00
STA $2116 ; VRAM address low
LDA.b #$20
STA $2117 ; VRAM address high
LDA.b #$FF
STA $2118 ; ← CORRUPTS VRAM if screen is on!
```
**Solution:** Use NMI or force blank:
```asm
; Method 1: During NMI
NMI_Handler:
; Screen is in VBlank, safe to write
LDA.b #$FF
STA $2118
; Method 2: Force blank
LDA.b #$80 : STA $2100 ; Screen off
LDA.b #$FF : STA $2118 ; Write to VRAM
LDA.b #$0F : STA $2100 ; Screen on
```
##### 3. **Corrupted Stripe Data**
ZScream uses stripe system for VRAM uploads. If stripe data is malformed:
```asm
; Stripe format: [Size] [Address Low] [Address High] [Data...] [00=End]
; BAD: Missing terminator
.stripe_data
db $04, $00, $20 ; Upload 4 bytes to $2000
db $FF, $FF, $FF, $FF
; ← Missing $00 terminator! Continues reading garbage
```
**Solution:**
```asm
.stripe_data
db $04, $00, $20 ; Upload 4 bytes to $2000
db $FF, $FF, $FF, $FF
db $00 ; ← Terminator
```
### Flickering Graphics
#### Cause: DMA During Active Display
```asm
; BAD: DMA while screen is active causes flickering
LDA.b #$01
STA $4300 ; DMA mode
; ... setup DMA ...
LDA.b #$01
STA $420B ; ← Trigger DMA mid-frame = flicker
```
**Solution:** Only DMA during VBlank (NMI):
```asm
NMI_Handler:
; Safe: We're in VBlank period
LDA.b #$01
STA $420B ; Trigger DMA
RTI
```
### Missing Tiles / Wrong Graphics
#### Symptoms
- Link appears as wrong sprite
- Enemies are garbled
- Tileset is corrupted
#### Common Causes
##### 1. **Wrong Graphics Slot**
ZScream uses 7 graphics slots:
- Slot 0-2: Static (BG graphics)
- Slot 3-6: Variable (Sprite graphics)
```asm
; BAD: Loading Link graphics to wrong slot
LDA.b #$05 ; Slot 5
LDX.w #LinkGFX
JSL LoadGraphicsSlot ; ← Overwrites enemy sprites!
```
**Solution:** Follow slot conventions:
```asm
; Slot 3: Usually Link graphics
; Slot 4: Enemy set 1
; Slot 5: Enemy set 2
; Slot 6: Special sprites
LDA.b #$03 ; Slot 3 = Link
LDX.w #LinkGFX
JSL LoadGraphicsSlot
```
Refer to `Docs/World/Overworld/ZSCustomOverworldAdvanced.md` Section 3 for detailed slot assignments.
##### 2. **Sprite Pointer Table Corruption**
```asm
; Sprite pointer table must be valid addresses
Pool_Overworld_SpritePointers_state_0_New:
dw Area00_Sprites, Area01_Sprites, ...
dw $0000 ; ← BAD: Null pointer causes crash
```
**Solution:**
```asm
Pool_Overworld_SpritePointers_state_0_New:
dw Area00_Sprites, Area01_Sprites, ...
dw EmptySprites ; ← Valid empty list
EmptySprites:
db $FF ; Terminator (no sprites)
```
### Palette Issues
#### Wrong Colors
##### Cause: Palette Not Updated
```asm
; Changed area but didn't reload palettes
LDA.b #$2B ; New area
STA.w $040A
; ← Missing: JSL OverworldPalettesLoader
```
**Solution:**
```asm
LDA.b #$2B
STA.w $040A
JSL OverworldPalettesLoader ; Load palettes for area $2B
```
#### Day/Night Palette Mismatch
If day/night transition doesn't update palettes:
```asm
; In time system transition
.switch_to_night
LDA.b #$01
STA.l $7EE001 ; Set night flag
; ← Missing: Reload palettes
JSL Oracle_LoadTimeBasedPalettes
```
---
## ZScream-Specific Issues
### Sprite Loading Fails
#### Symptom
- Sprites don't appear in custom overworld area
- Error: "No sprites loaded"
#### Cause 1: Missing Sprite Pointer Entry
```asm
Pool_Overworld_SpritePointers_state_0_New:
dw Area00_Sprites, Area01_Sprites, ...
; Only 80 entries, but you added area $81
```
**Solution:** Extend table:
```asm
Pool_Overworld_SpritePointers_state_0_New:
dw Area00_Sprites, Area01_Sprites, ...
; ... (0x00-0x7F) ...
dw Area80_Sprites, Area81_Sprites ; Add new entries
```
#### Cause 2: Day/Night State Not Handled
```asm
; Sprite loading checks day/night with Oracle_ZSO_CheckIfNight
; If that function doesn't exist or returns wrong value:
ZSO_CheckIfNight:
LDA.l $7EF3C5 ; Always returns day state
RTL ; ← Forgets to check $7EE000 (time)
```
**Solution:** Verify `ZSO_CheckIfNight` implementation (see Troubleshooting Section 5).
### Map16 Stripes Fail
#### Symptom
- Custom tiles don't appear
- Map16 changes aren't visible
#### Cause: Stripe Buffer Overflow
```asm
; ZScream uses 3 stripe buffers: $7EC800, $7ED800, $7EE800
; Each 2048 bytes
.generate_stripes
LDX.w #$0000
.loop
; Generate 1 tile worth of stripes (9 bytes)
; Loop 300 times = 2700 bytes
; ← OVERFLOWS! Corrupts other memory
```
**Solution:** Check buffer size:
```asm
.generate_stripes
LDX.w #$0000
.loop
; Generate stripe
INX #9
CPX.w #$0800 ; Check if buffer full (2048 bytes)
BCS .buffer_full
; Continue...
BRA .loop
.buffer_full
; Stop generating
```
### Transition Hangs
#### Symptom
- Game freezes when transitioning between areas
- Music continues but screen is stuck
#### Cause 1: Infinite Loop in Hook
```asm
org $0283EE ; Overworld transition hook
Oracle_CustomTransition:
JSL SomeFunction
JMP Oracle_CustomTransition ; ← Infinite loop!
```
**Solution:**
```asm
org $0283EE
Oracle_CustomTransition:
JSL SomeFunction
JMP $0283F3 ; Jump to vanilla code after hook
```
#### Cause 2: Module Not Advanced
```asm
Oracle_TransitionHook:
; ... custom transition logic ...
; ← Forgot to increment $10 (module)
RTL
```
**Solution:**
```asm
Oracle_TransitionHook:
; ... custom transition logic ...
INC.b $10 ; Advance module
RTL
```
### Missing Day/Night Graphics
#### Symptom
- Day sprites show at night
- Night enemies appear during day
#### Cause: Sprite Pointer Table Only Has 3 States
```asm
; You only defined 3 game states:
Pool_Overworld_SpritePointers_state_0_New:
dw Area00_Day, Area01_Day, ...
; But Oracle_ZSO_CheckIfNight returns state 4-5 for night!
```
**Solution:** Define all 6 states (day + night):
```asm
Pool_Overworld_SpritePointers_state_0_New:
; State 0 day (entries $00-$9F)
dw Area00_Day, Area01_Day, ...
; State 0 night (entries $A0-$13F)
dw Area00_Night, Area01_Night, ...
; State 1 day (entries $140-$1DF)
dw Area00_Day, Area01_Day, ...
; State 1 night (entries $1E0-$27F)
dw Area00_Night, Area01_Night, ...
; State 2 day/night (similar structure)
```
See `Docs/World/Overworld/ZSCustomOverworldAdvanced.md` Section 4.3 for phase offset calculation.
---
## Build Errors
### Asar Assembler Errors
#### "label not found"
```
error: (file.asm:42): label "CustomFunction" not found
```
**Causes:**
1. **Typo in label name**
2. **Label defined after use** (in some cases)
3. **Namespace issue** (see Section 5)
4. **File not included** in main assembly file
**Solutions:**
```asm
; 1. Check spelling
JMP CustomFunction ; ← Check this matches definition
CustomFunction: ; ← Must be exact match (case-sensitive)
; 2. Move definition before use (if forward reference fails)
; 3. Check namespace
namespace Oracle
{
JSL Oracle_CustomFunction ; ← Need prefix
}
; 4. Include file
; In Oracle_main.asm:
incsrc "Custom/file.asm" ; ← Make sure file is included
```
#### "redefined label"
```
error: (file.asm:100): label "CustomFunction" redefined
```
**Cause:** Same label defined twice.
**Solution:**
```asm
; BAD: Two definitions
CustomFunction:
RTL
CustomFunction: ; ← ERROR
RTL
; GOOD: Use sublabels
CustomFunction:
.entry_point1
RTL
.entry_point2
RTL
```
#### "org or pushpc/pullpc not at start of line"
```
error: (file.asm:50): org or pushpc/pullpc not at start of line
```
**Cause:** Asar requires `org`, `pushpc`, `pullpc` as first statement on line.
**Solution:**
```asm
; BAD
org $288000 ; ← Indented
; GOOD
org $288000 ; ← Column 1
```
#### "assertion failed"
```
error: (file.asm:200): assertion failed: pc() <= $09C50D
note: (file.asm:200): expanded from: 04C510 <= 04C50D
```
**Meaning:** Your code exceeded allowed space.
**Solution:**
```asm
org $09C4C7
CustomHook:
; ... 300 bytes of code ...
; PC is now at $09C55F
assert pc() <= $09C50D ; FAILS: $09C55F > $09C50D
; Fix: Move some code elsewhere
org $09C4C7
CustomHook:
; Minimal hook
JSL CustomHook_Main ; Jump to freespace
NOP : NOP
assert pc() <= $09C50D ; ← Now passes
org $288000
CustomHook_Main:
; Main implementation in freespace
; ... 300 bytes ...
RTL
```
---
## Debugging Tools and Techniques
### Emulator Debuggers
#### Mesen-S (Recommended)
**Features:**
- Powerful CPU debugger with execution breakpoints
- Memory viewer with live updates
- Conditional breakpoints: `A == #$42`
- Stack viewer
- Event viewer (shows NMI, IRQ timing)
**Usage:**
1. Tools → Debugger (F7)
2. Set breakpoint: Click line number in disassembly
3. Run until breakpoint hit
4. Examine registers (A, X, Y, P, S, PC, DB, PB)
**Advanced:**
```
Conditional breakpoint examples:
- Break when A equals value: A == #$0F
- Break when memory changes: [W]$7E0730
- Break when PC in range: PC >= $288000 && PC < $289000
- Break on BRK: PC == $000000
```
#### BSNES-Plus
**Features:**
- Cycle-accurate emulation (best for timing-sensitive debugging)
- Memory editor with search
- Tilemap viewer
- VRAM viewer
**Usage:**
1. Tools → Debugger
2. Set breakpoint: Right-click address
3. Memory search: Tools → Memory Editor → Search
### Manual Debugging Techniques
#### 1. **Breadcrumb Tracking**
```asm
; Add at suspected crash location
LDA.b #$01 : STA.l $7F5000 ; Breadcrumb 1
JSL SuspiciousFunction
LDA.b #$02 : STA.l $7F5000 ; Breadcrumb 2
; If crash occurs between breadcrumbs, $7F5000 = $01
; After crash, check $7F5000 in memory viewer
```
#### 2. **Register Logging**
```asm
; Log registers to RAM
Oracle_DebugLog:
STA.l $7F5000 ; Save A
PHX
TXA
STA.l $7F5001 ; Save X
PLX
PHY
TYA
STA.l $7F5002 ; Save Y
PLY
RTL
; Use it:
JSL Oracle_DebugLog ; Snapshot registers
JSL ProblematicFunction
JSL Oracle_DebugLog ; Snapshot again - compare
```
#### 3. **Assertion Macros**
```asm
; Define assertion macro
macro assert_equals(address, expected)
PHA
LDA.l <address>
CMP.b #<expected>
BEQ .ok
BRK ; Trigger crash if mismatch
.ok
PLA
endmacro
; Use it:
%assert_equals($7E0730, $05) ; Check variable is expected value
```
#### 4. **PC Tracking**
```asm
; At critical points, save PC to RAM
LDA.b #$01 : STA.l $7F5010 ; Mark "entered function 1"
JSL Function1
LDA.b #$02 : STA.l $7F5010 ; Mark "entered function 2"
JSL Function2
; After crash, $7F5010 shows last function entered
```
### Common Debug Checklist
When encountering an issue:
1. ✅ **Check error message** carefully - Asar errors are usually precise
2. ✅ **Verify namespace** - Is label prefixed correctly?
3. ✅ **Check stack balance** - Equal push/pop counts?
4. ✅ **Verify processor state** - REP/SEP correct for operation?
5. ✅ **Check memory bounds** - Assertions in place?
6. ✅ **Test in Mesen-S first** - Best debugger for SNES
7. ✅ **Use breadcrumbs** - Narrow down crash location
8. ✅ **Check build order** - Files included in correct order?
9. ✅ **Review recent changes** - Compare with known working version
10. ✅ **Read vanilla code** - Understand what you're hooking
---
## Conclusion
This guide covers the most common issues encountered in ALTTP ROM hacking, particularly for Oracle of Secrets. Key takeaways:
- **BRK crashes** are usually caused by invalid jumps or corrupted return addresses
- **Stack corruption** comes from unbalanced push/pop or JSR/JSL vs RTS/RTL mismatches
- **Processor status** (M/X flags) must be carefully managed across function boundaries
- **Namespace issues** require proper exports and `Oracle_` prefixes
- **Memory conflicts** need assertions and careful space management
- **Graphics issues** stem from VRAM timing or corrupted data
- **ZScream issues** often relate to sprite loading tables and day/night state
When stuck:
1. Use Mesen-S debugger
2. Add breadcrumb tracking
3. Verify processor state
4. Check assertions
5. Review vanilla code
For further help:
- Read `Docs/World/Overworld/ZSCustomOverworldAdvanced.md` for ZScream details
- Check `Docs/Core/Ram.md` for memory map
- Review `Docs/General/DevelopmentGuidelines.md` for best practices
Happy debugging! 🎮