Viboy Color - Bitácora de Desarrollo

Step 0482: Desbloquear Ruta FF92 (Mario) + Detectar Wait-Loop Real (Tetris DX) + Eliminar Estado Estático

Resumen

Este step implementa tres objetivos principales:

  1. Fase 0: Higiene - Eliminar estado estático compartido entre tests
  2. Fase A: Mario - Instrumentación para análisis de control flow (Branch Decision Counters, Last Compare/BIT Tracking, LCDC Disable Tracking)
  3. Fase B: Tetris DX - Detector dinámico de wait-loops y histograma de opcodes desconocidos

Implementación

Fase 0: Higiene - Eliminar Estado Estático

Convertidos contadores estáticos a miembros de instancia para aislar 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_

Fase A: Mario - Instrumentación para Análisis

A1) Branch Decision Counters

Gate: VIBOY_DEBUG_BRANCH=1

Implementado en CPU.cpp/CPU.hpp:

  • branch_decisions_: map de PC → BranchDecision (taken_count, not_taken_count, last_target, last_taken, last_flags)
  • last_cond_jump_pc_, last_target_, last_taken_, last_flags_
  • Getters expuestos en Cython
A2) Last Compare/BIT Tracking

Gate: VIBOY_DEBUG_BRANCH=1

Implementado en CPU.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 en instrucciones CP n (0xFE) y BIT n, r (CB opcodes)
  • Getters expuestos en Cython
A3) LCDC Disable Tracking

Implementado en MMU.cpp/MMU.hpp y PPU.cpp/PPU.hpp:

  • lcdc_disable_events_: contador de eventos (1→0)
  • last_lcdc_write_pc_, last_lcdc_write_value_
  • PPU::handle_lcd_disable(): resetea LY a 0, STAT mode a HBlank
  • Getters expuestos en Cython
A4) Test Clean-Room LCDC Disable

Archivo: tests/test_lcdc_disable_resets_ly_0482.py

Resultado:PASA

Verifica que cuando LCDC bit7 pasa de 1→0:

  • LY se resetea a 0 y se estabiliza
  • STAT mode se establece en HBlank (mode 0)
  • lcdc_disable_events == 1

Fase B: Tetris DX - Detectar Wait-Loop Real

B1) Dynamic Wait-Loop Detector

Archivo: tools/rom_smoke_0442.py

Función detect_dynamic_wait_loop():

  • Analiza I/O reads program en hotspot
  • Identifica I/O read dominante
  • Correlaciona con last_cmp/last_bit para determinar condición de espera
  • Retorna: waits_on_addr, mask, cmp, bit, io_reads_distribution
B2) Unknown Opcode Histogram

Archivo: tools/rom_smoke_0442.py

Función get_unknown_opcode_histogram():

  • Cuenta opcodes desconocidos (DB) en disasm window
  • Retorna top 10 opcodes ordenados por frecuencia
  • Permite priorizar implementación de opcodes en hotspot
Integración en Snapshots

Añadidos a snapshots de rom_smoke_0442.py:

  • BranchInfo: información de branch counters (si VIBOY_DEBUG_BRANCH=1)
  • LastCmp: último CP ejecutado (PC, A, Imm)
  • LastBit: último BIT ejecutado (PC, Bit, Val)
  • LCDC_DisableEvents: contador de eventos de disable
  • DynamicWaitLoop: resultado del detector dinámico
  • UnknownOpcodes: top 5 opcodes desconocidos en hotspot

Concepto de Hardware

LCDC (LCD Control Register - 0xFF40)

El registro LCDC controla el estado del LCD. El bit 7 (LCDC.7) activa/desactiva el LCD. Cuando el LCD se apaga (bit 7 pasa de 1→0), según Pan Docs:

  • LY (Line Y) se resetea a 0
  • STAT mode se establece en un estado estable (Mode 0 = HBlank)
  • No debe quedar frame pending infinito

Este comportamiento es crítico para muchos juegos que apagan el LCD durante transiciones de pantalla o inicialización.

Branch Decision Tracking

El tracking de decisiones de branch permite analizar el flujo de control del programa:

  • Taken/Not-Taken Counts: Cuántas veces un salto condicional fue tomado vs no tomado
  • Last Target/Flags: Último target y flags al momento del salto
  • Útil para identificar loops y condiciones que bloquean progreso

Last Compare/BIT Tracking

El tracking de últimas comparaciones y test de bits permite correlacionar:

  • CP (Compare): Último valor comparado (A vs immediate)
  • BIT: Último bit testeado (número de bit y valor)
  • Con I/O reads para identificar condiciones de espera en wait-loops

Tests y Verificación

Test LCDC Disable

Comando:

pytest tests/test_lcdc_disable_resets_ly_0482.py -v

Resultado:

1 passed in 0.25s

Código del Test (fragmento clave):

def test_lcdc_disable_resets_ly(self):
    mmu = PyMMU()
    ppu = PyPPU(mmu)
    mmu.set_ppu(ppu)
    
    # Encender LCD
    mmu.write(IO_LCDC, 0x91)
    ppu.step(4560)  # 10 scanlines
    assert ppu.get_ly() == 10
    
    # Apagar LCD
    mmu.write(IO_LCDC, 0x11)
    ppu.step(1000)
    
    # Verificar LY reseteado
    assert ppu.get_ly() == 0
    assert ppu.get_mode() == 0  # HBlank
    assert mmu.get_lcdc_disable_events() == 1

Validación: Test valida módulo compilado C++ (PPU::handle_lcd_disable)

Ejecución rom_smoke

ROMs ejecutadas:

  • mario.gbc - 240 frames
  • tetris_dx.gbc - 240 frames
  • tetris.gb - 240 frames

Logs:

  • /tmp/mario_smoke_0482.log
  • /tmp/tetris_dx_smoke_0482.log
  • /tmp/tetris_smoke_0482.log

Archivos Afectados

  • src/core/cpp/MMU.hpp / MMU.cpp
  • src/core/cpp/CPU.hpp / CPU.cpp
  • src/core/cpp/PPU.hpp / PPU.cpp
  • src/core/cython/mmu.pxd / mmu.pyx
  • src/core/cython/cpu.pxd / cpu.pyx
  • tests/test_lcdc_disable_resets_ly_0482.py (nuevo)
  • tools/rom_smoke_0442.py

Referencias

  • Pan Docs - LCD Control Register (FF40 - LCDC)
  • Pan Docs - CPU Instruction Set (CP, BIT)
  • Pan Docs - LCD Status Register (STAT)