Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Evidencia Dura Mario LDH a8≥0x80 + Tetris DX JOYP Estado Interno
Resumen
Step 0486 implementa instrumentación quirúrgica para recopilar evidencia dura sobre dos issues bloqueantes:
- Mario (mario.gbc): Investigar potencial bug de direccionamiento
LDH a8cuandoa8 >= 0x80que podría impedir queHRAM[FF92]se escriba y lea correctamente enIE. - Tetris DX (tetris_dx.gbc): Investigar comportamiento de
JOYP, específicamente si la ROM lee con selección activa o si el emulador está manejando incorrectamente writes/reads deJOYP.
Se implementa tracking detallado gated por variables de entorno, tests clean-room para validar LDH,
y intelligent autopress basado en eventos para Tetris DX.
Concepto de Hardware
LDH (Load High) Instruction
Las instrucciones LDH A,(a8) (opcode 0xF0) y LDH (a8),A (opcode 0xE0) acceden
a la región de I/O de alta memoria (0xFF00-0xFF7F) usando un operando de 8 bits.
La dirección efectiva se calcula como 0xFF00 | a8 (OR bitwise), no como suma con sign-extension.
Esto significa que cuando a8 >= 0x80, la dirección efectiva es 0xFF80 o superior,
accediendo a HRAM (High RAM) en lugar de I/O registers.
Ejemplo: LDH (0x92),A escribe en 0xFF92 (HRAM), no en 0x0092 ni 0xFE92.
Fuente: Pan Docs - CPU Instruction Set
HRAM (High RAM) - 0xFF80-0xFFFE
HRAM es una región de 127 bytes de RAM de alta velocidad accesible solo por la CPU. Se usa frecuentemente para variables temporales y flags que requieren acceso rápido.
0xFF92 es una dirección específica en HRAM que Mario usa para almacenar temporalmente
el valor de IE antes de escribirlo de vuelta.
JOYP Register (0xFF00) - Estado Interno
El registro JOYP tiene bits de selección (4-5) que determinan qué grupo de botones se lee. Cuando se escribe un valor, los bits de selección se almacenan internamente y afectan las lecturas subsecuentes.
Problema potencial: Si el emulador no preserva correctamente el estado interno de selección entre writes y reads, o si hay un delay entre write y read, la ROM podría leer con selección incorrecta.
Fuente: Pan Docs - Joypad Input
Implementación
Fase A: Mario - LDH Effective Address + Real HRAM Verification
A1) Instrumentación Quirúrgica LDH en CPU
Se añadió LDHAddressWatch struct en CPU.hpp que trackea:
- PC de la última instrucción LDH ejecutada
- Operando a8 de la instrucción
- Dirección efectiva calculada
- Tipo de operación (read/write)
- Contador de discrepancias de direccionamiento
Gated por VIBOY_DEBUG_MARIO_FF92=1.
A2) Tracking Específico HRAM FF92 en MMU
Se añadió HRAMFF92Watch struct en MMU.hpp que trackea:
- PC y valor del último write a 0xFF92
- PC y valor del último read de 0xFF92
- Readback inmediato después de write (diagnóstico)
- Contador de discrepancias write/readback
Fase B: Mario - Complete Chain FF92 → IE
B1) Trace Mini FF92→IE
Se añadió FF92ToIETrace struct (global scope) que detecta la secuencia:
- Write a FF92 en PC=0x1288
- Read de FF92 en PC=0x1298
- Write a IE en PC=0x129A
Captura valores escritos/leídos y frame de ocurrencia.
B2) IE/IME/IF en Snapshots
Se añadieron campos explícitos a snapshots en rom_smoke_0442.py:
IE_value, IE_last_write_pc, IE_last_write_val,
IME_value, IF_value, irq_serviced_count.
Fase C: Clean-Room Tests
Se creó tests/test_ldh_a8_ge_0x80_0486.py con 4 tests que validan:
LDH (0x92),Aescribe en0xFF92LDH A,(0x92)lee de0xFF92LDH (0xFF),Aescribe en0xFFFF(IE)LDH A,(0xFF)lee de0xFFFF(IE)
Resultado: ✅ Todos los tests pasan.
Fase D: Tetris DX - JOYP Trace con Estado Interno
D1) Actualización JOYPTraceEvent
Se actualizó JOYPTraceEvent con:
Sourceenum:PROGRAMoCPU_POLLp1_reg_before,p1_reg_after,p1_reg_at_readselect_bits_at_read,low_nibble_at_read
D2) Contadores JOYP por Source y Selección
Se añadieron 6 contadores que distinguen entre reads desde programa vs cpu_poll, y entre buttons selected, dpad selected, o none selected.
Fase E: Intelligent Autopress
Se implementó autopress basado en eventos en src/viboy.py que:
- Activa START cuando detecta write a JOYP con buttons selected (bit 5 = 0)
- Libera START después de read con buttons selected o timeout de 60 frames
Tests y Verificación
Tests Clean-Room LDH
Comando: pytest tests/test_ldh_a8_ge_0x80_0486.py -v
Resultado:
tests/test_ldh_a8_ge_0x80_0486.py::TestLDHA8Ge0x80::test_ldh_write_0x92_writes_to_ff92 PASSED
tests/test_ldh_a8_ge_0x80_0486.py::TestLDHA8Ge0x80::test_ldh_read_0x92_reads_from_ff92 PASSED
tests/test_ldh_a8_ge_0x80_0486.py::TestLDHA8Ge0x80::test_ldh_write_0xFF_writes_to_ffff PASSED
tests/test_ldh_a8_ge_0x80_0486.py::TestLDHA8Ge0x80::test_ldh_read_0xFF_reads_from_ffff PASSED
============================== 4 passed in 0.14s ===============================
Validación: Los tests confirman que LDH calcula correctamente la dirección efectiva
cuando a8 >= 0x80. No hay bug de direccionamiento en la implementación base.
Resultados
Mario (mario.gbc) - 300 frames
Evidencia de writes a FF92: ✅ Logs muestran [HRAM-WRITE] Write FF92=00 PC:1288
Tracking estructurado: ⚠️ HRAM_FF92_WriteCount=0 en snapshots
(posible issue con gating o contador)
IE value: ⚠️ Permanece en 0x00 durante toda la ejecución
Conclusión: No hay evidencia de bug de direccionamiento en LDH.
El problema parece estar en la cadena FF92→IE, donde IE no se está actualizando
correctamente después de escribir en FF92.
Tetris DX (tetris_dx.gbc) - 300 frames
JOYP writes: ✅ ROM escribe 0x30 (buttons selected) frecuentemente
(17811 writes en 240 frames)
JOYP reads: ⚠️ Reads muestran select_bits=0x03 (ninguna selección activa)
y low_nibble=0x0F (todos los botones "soltados")
Conclusión: La ROM está escribiendo para seleccionar buttons, pero los reads ocurren cuando la selección ya se ha desactivado. Esto sugiere un problema de timing o de preservación del estado de selección entre write y read.
Archivos Afectados
src/core/cpp/CPU.hpp- AñadidoLDHAddressWatchstruct y getterssrc/core/cpp/CPU.cpp- Tracking LDH en opcodes 0xE0 y 0xF0src/core/cpp/MMU.hpp- AñadidosHRAMFF92Watch,FF92ToIETrace, actualizadoJOYPTraceEventsrc/core/cpp/MMU.cpp- Implementación de tracking FF92, IE, y JOYPsrc/core/cython/cpu.pyx,cpu.pxd- Wrappers Cython para getters LDHsrc/core/cython/mmu.pyx,mmu.pxd- Wrappers Cython para getters FF92, IE, JOYPtests/test_ldh_a8_ge_0x80_0486.py- Tests clean-room para LDHtools/rom_smoke_0442.py- Añadidos campos IE/IME/IF a snapshotssrc/viboy.py- Implementado intelligent autopress
Próximos Pasos
- Mario: Investigar por qué
HRAM_FF92_WriteCountpermanece en 0 a pesar de los logs. Verificar gatingVIBOY_DEBUG_MARIO_FF92=1. - Mario: Investigar por qué
IEno se actualiza después de escribir en FF92. Verificar secuencia FF92→IE usandoget_ff92_to_ie_trace(). - Tetris DX: Ejecutar con intelligent autopress activo para recopilar evidencia de reads con START bit = 0.
- Tetris DX: Investigar timing entre writes y reads de JOYP. Verificar si hay un delay entre write y read que cause que la selección se desactive.