Step 0479: Close Wait Loop - Identify Exact I/O and Fix Only That
Executive Summary
Step 0478 identified that the CGB ROMs are stuck in wait loops, but the exact I/O they are waiting for was not identified. Step 0479 implements automatic diagnostics to identify the exact I/O of the loop (LY, STAT, IF, CGB I/O) and applies minimum fixes only if there is conclusive evidence.
Result:✅ Implemented `parse_loop_io_pattern()` to automatically detect expected I/O. ✅ Added LY/STAT/IF-specific and expected I/O gated instrumentation in MMU. ✅ Clean-room tests implemented (2/2 passing). ✅ Executed rom_smoke with clean baseline and generated report. ✅ I/O identified: mario.gbc waits `0xFF92` (non-standard CGB I/O), tetris_dx.gbc waits `0xFF00` (JOYP).
Hardware Concept
CPU Wait Loops on Game Boy
Wait loops are common patterns in Game Boy code where the CPU waits for an I/O register to change to a specific value. The typical pattern is:
loop:
LDH A,(IO_REG) ; Read I/O register
AND 0xXX ; Apply mask (optional)
CP 0xYY ; Compare with expected value (optional)
JR NZ, loop ; Skip if no match
Fountain:Pan Docs - I/O Registers
Common I/O Registers in Wait Loops
- LY (0xFF44):LCD Y-Coordinate - Games expect LY to reach a specific value (ex: LY >= 0x90 for VBlank)
- STAT (0xFF41):LCD Status - Games wait for the STAT mode to change (ex: VBlank mode = 0x01)
- IF (0xFF0F):Interrupt Flag - Games wait for an IF bit to be set (ex: IF bit0 = VBlank)
- JOYP (0xFF00):Joypad Input - Games wait for the joypad to change (rare for initialization wait loops)
Non-Standard CGB I/O (0xFF92, etc.)
Some CGB games use I/O registers not documented in standard Pan Docs. These can be CGB hardware-specific registers or custom registers used by the game.
Fountain:Game Boy Color Technical Documentation
Implementation
Phase A: Extract Exact Condition of the Loop (Without Touching Core)
Function implementedparse_loop_io_pattern()intools/rom_smoke_0442.pywhich scans the disasm window around the PC hotspot to automatically identify:
waits_on: Expected I/O address (0xFF44, 0xFF41, 0xFF0F, etc.)mask: AND mask applied (0x01, 0x03, etc.)cmp: Compared value (if there is CP)patterns: Wait type (LY_GE, STAT_MODE, IF_BIT, CGB_IO, UNKNOWN)
These fields were added to the snapshot output for each CGB ROM at frame 180.
Phase B: Gated and Specific Instrumentation
Instrumentation was added inMMU.cpp/hppto track:
- Instrumentation gated by expected I/O:Counter of reads from the expected I/O program (controlled by
VIBOY_DEBUG_IO) - Specific LY/STAT/IF instrumentation:Frame change counters for LY, STAT mode, and IF bit0
The new methods exposed in Cython:
set_waits_on_addr(uint16_t addr): Configures the expected I/O for gated instrumentationget_ly_changes_this_frame(): Counter of LY changes per frameget_stat_mode_changes_count(): STAT mode change counter per frameget_if_bit0_set_count_this_frame(): Counter of times that IF bit0 is set to 1 per frame
Phase D: Clean-Room Tests
Two clean-room tests were implemented to validate basic behavior before applying fixes:
test_ly_stat_progress_realistic_0479.py: Verifies that LY progresses correctly during executiontest_if_set_by_ppu_vblank_0479.py: Verifies that IF bit0 is set to 1 when the PPU enters VBlank
Both tests pass ✅.
Run Phase: Execution and Report
rom_smoke was executed with a clean baseline (VIBOY_SIM_BOOT_LOGO=0, VIBOY_DEBUG_IO=1) for the CGB ROMs:
mario.gbc- 240 framestetris_dx.gbc- 240 frames
The report was generated/tmp/report_step0479.mdwith detailed analysis of each ROM.
Results
mario.gbc (Frame 180)
- PC_hotspot1:
0x12A0 - LoopWaitsOn:
0xFF92(Non-standard CGB I/O) - Pattern:
UNKNOWN - LY_ReadMax:
145✅ (LY progresses correctly) - STAT_LastRead:
0x00⚠️ (STAT is not being read or is at 0) - IE:
0x00❌ - IF:
0xE3(active bits)
Analysis:The loop is waiting0xFF92(non-standard CGB I/O), not a standard I/O. LY progresses correctly, but the loop does not unlock because0xFF92is not implemented or does not change.
tetris_dx.gbc (Frame 180)
- PC_hotspot1:
0x1383 - LoopWaitsOn:
0xFF00(JOYP) - Pattern:
CGB_IO - Mask:
0x03 - Cmp:
0x03 - LY_ReadMax:
145✅ (LY progresses correctly) - STAT_LastRead:
0x00⚠️ - IE:
0x00❌ - IF:
0xE1(active bits)
Analysis:The loop is waiting for JOYP with specific condition (mask=0x03, cmp=0x03). LY progresses correctly, but the loop does not unlock because the JOYP condition is not met.
Key Findings
- LY progresses correctly:Both ROMs show
LY_ReadMax=145, indicating that LY IS changing. - STAT is not read correctly:
STAT_LastRead=0x00in both ROMs it is suspicious. It may indicate that STAT is not being read during execution or that there is a problem reading STAT from the PPU. - IE is still at 0x00:Both ROMs have
IE=0x00, confirming the finding of Step 0478. - Different expected I/O:mario.gbc wait
0xFF92(non-standard CGB I/O), tetris_dx.gbc wait0xFF00(JOYP).
Affected Files
tools/rom_smoke_0442.py- Added functionparse_loop_io_pattern()and fields to the snapshotsrc/core/cpp/MMU.hpp- Added private members for instrumentationsrc/core/cpp/MMU.cpp- Implemented gated and specific instrumentationsrc/core/cython/mmu.pxd- Added new method declarationssrc/core/cython/mmu.pyx- Exposure of new methods to Pythontests/test_ly_stat_progress_realistic_0479.py- Clean-room test for LY/STATtests/test_if_set_by_ppu_vblank_0479.py- Clean-room test for IF bit0
Tests and Verification
Unit tests:pytest with 2 new tests passing:
- ✅
test_ly_stat_progress_realistic_0479.py::test_ly_stat_progress_realistic- Verify that LY is progressing correctly - ✅
test_if_set_by_ppu_vblank_0479.py::test_if_set_by_ppu_vblank- Verify that IF bit0 is set to 1 in VBlank
rom_smoke execution:Executed with clean baseline for mario.gbc and tetris_dx.gbc. Report generated in/tmp/report_step0479.md.
Sources consulted
- Pan Docs: I/O Registers (LY, STAT, IF, JOYP)
- Game Boy Color Technical Documentation: Non-standard I/O Registers
Educational Integrity
What I Understand Now
- Wait Loops:Game Boy games use wait loops waiting for I/O registers to change to specific values. The typical pattern is LDH A,(IO) → AND mask → CP val → JR NZ, loop.
- Automatic Diagnosis:You can analyze the disasm window around the hotspot to automatically identify the expected I/O and loop condition.
- Gated Instrumentation:You can instrument only the expected I/O to avoid overhead in the main loop, activating instrumentation only when
VIBOY_DEBUG_IOis enabled.
What remains to be confirmed
- I/O CGB 0xFF92:This record is not documented in standard Pan Docs. Requires additional investigation to determine if it is a specific CGB record or a custom record used by mario.gbc.
- STAT LastRead=0x00:Why STAT is read as 0x00 when it should have valid values. It may be a problem with reading STAT from the PPU or with LCD initialization.
- JOYP Condition:Why tetris_dx.gbc expects JOYP with mask=0x03 and cmp=0x03. Requires checking JOYP defaults and read/write semantics.