Step 0480: Close JOYP and HRAM FF92 Loops + Fix Parser
Executive Summary
Step 0479 identified that mario.gbc expects 0xFF92 (HRAM, not I/O) and tetris_dx.gbc expects 0xFF00 (JOYP). Step 0480 fixes the parser to avoid false positives (FF92=HRAM), implements HRAM surgical instrumentation[FF92], fixes JOYP semantics (bits 6-7 always 1), and improves disasm_window to mark PC and respect banking.
Result:✅ Parser fixed (distinguishes I/O vs HRAM). ✅ HRAM[FF92] instrumentation working. ✅ Corrected JOYP semantics. ✅ Clean-room tests (3/3 passing). ✅ Improved Disasm. ✅ Report generated: HRAM[FF92] is never written (value 0 is initial), JOYP fix does not unlock tetris_dx yet (requires more investigation).
Hardware Concept
HRAM (High RAM) on Game Boy
HRAM is a 127-byte memory region (0xFF80-0xFFFE) that is quickly accessible by the CPU. Unlike I/O registers (0xFF00-0xFF7F), HRAM is normal memory that can be read and written without side effects.
Fountain:Pan Docs - Memory Map
JOYP Register (0xFF00)
The JOYP register (P1) controls the joypad input. According to Pan Docs, bits 6-7 always read as 1, regardless of the value written. Bits 4-5 select which row of buttons to read (directions or actions), and bits 0-3 reflect the state of the buttons (0 = pressed, 1 = released).
Fountain:Pan Docs - Joypad Input, P1 Register
Implementation
Phase A: Fix Parser
The `parse_loop_io_pattern` parser now correctly distinguishes between:
- I/O registers (0xFF00-0xFF7F):Detected as wait loops if there is LDH A,(addr) with addr< 0xFF80, seguido de AND/BIT/CP y JR/JP de vuelta al hotspot.
- HRAM (0xFF80-0xFFFF):NOT detected as wait loops (avoid false positives like 0xFF92).
Phase B: HRAM Instrumentation[FF92]
Added surgical instrumentation in MMU to track reads/writes to 0xFF92:
- Write and read counters (only from program, not CPU poll)
- Last PC, value and write timestamp
- Last PC and read value
- Gated by
VIBOY_DEBUG_IO=1
Phase C: JOYP Semantics
Fixed JOYP semantics in MMU::read(0xFF00):
- Bits 6-7 always read as 1 (according to Pan Docs)
- Default value: 0xFF (all bits set to 1 = not pressed)
Phase D: Improved Disasm
Improved `disasm_window` in `tools/rom_smoke_0442.py`:
- Mark the current PC with
>>> ... <<< PC ACTUAL - Respects banking when reading bytes from ROM (uses MMU instead of raw file reads)
- Avoid "DB" (data byte) garbage in disassembly
Tests and Verification
Clean-Room Tests
Created 3 new tests to validate JOYP semantics:
test_joyp_default_no_input_returns_1s_0480.py: Verify that JOYP with no input returns bits 0-1 in 1test_joyp_select_buttons_affects_low_nibble_0480.py: Verify that selecting row of buttons affects low nibbletest_joyp_select_dpad_affects_low_nibble_0480.py: Verify that selecting address row affects low nibble
Result:✅ 3/3 tests passed
Running rom_smoke
Executedrom_smoke_0442.pywith clean baseline for 3 ROMs:
mario.gbc: 240 framestetris_dx.gbc: 240 framestetris.gb: 240 frames
Flags: VIBOY_SIM_BOOT_LOGO=0 VIBOY_DEBUG_IO=1 VIBOY_DEBUG_INJECTION=0 VIBOY_AUTOPRESS=0 VIBOY_FORCE_BGP=0 VIBOY_FRAMEBUFFER_TRACE=0
Results
mario.gbc
- ✅ HRAM[FF92] instrumentation working
- ❌ HRAM[FF92] is NEVER written (counter remains at 0)
- ⚠️ HRAM[FF92] is not read from the program (all reads are from CPU poll)
- Conclusion:HRAM[FF92] is read but never written. The value 0 is the initial value (HRAM is initialized to 0x00).
tetris_dx.gbc
- ❌ JOYP loop NOT detected by parser (requires further investigation)
- ✅ JOYP semantics corrected (bits 6-7 always 1)
- Conclusion:The JOYP fix does not unlock the loop yet. Requires further investigation into the exact condition of the loop.
Affected Files
tools/rom_smoke_0442.py: Parser fixed, disasm_window improved, FF92 HRAM metrics addedsrc/core/cpp/MMU.hpp: Private members for HRAM instrumentation[FF92]src/core/cpp/MMU.cpp: HRAM[FF92] instrumentation, corrected JOYP semanticstests/test_joyp_*_0480.py: 3 new tests to validate JOYP semantics
Educational Integrity
What I Understand Now
- HRAM vs I/O:HRAM (0xFF80-0xFFFF) is normal memory, not I/O registers. The parser must distinguish between the two to avoid false positives.
- JOYP Semantics:Bits 6-7 of JOYP always read as 1 according to Pan Docs. This is critical for correct registry semantics.
- Disasm Banking:When disassembling code, ROM banking should be respected using MMU instead of raw file reads to avoid "DB" garbage.
What remains to be confirmed
- HRAM[FF92] in mario.gbc:Why is it read but never written. It may be that the value 0 is sufficient or that it is written at an uncaptured time.
- JOYP Loop in tetris_dx.gbc:Why the parser does not detect the JOYP loop. It may require adjustments to the detection algorithm or the condition is more complex.