5.9 KiB
Overworld Tail Map Expansion (0xA0-0xBF)
Consolidated: 2025-12-08 Status: IMPLEMENTED Owner: zelda3-hacking-expert / overworld-specialist Purpose: Enable editing of special-world tail map slots (0xA0-0xBF) without corrupting existing data
Quick Start
# Apply tail expansion to a ZSCustomOverworld v3 ROM
z3ed overworld-doctor --rom=zelda3.sfc --apply-tail-expansion --output=expanded.sfc
# Or apply manually with Asar (after ZSCustomOverworld v3)
asar assets/patches/Overworld/TailMapExpansion.asm zelda3.sfc
Prerequisites:
- ZSCustomOverworld v3 must be applied first
- ROM must be 2MB (standard expanded size)
Problem Statement
The vanilla pointer tables only have 160 entries (maps 0x00-0x9F).
Original High Table: PC 0x1794D (SNES $02:F94D), 160 entries x 3 bytes
Original Low Table: PC 0x17B2D (SNES $02:FB2D), 160 entries x 3 bytes
Writing to entries for maps 0xA0+ would overwrite existing data, corrupting Light World maps 0x00-0x1F.
Solution: TailMapExpansion.asm
The TailMapExpansion.asm patch relocates pointer tables to safe free space with 192 entries:
| Component | PC Address | SNES Address | Size |
|---|---|---|---|
| Detection Marker | 0x1423FF | $28:A3FF | 1 byte (value: 0xEA) |
| New High Table | 0x142400 | $28:A400 | 576 bytes (192 x 3) |
| New Low Table | 0x142640 | $28:A640 | 576 bytes (192 x 3) |
| Blank Map High | 0x180000 | $30:8000 | 188 bytes |
| Blank Map Low | 0x181000 | $30:9000 | 4 bytes |
Game Code Patches
The patch updates 8 LDA instructions in bank $02:
Function 1: Overworld_DecompressAndDrawOneQuadrant
| PC Address | Original | Patched |
|---|---|---|
| 0x1F59D | LDA.l $02F94D,X |
LDA.l $28A400,X |
| 0x1F5A3 | LDA.l $02F94E,X |
LDA.l $28A401,X |
| 0x1F5C8 | LDA.l $02FB2D,X |
LDA.l $28A640,X |
| 0x1F5CE | LDA.l $02FB2E,X |
LDA.l $28A641,X |
Function 2: Secondary quadrant loader
| PC Address | Original | Patched |
|---|---|---|
| 0x1F7E3 | LDA.l $02F94D,X |
LDA.l $28A400,X |
| 0x1F7E9 | LDA.l $02F94E,X |
LDA.l $28A401,X |
| 0x1F80E | LDA.l $02FB2D,X |
LDA.l $28A640,X |
| 0x1F814 | LDA.l $02FB2E,X |
LDA.l $28A641,X |
ROM Layout (LoROM, no header)
Expanded Data Regions (DO NOT OVERWRITE)
| Region | PC Start | PC End | SNES | Contents |
|---|---|---|---|---|
| Tile16 Expanded | 0x1E8000 | 0x1EFFFF | $3D:0000-$3D:FFFF | 4096 tile16 entries |
| Map32 BL Expanded | 0x1F0000 | 0x1F7FFF | $3E:0000-$3E:7FFF | Map32 bottom-left tiles |
| Map32 BR Expanded | 0x1F8000 | 0x1FFFFF | $3E:8000-$3F:FFFF | Map32 bottom-right tiles |
| ZSCustom Tables | 0x140000 | 0x1421FF | $28:8000+ | Custom overworld arrays |
| Overlay Space | 0x120000 | 0x12FFFF | $24:8000+ | Expanded overlay data |
Safe Free Space
| PC Start | PC End | Size | SNES Bank | Notes |
|---|---|---|---|---|
| 0x1422B2 | 0x17FFFF | ~245 KB | $28-$2F | Primary free space |
| 0x180000 | 0x1DFFFF | 384 KB | $30-$3B | Additional free space |
WARNING: Do NOT use addresses 0x1E0000+ for new data!
CLI Tools
overworld-doctor
Full diagnostic and repair:
# Diagnose only
z3ed overworld-doctor --rom=file.sfc --verbose
# Compare with baseline
z3ed overworld-doctor --rom=file.sfc --baseline=vanilla.sfc
# Apply tile16 fixes
z3ed overworld-doctor --rom=file.sfc --fix --output=fixed.sfc
# Apply tail map expansion
z3ed overworld-doctor --rom=file.sfc --apply-tail-expansion --output=expanded.sfc
# Preview tail expansion (dry run)
z3ed overworld-doctor --rom=file.sfc --apply-tail-expansion --dry-run
overworld-validate
Validate map pointers and decompression:
z3ed overworld-validate --rom=file.sfc # Check maps 0x00-0x9F
z3ed overworld-validate --rom=file.sfc --include-tail # Include 0xA0-0xBF
z3ed overworld-validate --rom=file.sfc --check-tile16 # Check tile16 region
rom-compare
Compare two ROMs:
z3ed rom-compare --rom=target.sfc --baseline=vanilla.sfc --verbose --show-diff
Detection
yaze detects expanded pointer tables by checking for the marker byte:
// In src/zelda3/overworld/overworld.h
bool HasExpandedPointerTables() const {
return rom_->data()[0x1423FF] == 0xEA;
}
The loading code checks BOTH conditions before allowing tail map access:
const bool allow_special_tail =
core::FeatureFlags::get().overworld.kEnableSpecialWorldExpansion &&
HasExpandedPointerTables();
Source Files
| File | Purpose |
|---|---|
assets/patches/Overworld/TailMapExpansion.asm |
ASM patch file |
src/zelda3/overworld/overworld.h |
HasExpandedPointerTables() method |
src/zelda3/overworld/overworld.cc |
Tail map guards |
src/cli/handlers/tools/overworld_doctor_commands.cc |
CLI apply logic |
src/cli/handlers/tools/overworld_validate_commands.cc |
Validator command |
src/cli/handlers/tools/rom_compare_commands.cc |
ROM comparison |
src/cli/handlers/tools/diagnostic_types.h |
Constants |
Testing
# 1. Check ROM baseline
z3ed rom-doctor --rom vanilla.sfc --format json
# 2. Apply tail expansion patch
z3ed overworld-doctor --rom vanilla.sfc --apply-tail-expansion --output expanded.sfc
# 3. Verify expansion marker
z3ed rom-doctor --rom expanded.sfc --format json | jq '.expanded_pointer_tables'
# 4. Run integration tests
z3ed test-run --label rom_dependent --format json
Known Issues / History
Previous Errors (FIXED)
The original padding attempted to use addresses inside the expanded tile16 region:
0x1E878B, 0x1E95A3, 0x1ED6F3, 0x1EF540
These caused tile corruption. The overworld-doctor CLI tool can detect and repair this corruption.
Remaining Tasks
- Integration testing with fully patched ROM
- GUI indicator in Overworld Editor showing tail map availability
- Unit tests for marker detection and guard logic