Step 0480: Close JOYP and HRAM FF92 Loops + Fix Parser

← Return to index

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 byVIBOY_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 1
  • test_joyp_select_buttons_affects_low_nibble_0480.py: Verify that selecting row of buttons affects low nibble
  • test_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 frames
  • tetris_dx.gbc: 240 frames
  • tetris.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 added
  • src/core/cpp/MMU.hpp: Private members for HRAM instrumentation[FF92]
  • src/core/cpp/MMU.cpp: HRAM[FF92] instrumentation, corrected JOYP semantics
  • tests/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.

Next Steps

  • [ ] Investigate why HRAM[FF92] is never written to mario.gbc - it may be that the value 0 is sufficient
  • [ ] Investigate why the parser does not detect the JOYP loop in tetris_dx.gbc - may require adjustments to the algorithm
  • [ ] Apply additional fixes based on evidence once the root cause is identified