Step 0479: Close Wait Loop - Identify Exact I/O and Fix Only That

← Return to index

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 byVIBOY_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 instrumentation
  • get_ly_changes_this_frame(): Counter of LY changes per frame
  • get_stat_mode_changes_count(): STAT mode change counter per frame
  • get_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 execution
  • test_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 frames
  • tetris_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

  1. LY progresses correctly:Both ROMs showLY_ReadMax=145, indicating that LY IS changing.
  2. 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.
  3. IE is still at 0x00:Both ROMs haveIE=0x00, confirming the finding of Step 0478.
  4. Different expected I/O:mario.gbc wait0xFF92(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 snapshot
  • src/core/cpp/MMU.hpp- Added private members for instrumentation
  • src/core/cpp/MMU.cpp- Implemented gated and specific instrumentation
  • src/core/cython/mmu.pxd- Added new method declarations
  • src/core/cython/mmu.pyx- Exposure of new methods to Python
  • tests/test_ly_stat_progress_realistic_0479.py- Clean-room test for LY/STAT
  • tests/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 whenVIBOY_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.

Next Steps

  • [ ] Investigate I/O CGB 0xFF92 (mario.gbc) - check CGB documentation or implement according to specification
  • [ ] Check JOYP defaults and read/write semantics (tetris_dx.gbc)
  • [ ] Investigate why STAT_LastRead=0x00 - may be problem with reading STAT from PPU
  • [ ] Apply minimum fixes (Phase C) according to evidence once the exact I/O and cause have been identified