backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)
This commit is contained in:
265
docs/internal/archive/handoffs/handoff-dungeon-object-preview.md
Normal file
265
docs/internal/archive/handoffs/handoff-dungeon-object-preview.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# Handoff: Dungeon Object Emulator Preview
|
||||
|
||||
**Date:** 2025-11-26
|
||||
**Status:** Root Cause Identified - Emulator Mode Requires Redesign
|
||||
**Priority:** Medium
|
||||
|
||||
## Summary
|
||||
|
||||
Implemented a dual-mode object preview system for the dungeon editor. The **Static mode** (ObjectDrawer-based) works and renders objects. The **Emulator mode** has been significantly improved with proper game state initialization based on expert analysis of ALTTP's drawing handlers.
|
||||
|
||||
## CRITICAL DISCOVERY: Handler Execution Root Cause (Session 2, Final)
|
||||
|
||||
**The emulator mode cannot work with cold-start execution.**
|
||||
|
||||
### Test Suite Investigation
|
||||
|
||||
A comprehensive test suite was created at `test/integration/emulator_object_preview_test.cc` to trace handler execution. The `TraceObject00Handler` test revealed the root cause:
|
||||
|
||||
```
|
||||
[TEST] Object 0x00 handler: $8B89
|
||||
[TRACE] Starting execution trace from $01:8B89
|
||||
[ 0] $01:8B89: 20 -> $00:8000 (A=$0000 X=$00D8 Y=$0020)
|
||||
[ 1] $00:8000: 78 [CPU_AUDIO] === ENTERED BANK $00 at PC=$8000 ===
|
||||
```
|
||||
|
||||
**Finding:** Object 0x00's handler at `$8B89` immediately executes `JSR $8000`, which is the **game's RESET vector**. This runs full game initialization including:
|
||||
- Hardware register setup
|
||||
- APU initialization (the $00:8891 handshake loop)
|
||||
- WRAM clearing
|
||||
- NMI/interrupt setup
|
||||
|
||||
### Why Handlers Cannot Run in Isolation
|
||||
|
||||
ALTTP's object handlers are designed to run **within an already-running game context**:
|
||||
|
||||
1. **Shared Subroutines**: Handlers call common routines that assume game state is initialized
|
||||
2. **Bank Switching**: Code frequently jumps between banks, requiring proper stack/return state
|
||||
3. **Zero-Page Dependencies**: Dozens of zero-page variables must be pre-set by the game
|
||||
4. **Interrupt Context**: Some operations depend on NMI/HDMA being active
|
||||
|
||||
### Implications
|
||||
|
||||
The current approach of "cold start emulation" (reset SNES → jump to handler) is fundamentally flawed for ALTTP. Object handlers are **not self-contained functions** - they're subroutines within a complex runtime environment.
|
||||
|
||||
### Recommended Future Approaches
|
||||
|
||||
1. **Save State Injection**: Load a save state from a running game, modify WRAM to set up object parameters, then execute handler
|
||||
2. **Full Game Boot**: Run the game to a known "drawing ready" state (room loaded), then call handlers
|
||||
3. **Static Mode**: Continue using ObjectDrawer for reliable rendering (current default)
|
||||
4. **Hybrid Tracing**: Use emulator for debugging/analysis only, not rendering
|
||||
|
||||
---
|
||||
|
||||
## Recent Improvements (2025-11-26)
|
||||
|
||||
### CRITICAL FIX: SNES-to-PC Address Conversion (Session 2)
|
||||
- **Issue:** All ROM addresses were SNES addresses (e.g., `$01:8000`) but code used them as PC file offsets
|
||||
- **Root Cause:** ALTTP uses LoROM mapping where SNES addresses must be converted to PC offsets
|
||||
- **Fix:** Added `SnesToPc()` helper function and converted all ROM address accesses
|
||||
- **Conversion Formula:** `PC = (bank & 0x7F) * 0x8000 + (addr - 0x8000)`
|
||||
- **Examples:**
|
||||
- `$01:8000` → PC `$8000` (handler table)
|
||||
- `$01:8200` → PC `$8200` (handler routine table)
|
||||
- `$0D:D308` → PC `$6D308` (sprite aux palette)
|
||||
- **Result:** Correct handler addresses from ROM
|
||||
|
||||
### Tilemap Pointer Fix (Session 2, Update 2)
|
||||
- **Issue:** Tilemap pointers read from ROM were garbage - they're NOT stored in ROM
|
||||
- **Root Cause:** Game initializes these pointers dynamically at runtime, not from ROM data
|
||||
- **Fix:** Manually initialize tilemap pointers to point to WRAM buffer rows
|
||||
- **Pointers:** `$BF`, `$C2`, `$C5`, ... → `$7E2000`, `$7E2080`, `$7E2100`, ... (each row +$80)
|
||||
- **Result:** Valid WRAM pointers for indirect long addressing (`STA [$BF],Y`)
|
||||
|
||||
### APU Mock Fix (Session 2, Update 3)
|
||||
- **Issue:** APU handshake at `$00:8891` still hanging despite writing mock values
|
||||
- **Root Cause:** APU I/O ports have **separate read/write latches**:
|
||||
- `Write($2140)` goes to `in_ports_[]` (CPU→SPC direction)
|
||||
- `Read($2140)` returns from `out_ports_[]` (SPC→CPU direction)
|
||||
- **Fix:** Set `out_ports_[]` directly instead of using Write():
|
||||
```cpp
|
||||
apu.out_ports_[0] = 0xAA; // CPU reads $AA from $2140
|
||||
apu.out_ports_[1] = 0xBB; // CPU reads $BB from $2141
|
||||
```
|
||||
- **Result:** APU handshake check passes, handler execution continues
|
||||
|
||||
### Palette Fix (Both Modes)
|
||||
- **Issue:** Tiles specifying palette indices 6-7 showed magenta (out-of-bounds)
|
||||
- **Fix:** Now loads sprite auxiliary palettes from ROM `$0D:D308` (PC: `$6D308`) into indices 90-119
|
||||
- **Result:** Full 120-color palette support (palettes 0-7)
|
||||
|
||||
### Emulator Mode Fixes (Session 1)
|
||||
Based on analysis from zelda3-hacking-expert and snes-emulator-expert agents:
|
||||
|
||||
1. **Zero-Page Tilemap Pointers** - Initialized $BF-$DD from `RoomData_TilemapPointers` at `$01:86F8`
|
||||
2. **APU Mock** - Set `$2140-$2143` to "ready" values (`$AA`, `$BB`) to prevent infinite APU handshake loop at `$00:8891`
|
||||
3. **Two-Table Handler Lookup** - Now uses both data offset table and handler address table
|
||||
4. **Object Parameters** - Properly initializes zero-page variables ($04, $08, $B2, $B4, etc.)
|
||||
5. **CPU State** - Correct register setup (X=data_offset, Y=tilemap_pos, PB=$01, DB=$7E)
|
||||
6. **STP Trap** - Uses STP opcode at `$01:FF00` for reliable return detection
|
||||
|
||||
## What Was Built
|
||||
|
||||
### DungeonObjectEmulatorPreview Widget
|
||||
Location: `src/app/gui/widgets/dungeon_object_emulator_preview.cc`
|
||||
|
||||
A preview tool that renders individual dungeon objects using two methods:
|
||||
|
||||
1. **Static Mode (Default, Working)**
|
||||
- Uses `zelda3::ObjectDrawer` to render objects
|
||||
- Same rendering path as the main dungeon canvas
|
||||
- Reliable and fast
|
||||
- Now supports full 120-color palette (palettes 0-7)
|
||||
|
||||
2. **Emulator Mode (Enhanced)**
|
||||
- Runs game's native drawing handlers via CPU emulation
|
||||
- Full room context initialization
|
||||
- Proper WRAM state setup
|
||||
- APU mock to prevent infinite loops
|
||||
|
||||
### Key Features
|
||||
- Object ID input with hex display and name lookup
|
||||
- Quick-select presets for common objects
|
||||
- Object browser with all Type 1/2/3 objects
|
||||
- Position (X/Y) and size controls
|
||||
- Room ID for graphics/palette context
|
||||
- Render mode toggle (Static vs Emulator)
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Palette Handling (Updated)
|
||||
- Dungeon main palette: 6 sub-palettes × 15 colors = 90 colors (indices 0-89)
|
||||
- Sprite auxiliary palette: 2 sub-palettes × 15 colors = 30 colors (indices 90-119)
|
||||
- Total: 120 colors (palettes 0-7)
|
||||
- Source: Main from palette group, Aux from ROM `$0D:D308`
|
||||
|
||||
### Emulator State Initialization
|
||||
```
|
||||
1. Reset SNES, load room context
|
||||
2. Load full 120-color palette into CGRAM
|
||||
3. Convert 8BPP graphics to 4BPP planar, load to VRAM
|
||||
4. Clear tilemap buffers ($7E:2000, $7E:4000)
|
||||
5. Initialize zero-page tilemap pointers from $01:86F8
|
||||
6. Mock APU I/O ($2140-$2143 = $AA/$BB)
|
||||
7. Set object parameters in zero-page
|
||||
8. Two-table handler lookup (data offset + handler address)
|
||||
9. Setup CPU: X=data_offset, Y=tilemap_pos, PB=$01, DB=$7E
|
||||
10. Push STP trap address, jump to handler
|
||||
11. Execute until STP or timeout
|
||||
12. Copy WRAM buffers to VRAM, render PPU
|
||||
```
|
||||
|
||||
### Files Modified
|
||||
- `src/app/gui/widgets/dungeon_object_emulator_preview.h` - Static rendering members
|
||||
- `src/app/gui/widgets/dungeon_object_emulator_preview.cc` - All emulator fixes
|
||||
- `src/app/editor/ui/right_panel_manager.cc` - Fixed deprecated ImGui flags
|
||||
|
||||
### Tests
|
||||
- BPP Conversion Tests: 12/12 PASS
|
||||
- Dungeon Object Rendering Tests: 8/8 PASS
|
||||
- **Emulator State Injection Tests**: `test/integration/emulator_object_preview_test.cc`
|
||||
- LoROM Conversion Tests: Validates `SnesToPc()` formula
|
||||
- APU Mock Tests: Verifies `out_ports_[]` read behavior
|
||||
- Tilemap Pointer Setup Tests: Confirms WRAM pointer initialization
|
||||
- Handler Table Reading Tests: Validates two-table lookup
|
||||
- Handler Execution Trace Tests: Traces handler execution flow
|
||||
|
||||
## ROM Addresses Reference
|
||||
|
||||
**IMPORTANT:** ALTTP uses LoROM mapping. Always use `SnesToPc()` to convert SNES addresses to PC file offsets!
|
||||
|
||||
| SNES Address | PC Offset | Purpose |
|
||||
|--------------|-----------|---------|
|
||||
| `$01:8000` | `$8000` | Type 1 data offset table |
|
||||
| `$01:8200` | `$8200` | Type 1 handler routine table |
|
||||
| `$01:8370` | `$8370` | Type 2 data offset table |
|
||||
| `$01:8470` | `$8470` | Type 2 handler routine table |
|
||||
| `$01:84F0` | `$84F0` | Type 3 data offset table |
|
||||
| `$01:85F0` | `$85F0` | Type 3 handler routine table |
|
||||
| `$00:9B52` | `$1B52` | RoomDrawObjectData (tile definitions) |
|
||||
| `$0D:D734` | `$6D734` | Dungeon main palettes (0-5) |
|
||||
| `$0D:D308` | `$6D308` | Sprite auxiliary palettes (6-7) |
|
||||
| `$7E:2000` | (WRAM) | BG1 tilemap buffer (8KB) |
|
||||
| `$7E:4000` | (WRAM) | BG2 tilemap buffer (8KB) |
|
||||
|
||||
**Note:** Tilemap pointers at `$BF-$DD` are NOT in ROM - they're initialized dynamically to `$7E2000+` at runtime.
|
||||
|
||||
## Known Issues
|
||||
|
||||
### 1. ~~SNES-to-PC Address Conversion~~ (FIXED - Session 2)
|
||||
~~ROM addresses were SNES addresses but used as PC offsets~~ - Fixed with corrected `SnesToPc()` helper.
|
||||
- Formula: `PC = (bank & 0x7F) * 0x8000 + (addr - 0x8000)`
|
||||
|
||||
### 2. ~~Tilemap Pointers from ROM~~ (FIXED - Session 2)
|
||||
~~Tried to read tilemap pointers from ROM at $01:86F8~~ - Pointers are NOT stored in ROM.
|
||||
- Fixed by manually initializing pointers to WRAM buffer rows ($7E2000, $7E2080, etc.)
|
||||
|
||||
### 3. Emulator Mode Fundamentally Broken (ROOT CAUSE IDENTIFIED)
|
||||
|
||||
**Root Cause:** Object handlers are NOT self-contained. Test tracing revealed that handler `$8B89` (object 0x00) immediately calls `JSR $8000` - the game's RESET vector. This means:
|
||||
|
||||
- Handlers expect to run **within a fully initialized game**
|
||||
- Cold-start emulation will **always** hit APU initialization at `$00:8891`
|
||||
- The handler code shares subroutines with game initialization
|
||||
|
||||
**Test Evidence:**
|
||||
```
|
||||
[ 0] $01:8B89: 20 -> $00:8000 (JSR to RESET vector)
|
||||
[ 1] $00:8000: 78 (SEI - start of game init)
|
||||
```
|
||||
|
||||
**Current Status:** Emulator mode requires architectural redesign. See "Recommended Future Approaches" at top of document.
|
||||
|
||||
**Workaround:** Use static mode for reliable rendering (default).
|
||||
|
||||
### 4. BG Layer Transparency
|
||||
The compositing uses `0xFF` as transparent marker, but edge cases with palette index 0 may exist.
|
||||
|
||||
## How to Test
|
||||
|
||||
### GUI Testing
|
||||
```bash
|
||||
# Build
|
||||
cmake --build build --target yaze -j8
|
||||
|
||||
# Run with dungeon editor
|
||||
./build/bin/Debug/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon
|
||||
|
||||
# Open Emulator Preview from View menu or right panel
|
||||
# Test both Static and Emulator modes
|
||||
# Try objects: Wall (0x01), Floor (0x80), Chest (0xF8)
|
||||
```
|
||||
|
||||
### Emulator State Injection Tests
|
||||
```bash
|
||||
# Build tests
|
||||
cmake --build build --target yaze_test_rom_dependent -j8
|
||||
|
||||
# Run with ROM path
|
||||
YAZE_TEST_ROM_PATH=/path/to/zelda3.sfc ./build/bin/Debug/yaze_test_rom_dependent \
|
||||
--gtest_filter="*EmulatorObjectPreviewTest*"
|
||||
|
||||
# Run specific test suites
|
||||
YAZE_TEST_ROM_PATH=/path/to/zelda3.sfc ./build/bin/Debug/yaze_test_rom_dependent \
|
||||
--gtest_filter="*EmulatorStateInjectionTest*"
|
||||
|
||||
YAZE_TEST_ROM_PATH=/path/to/zelda3.sfc ./build/bin/Debug/yaze_test_rom_dependent \
|
||||
--gtest_filter="*HandlerExecutionTraceTest*"
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
The test suite validates:
|
||||
1. **LoROM Conversion** - `SnesToPc()` formula correctness
|
||||
2. **APU Mock** - Proper `out_ports_[]` vs `in_ports_[]` behavior
|
||||
3. **Handler Tables** - Two-table lookup for all object types
|
||||
4. **Tilemap Pointers** - WRAM pointer initialization
|
||||
5. **Execution Tracing** - Handler flow analysis (reveals root cause)
|
||||
|
||||
## Related Files
|
||||
|
||||
- `src/zelda3/dungeon/object_drawer.h` - ObjectDrawer class
|
||||
- `src/app/gfx/render/background_buffer.h` - BackgroundBuffer for tile storage
|
||||
- `src/zelda3/dungeon/room_object.h` - RoomObject data structure
|
||||
- `docs/internal/architecture/dungeon-object-rendering-plan.md` - Overall rendering architecture
|
||||
- `assets/asm/usdasm/bank_01.asm` - Handler disassembly reference
|
||||
- `test/integration/emulator_object_preview_test.cc` - Emulator state injection test suite
|
||||
Reference in New Issue
Block a user