Step 0480: Cerrar Loops JOYP y HRAM FF92 + Arreglar Parser
Resumen Ejecutivo
Step 0479 identificó que mario.gbc espera 0xFF92 (HRAM, no I/O) y tetris_dx.gbc espera 0xFF00 (JOYP). Step 0480 corrige el parser para evitar falsos positivos (FF92=HRAM), implementa instrumentación quirúrgica de HRAM[FF92], corrige semántica JOYP (bits 6-7 siempre 1), y mejora disasm_window para marcar PC y respetar banking.
Resultado: ✅ Parser corregido (distingue I/O vs HRAM). ✅ Instrumentación HRAM[FF92] funcionando. ✅ Semántica JOYP corregida. ✅ Tests clean-room (3/3 pasando). ✅ Disasm mejorado. ✅ Reporte generado: HRAM[FF92] nunca se escribe (valor 0 es inicial), JOYP fix no desbloquea tetris_dx aún (requiere más investigación).
Concepto de Hardware
HRAM (High RAM) en Game Boy
HRAM es una región de memoria de 127 bytes (0xFF80-0xFFFE) que es accesible rápidamente por la CPU. A diferencia de los registros I/O (0xFF00-0xFF7F), HRAM es memoria normal que se puede leer y escribir sin efectos secundarios.
Fuente: Pan Docs - Memory Map
JOYP Register (0xFF00)
El registro JOYP (P1) controla el input del joypad. Según Pan Docs, los bits 6-7 siempre leen como 1, independientemente del valor escrito. Los bits 4-5 seleccionan qué fila de botones leer (direcciones o acciones), y los bits 0-3 reflejan el estado de los botones (0 = presionado, 1 = suelto).
Fuente: Pan Docs - Joypad Input, P1 Register
Implementación
Fase A: Corregir Parser
El parser `parse_loop_io_pattern` ahora distingue correctamente entre:
- I/O registers (0xFF00-0xFF7F): Detectados como wait loops si hay LDH A,(addr) con addr < 0xFF80, seguido de AND/BIT/CP y JR/JP de vuelta al hotspot.
- HRAM (0xFF80-0xFFFF): NO detectados como wait loops (evita falsos positivos como 0xFF92).
Fase B: Instrumentación HRAM[FF92]
Añadida instrumentación quirúrgica en MMU para trackear reads/writes a 0xFF92:
- Contadores de writes y reads (solo desde programa, no CPU poll)
- Último PC, valor y timestamp de write
- Último PC y valor de read
- Gated por
VIBOY_DEBUG_IO=1
Fase C: Semántica JOYP
Corregida semántica de JOYP en MMU::read(0xFF00):
- Bits 6-7 siempre leen como 1 (según Pan Docs)
- Valor por defecto: 0xFF (todos los bits en 1 = no presionados)
Fase D: Disasm Mejorado
Mejorado `disasm_window` en `tools/rom_smoke_0442.py`:
- Marca el PC actual con
>>> ... <<< PC ACTUAL - Respeta banking al leer bytes de ROM (usa MMU en lugar de raw file reads)
- Evita "DB" (data byte) garbage en el disassembly
Tests y Verificación
Tests Clean-Room
Creados 3 tests nuevos para validar semántica JOYP:
test_joyp_default_no_input_returns_1s_0480.py: Verifica que JOYP con sin input devuelve bits 0-1 en 1test_joyp_select_buttons_affects_low_nibble_0480.py: Verifica que seleccionar fila de botones afecta el nibble bajotest_joyp_select_dpad_affects_low_nibble_0480.py: Verifica que seleccionar fila de direcciones afecta el nibble bajo
Resultado: ✅ 3/3 tests pasando
Ejecución rom_smoke
Ejecutado rom_smoke_0442.py con baseline limpio para 3 ROMs:
mario.gbc: 240 framestetris_dx.gbc: 240 framestetris.gb: 240 frames
Flags: VIBOY_SIM_BOOT_LOGO=0 VIBOY_DEBUG_IO=1 VIBOY_DEBUG_INJECTION=0 VIBOY_AUTOPRESS=0 VIBOY_FORCE_BGP=0 VIBOY_FRAMEBUFFER_TRACE=0
Resultados
mario.gbc
- ✅ HRAM[FF92] instrumentación funcionando
- ❌ HRAM[FF92] NUNCA se escribe (contador permanece en 0)
- ⚠️ HRAM[FF92] no se lee desde programa (todas las lecturas son desde CPU poll)
- Conclusión: HRAM[FF92] se lee pero nunca se escribe. El valor 0 es el valor inicial (HRAM se inicializa a 0x00).
tetris_dx.gbc
- ❌ Loop JOYP NO detectado por el parser (requiere más investigación)
- ✅ JOYP semántica corregida (bits 6-7 siempre 1)
- Conclusión: El fix de JOYP no desbloquea el loop aún. Requiere más investigación sobre la condición exacta del loop.
Archivos Afectados
tools/rom_smoke_0442.py: Parser corregido, disasm_window mejorado, métricas HRAM FF92 añadidassrc/core/cpp/MMU.hpp: Miembros privados para instrumentación HRAM[FF92]src/core/cpp/MMU.cpp: Instrumentación HRAM[FF92], semántica JOYP corregidatests/test_joyp_*_0480.py: 3 tests nuevos para validar semántica JOYP
Integridad Educativa
Lo que Entiendo Ahora
- HRAM vs I/O: HRAM (0xFF80-0xFFFF) es memoria normal, no registros I/O. El parser debe distinguir entre ambos para evitar falsos positivos.
- JOYP Semantics: Los bits 6-7 de JOYP siempre leen como 1 según Pan Docs. Esto es crítico para la semántica correcta del registro.
- Disasm Banking: Al desensamblar código, se debe respetar el banking de ROM usando MMU en lugar de raw file reads para evitar "DB" garbage.
Lo que Falta Confirmar
- HRAM[FF92] en mario.gbc: Por qué se lee pero nunca se escribe. Puede ser que el valor 0 sea suficiente o que se escriba en un momento no capturado.
- JOYP Loop en tetris_dx.gbc: Por qué el parser no detecta el loop JOYP. Puede requerir ajustes en el algoritmo de detección o la condición es más compleja.