Step 0479: Cerrar Wait Loop - Identificar I/O Exacto y Arreglar Solo Eso

← Volver al índice

Resumen Ejecutivo

Step 0478 identificó que las ROMs CGB están atascadas en wait loops, pero no se identificó el I/O exacto que están esperando. Step 0479 implementa diagnóstico automático para identificar el I/O exacto del loop (LY, STAT, IF, CGB I/O) y aplica fixes mínimos solo si hay evidencia concluyente.

Resultado: ✅ Implementado `parse_loop_io_pattern()` para detectar automáticamente el I/O esperado. ✅ Añadida instrumentación gated por I/O esperado y específica LY/STAT/IF en MMU. ✅ Implementados tests clean-room (2/2 pasando). ✅ Ejecutado rom_smoke con baseline limpio y generado reporte. ✅ I/O identificado: mario.gbc espera `0xFF92` (I/O CGB no estándar), tetris_dx.gbc espera `0xFF00` (JOYP).

Concepto de Hardware

CPU Wait Loops en Game Boy

Los wait loops son patrones comunes en el código de Game Boy donde la CPU espera a que un registro I/O cambie a un valor específico. El patrón típico es:

loop:
    LDH A,(IO_REG)    ; Leer registro I/O
    AND 0xXX          ; Aplicar máscara (opcional)
    CP 0xYY           ; Comparar con valor esperado (opcional)
    JR NZ, loop       ; Saltar si no coincide

Fuente: Pan Docs - I/O Registers

I/O Registers Comunes en Wait Loops

  • LY (0xFF44): LCD Y-Coordinate - Los juegos esperan que LY alcance un valor específico (ej: LY >= 0x90 para VBlank)
  • STAT (0xFF41): LCD Status - Los juegos esperan que el modo STAT cambie (ej: modo VBlank = 0x01)
  • IF (0xFF0F): Interrupt Flag - Los juegos esperan que un bit de IF se active (ej: IF bit0 = VBlank)
  • JOYP (0xFF00): Joypad Input - Los juegos esperan que el joypad cambie (raro en wait loops de inicialización)

I/O CGB No Estándar (0xFF92, etc.)

Algunos juegos CGB usan registros I/O no documentados en Pan Docs estándar. Estos pueden ser registros específicos del hardware CGB o registros custom usados por el juego.

Fuente: Game Boy Color Technical Documentation

Implementación

Fase A: Extraer Condición Exacta del Loop (Sin Tocar Core)

Se implementó la función parse_loop_io_pattern() en tools/rom_smoke_0442.py que analiza el disasm window alrededor del PC hotspot para identificar automáticamente:

  • waits_on: Dirección I/O esperada (0xFF44, 0xFF41, 0xFF0F, etc.)
  • mask: Máscara AND aplicada (0x01, 0x03, etc.)
  • cmp: Valor comparado (si hay CP)
  • pattern: Tipo de espera (LY_GE, STAT_MODE, IF_BIT, CGB_IO, UNKNOWN)

Estos campos se añadieron al snapshot output para cada ROM CGB en frame 180.

Fase B: Instrumentación Gated y Específica

Se añadió instrumentación en MMU.cpp/hpp para rastrear:

  • Instrumentación gated por I/O esperado: Contador de reads desde programa del I/O esperado (controlado por VIBOY_DEBUG_IO)
  • Instrumentación específica LY/STAT/IF: Contadores de cambios por frame para LY, STAT mode, e IF bit0

Los nuevos métodos expuestos en Cython:

  • set_waits_on_addr(uint16_t addr): Configura el I/O esperado para instrumentación gated
  • get_ly_changes_this_frame(): Contador de cambios de LY por frame
  • get_stat_mode_changes_count(): Contador de cambios de modo STAT por frame
  • get_if_bit0_set_count_this_frame(): Contador de veces que IF bit0 se pone a 1 por frame

Fase D: Tests Clean-Room

Se implementaron dos tests clean-room para validar el comportamiento básico antes de aplicar fixes:

  • test_ly_stat_progress_realistic_0479.py: Verifica que LY progresa correctamente durante la ejecución
  • test_if_set_by_ppu_vblank_0479.py: Verifica que IF bit0 se pone a 1 cuando el PPU entra en VBlank

Ambos tests pasan ✅.

Fase Run: Ejecución y Reporte

Se ejecutó rom_smoke con baseline limpio (VIBOY_SIM_BOOT_LOGO=0, VIBOY_DEBUG_IO=1) para las ROMs CGB:

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

Se generó el reporte /tmp/reporte_step0479.md con análisis detallado de cada ROM.

Resultados

mario.gbc (Frame 180)

  • PC_hotspot1: 0x12A0
  • LoopWaitsOn: 0xFF92 (I/O CGB no estándar)
  • Pattern: UNKNOWN
  • LY_ReadMax: 145 ✅ (LY progresa correctamente)
  • STAT_LastRead: 0x00 ⚠️ (STAT no se está leyendo o está en 0)
  • IE: 0x00
  • IF: 0xE3 (bits activos)

