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.
El Francotirador: Cazando el Bucle Infinito
Resumen
El "Estetoscopio" (Step 0222) reveló que la CPU está atrapada en un bucle infinito en la dirección de memoria 0x02B4, con el fondo apagado (LCDC Bit 0 = 0) y la VRAM vacía. Para entender qué condición de salida no se está cumpliendo (probablemente esperando V-Blank o un estado específico de hardware), implementamos un trazado condicional que solo se activa cuando el PC está en el rango 0x02B0-0x02C0. Esta instrumentación quirúrgica nos permitirá ver las instrucciones del bucle y los valores de los registros sin saturar la consola con logs masivos.
Concepto de Hardware
Cuando un juego de Game Boy se inicia, típicamente sigue este flujo:
- Inicialización de hardware: Configura registros de I/O (LCDC, BGP, etc.)
- Espera de V-Blank: Muchos juegos esperan el primer V-Blank antes de copiar gráficos a VRAM
- Copia de gráficos: Transfiere tiles desde ROM a VRAM
- Activación del fondo: Enciende el LCDC Bit 0 para mostrar el fondo
El problema que estamos enfrentando es que el juego está atascado en el paso 2: está esperando V-Blank, pero nuestro emulador no está generando V-Blank correctamente, o el juego está leyendo un registro de hardware (como STAT o LY) que no está actualizándose como espera.
El registro LY (Line Y) en la dirección 0xFF44 indica la línea de escaneo actual (0-153). Cuando LY alcanza 144, la PPU entra en modo V-Blank. Muchos juegos hacen polling de este registro en un bucle como:
wait_vblank:
LDH A, (0x44) ; Lee LY
CP 0x90 ; Compara con 144 (0x90)
JR NZ, wait_vblank ; Si no es 144, vuelve a esperar
Si LY nunca alcanza 144 (porque la PPU no está avanzando), el juego se queda atascado en este bucle infinitamente. El "Francotirador" nos permitirá ver exactamente qué instrucciones se están ejecutando y qué valores están comparando.
Implementación
Implementamos un bloque de debug condicional en el método step() de la CPU que solo imprime información cuando el PC está en el rango problemático (0x02B0-0x02C0). Esto nos permite ver:
- PC: La dirección de memoria actual (para ver el flujo del bucle)
- Opcode: La instrucción que se está ejecutando
- AF: El registro AF (contiene flags y acumulador, útil para ver comparaciones)
- LY: El valor actual de la línea de escaneo (crítico para detectar si V-Blank está funcionando)
Modificación en CPU.cpp
Se añadió un bloque condicional justo antes del fetch del opcode:
// --- Step 0223: EL FRANCOTIRADOR (Debug Quirúrgico) ---
// Solo imprimimos si el PC está en la zona caliente del bucle infinito (0x02B0 - 0x02C0)
// Esto nos dirá qué está comprobando el juego sin saturar la consola.
if (regs_->pc >= 0x02B0 && regs_->pc <= 0x02C0) {
uint8_t opcode_preview = mmu_->read(regs_->pc);
uint8_t ly_value = (ppu_ != nullptr) ? ppu_->get_ly() : 0;
printf("[SNIPER] PC: 0x%04X | Opcode: 0x%02X | AF: 0x%04X | LY: %d\n",
regs_->pc, opcode_preview, regs_->get_af(), ly_value);
}
// -----------------------------------------------------
Decisiones de diseño
- Rango de memoria: Elegimos
0x02B0-0x02C0porque el "Estetoscopio" detectó que el PC estaba en0x02B4. Incluimos un rango para capturar todo el bucle, no solo una instrucción. - Información mínima: Solo imprimimos PC, Opcode, AF y LY. Esto es suficiente para identificar el bucle sin saturar la consola.
- Verificación de PPU: Verificamos que
ppu_no seanullptrantes de llamar aget_ly()para evitar crashes. - Sin límite de impresiones: A diferencia del trace anterior (Step 0205) que limitaba a 200 instrucciones, este no tiene límite porque solo se activa en un rango muy pequeño de memoria, por lo que no saturará la consola.
Archivos Afectados
src/core/cpp/CPU.cpp- Añadido bloque de debug quirúrgico "El Francotirador" en el métodostep()
Tests y Verificación
Para validar esta implementación:
- Recompilar: Ejecutar
.\rebuild_cpp.ps1para recompilar la extensión Cython con el nuevo código C++. - Ejecutar:
python main.py roms/tetris.gb - Observar salida: La consola debería mostrar líneas como:
[SNIPER] PC: 0x02B4 | Opcode: 0xE0 | AF: 0xXXXX | LY: XX [SNIPER] PC: 0x02B6 | Opcode: 0xFE | AF: 0xXXXX | LY: XX - Análisis esperado:
- Si vemos
Opcode: 0xF0(LDH A, (n)) seguido deOpcode: 0xFE(CP d8) conLYconstante: El juego está esperando V-Blank pero LY no avanza. - Si vemos
Opcode: 0xF3(LDH A, (0x41)) seguido de comparaciones: El juego está esperando un modo específico de STAT. - Si vemos que LY cambia pero el bucle continúa: El juego está esperando otra condición (posiblemente un flag de interrupción).
- Si vemos
Validación de módulo compilado C++: El código se compila en la extensión Cython, por lo que cualquier error de compilación se detectará durante rebuild_cpp.ps1.
Fuentes Consultadas
- Pan Docs: LCDC Register - Registro de control del LCD
- Pan Docs: STAT Register - Registro de estado del LCD
- Pan Docs: LY Register - Registro de línea de escaneo Y
- Pan Docs: CPU Instruction Set - Conjunto de instrucciones del LR35902
Integridad Educativa
Lo que Entiendo Ahora
- Polling de hardware: Los juegos de Game Boy frecuentemente hacen polling de registros de hardware (LY, STAT) en bucles para sincronizar con eventos del hardware. Si estos registros no se actualizan correctamente, el juego se queda atascado.
- Debug quirúrgico: En lugar de trazar todas las instrucciones (que satura la consola), podemos usar trazas condicionales que solo se activan en rangos específicos de memoria. Esto es más eficiente y permite identificar problemas localizados.
- V-Blank como condición de salida: Muchos juegos esperan V-Blank antes de copiar gráficos porque es el único momento "seguro" en que la PPU no está leyendo VRAM. Si V-Blank nunca ocurre, el juego no puede proceder.
Lo que Falta Confirmar
- Valor de LY: Necesitamos verificar si LY está avanzando correctamente. Si LY está atascado en un valor (ej: 0 o 143), sabremos que la PPU no está actualizando el registro.
- Instrucciones del bucle: Necesitamos ver qué instrucciones exactas conforman el bucle. Si vemos LDH A, (0x44) seguido de CP 0x90, confirmaremos que está esperando V-Blank.
- Estado de AF: El registro AF contiene los flags. Si vemos que el flag Z cambia pero el bucle continúa, puede indicar que la condición de salida está mal implementada.
Hipótesis y Suposiciones
Hipótesis principal: El juego está esperando V-Blank (LY = 144) pero la PPU no está actualizando LY correctamente, o la PPU no está entrando en modo V-Blank. El "Francotirador" nos confirmará si esta hipótesis es correcta mostrándonos el valor de LY durante el bucle.
Próximos Pasos
- [ ] Ejecutar el emulador y recopilar el log del "Francotirador"
- [ ] Analizar el patrón de instrucciones para identificar el bucle exacto
- [ ] Verificar si LY está avanzando o estático
- [ ] Si LY no avanza: Investigar por qué la PPU no actualiza LY
- [ ] Si LY avanza pero el bucle continúa: Investigar la condición de salida del bucle
- [ ] Implementar la corrección necesaria basada en los hallazgos