Step 0482: Unlock FF92 Route (Mario) + Detect Real Wait-Loop (Tetris DX) + Delete Static State
Summary
This step implements three main objectives:
- Phase 0: Hygiene- Delete static state shared between tests
- Phase A: Mario- Instrumentation for control flow analysis (Branch Decision Counters, Last Compare/BIT Tracking, LCDC Disable Tracking)
- Phase B: Tetris DX- Dynamic wait-loops detector and histogram of unknown opcodes
Implementation
Phase 0: Hygiene - Eliminate Static State
Converted static counters to instance members to isolate tests:
- MMU:
ie_write_count_,last_ie_written_,last_ie_write_pc_,last_ie_write_timestamp_,last_ie_read_value_,ie_read_count_,key1_write_count_,joyp_write_count_,io_read_counts_,lcdc_disable_events_ - CPU:
ei_count_,di_count_
Phase A: Mario - Instrumentation for Analysis
A1) Branch Decision Counters
Gate: VIBOY_DEBUG_BRANCH=1
Implemented inCPU.cpp/CPU.hpp:
branch_decisions_: pc map → BranchDecision (taken_count, not_taken_count, last_target, last_taken, last_flags)last_cond_jump_pc_,last_target_,last_taken_,last_flags_- Getters exposed in Cython
A2) Last Compare/BIT Tracking
Gate: VIBOY_DEBUG_BRANCH=1
Implemented inCPU.cpp/CPU.hpp:
- Last Compare:
last_cmp_pc_,last_cmp_a_,last_cmp_imm_,last_cmp_result_flags_ - Last BIT:
last_bit_pc_,last_bit_n_,last_bit_value_ - Tracking in instructions
CP n(0xFE) andBIT n, r(CB opcodes) - Getters exposed in Cython
A3) LCDC Disable Tracking
Implemented inMMU.cpp/MMU.hppandPPU.cpp/PPU.hpp:
lcdc_disable_events_: event counter (1→0)last_lcdc_write_pc_,last_lcdc_write_value_PPU::handle_lcd_disable(): reset LY to 0, STAT mode to HBlank- Getters exposed in Cython
A4) Test Clean-Room LCDC Disable
Archive: tests/test_lcdc_disable_resets_ly_0482.py
Result: ✅ HAPPENS
Verify that when LCDC bit7 goes from 1→0:
- LY resets to 0 and stabilizes
- STAT mode is set to HBlank (mode 0)
lcdc_disable_events == 1
Phase B: Tetris DX - Detect Real Wait-Loop
B1) Dynamic Wait-Loop Detector
Archive: tools/rom_smoke_0442.py
Functiondetect_dynamic_wait_loop():
- Analyze I/O reads program in hotspot
- Identifies I/O read dominant
- Correlates with
last_cmp/last_bitto determine waiting condition - Returns:
waits_on_addr,mask,cmp,bit,io_reads_distribution
B2) Unknown Opcode Histogram
Archive: tools/rom_smoke_0442.py
Functionget_unknown_opcode_histogram():
- Count unknown opcodes (DB) in disasm window
- Returns top 10 opcodes sorted by frequency
- Allows you to prioritize implementation of opcodes in hotspots
Integration in Snapshots
Added to snapshotsrom_smoke_0442.py:
BranchInfo: branch counters information (if VIBOY_DEBUG_BRANCH=1)LastCmp: last CP executed (PC, A, Imm)LastBit: last BIT executed (PC, Bit, Val)LCDC_DisableEvents: disable event counterDynamicWaitLoop: dynamic detector resultUnknownOpcodes: top 5 unknown opcodes in hotspot
Hardware Concept
LCDC (LCD Control Register - 0xFF40)
The LCDC register controls the state of the LCD. Bit 7 (LCDC.7) turns the LCD on/off. When the LCD turns off (bit 7 goes from 1→0), according to Pan Docs:
- LY (Line Y) is reset to 0
- STAT mode is set to a stable state (Mode 0 = HBlank)
- There should not be infinite frame pending
This behavior is critical for many games that turn off the LCD during transitions screen or initialization.
Branch Decision Tracking
Branch decision tracking allows you to analyze the control flow of the program:
- Taken/Not-Taken Counts: How many times a conditional jump was taken vs not taken
- Last Target/Flags: Last target and flags at the time of the jump
- Useful for identifying loops and conditions that block progress
Last Compare/BIT Tracking
The tracking of last comparisons and bit tests allows correlating:
- CP (Compare): Last value compared (A vs immediate)
- BIT: Last bit tested (bit number and value)
- With I/O reads to identify wait conditions in wait-loops
Tests and Verification
LCDC Test Disable
Command:
pytest tests/test_lcdc_disable_resets_ly_0482.py -v
Result:
1 passed in 0.25s
Test code (key fragment):
def test_lcdc_disable_resets_ly(self):
mmu = PyMMU()
ppu = PyPPU(mmu)
mmu.set_ppu(ppu)
# Turn on LCD
mmu.write(IO_LCDC, 0x91)
ppu.step(4560) # 10 scanlines
assert ppu.get_ly() == 10
# Turn off LCD
mmu.write(IO_LCDC, 0x11)
ppu.step(1000)
# Verify LY reset
assert ppu.get_ly() == 0
assert ppu.get_mode() == 0 # HBlank
assert mmu.get_lcdc_disable_events() == 1
Validation:Test validates compiled C++ module (PPU::handle_lcd_disable)
Running rom_smoke
Run ROMs:
mario.gbc- 240 framestetris_dx.gbc- 240 framestetris.gb- 240 frames
Logs:
/tmp/mario_smoke_0482.log/tmp/tetris_dx_smoke_0482.log/tmp/tetris_smoke_0482.log
Affected Files
src/core/cpp/MMU.hpp/MMU.cppsrc/core/cpp/CPU.hpp/CPU.cppsrc/core/cpp/PPU.hpp/PPU.cppsrc/core/cython/mmu.pxd/mmu.pyxsrc/core/cython/cpu.pxd/cpu.pyxtests/test_lcdc_disable_resets_ly_0482.py(new)tools/rom_smoke_0442.py
References
- Pan Docs - LCD Control Register (FF40 - LCDC)
- Pan Docs - CPU Instruction Set (CP, BIT)
- Pan Docs - LCD Status Register (STAT)