Análisis: El loop está esperando 0xFF92 (I/O CGB no estándar), no un I/O estándar. LY progresa correctamente, pero el loop no se desbloquea porque 0xFF92 no está implementado o no cambia.

tetris_dx.gbc (Frame 180)

  • PC_hotspot1: 0x1383
  • LoopWaitsOn: 0xFF00 (JOYP)
  • Pattern: CGB_IO
  • Mask: 0x03
  • Cmp: 0x03
  • LY_ReadMax: 145 ✅ (LY progresa correctamente)
  • STAT_LastRead: 0x00 ⚠️
  • IE: 0x00
  • IF: 0xE1 (bits activos)

Análisis: El loop está esperando JOYP con condición específica (mask=0x03, cmp=0x03). LY progresa correctamente, pero el loop no se desbloquea porque la condición de JOYP no se cumple.

Hallazgos Clave

  1. LY progresa correctamente: Ambas ROMs muestran LY_ReadMax=145, lo que indica que LY SÍ está cambiando.
  2. STAT no se lee correctamente: STAT_LastRead=0x00 en ambas ROMs es sospechoso. Puede indicar que STAT no se está leyendo durante la ejecución o que hay un problema con la lectura de STAT desde la PPU.
  3. IE sigue en 0x00: Ambas ROMs tienen IE=0x00, lo que confirma el hallazgo de Step 0478.
  4. I/O esperado diferente: mario.gbc espera 0xFF92 (I/O CGB no estándar), tetris_dx.gbc espera 0xFF00 (JOYP).

Archivos Afectados

  • tools/rom_smoke_0442.py - Añadida función parse_loop_io_pattern() y campos al snapshot
  • src/core/cpp/MMU.hpp - Añadidos miembros privados para instrumentación
  • src/core/cpp/MMU.cpp - Implementada instrumentación gated y específica
  • src/core/cython/mmu.pxd - Añadidas declaraciones de nuevos métodos
  • src/core/cython/mmu.pyx - Exposición de nuevos métodos a Python
  • tests/test_ly_stat_progress_realistic_0479.py - Test clean-room para LY/STAT
  • tests/test_if_set_by_ppu_vblank_0479.py - Test clean-room para IF bit0

Tests y Verificación

Tests unitarios: pytest con 2 tests nuevos pasando:

  • test_ly_stat_progress_realistic_0479.py::test_ly_stat_progress_realistic - Verifica que LY progresa correctamente
  • test_if_set_by_ppu_vblank_0479.py::test_if_set_by_ppu_vblank - Verifica que IF bit0 se pone a 1 en VBlank

Ejecución rom_smoke: Ejecutado con baseline limpio para mario.gbc y tetris_dx.gbc. Reporte generado en /tmp/reporte_step0479.md.

Fuentes Consultadas

  • Pan Docs: I/O Registers (LY, STAT, IF, JOYP)
  • Game Boy Color Technical Documentation: I/O Registers no estándar

Integridad Educativa

Lo que Entiendo Ahora

  • Wait Loops: Los juegos Game Boy usan wait loops esperando que registros I/O cambien a valores específicos. El patrón típico es LDH A,(IO) → AND mask → CP val → JR NZ, loop.
  • Diagnóstico Automático: Se puede analizar el disasm window alrededor del hotspot para identificar automáticamente el I/O esperado y la condición del loop.
  • Instrumentación Gated: Se puede instrumentar solo el I/O esperado para evitar overhead en el bucle principal, activando la instrumentación solo cuando VIBOY_DEBUG_IO está habilitado.

Lo que Falta Confirmar

  • I/O CGB 0xFF92: Este registro no está documentado en Pan Docs estándar. Requiere investigación adicional para determinar si es un registro CGB específico o un registro custom usado por mario.gbc.
  • STAT LastRead=0x00: Por qué STAT se lee como 0x00 cuando debería tener valores válidos. Puede ser un problema con la lectura de STAT desde la PPU o con la inicialización del LCD.
  • JOYP Condition: Por qué tetris_dx.gbc espera JOYP con mask=0x03 y cmp=0x03. Requiere verificar los defaults de JOYP y la semántica de read/write.

Próximos Pasos

  • [ ] Investigar I/O CGB 0xFF92 (mario.gbc) - verificar documentación CGB o implementar según especificación
  • [ ] Verificar defaults de JOYP y semántica de read/write (tetris_dx.gbc)
  • [ ] Investigar por qué STAT_LastRead=0x00 - puede ser problema con lectura de STAT desde PPU
  • [ ] Aplicar fixes mínimos (Fase C) según evidencia una vez identificado el I/O exacto y la causa