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.
Francotirador II: El Bucle de la Muerte
Resumen
La autopsia del Step 0235 reveló que la CPU se ha estancado en la dirección 0x2B30 tras 9.5 millones de ciclos, con la VRAM vacía y el LCD apagado. Activamos una traza quirúrgica en esa dirección para identificar la instrucción exacta y la condición de espera que impide que el juego continúe.
Concepto de Hardware
Cuando un programa se detiene en una dirección específica durante millones de ciclos, generalmente está esperando una condición que nunca se cumple. Esto puede ser:
- Busy Wait Loop: Un bucle que lee un registro de hardware (como STAT, DIV, o un registro de I/O) esperando que un bit cambie de estado.
- Condición de Flag: Una instrucción condicional (JR NZ, JR Z, etc.) que salta a sí misma porque el flag nunca cambia.
- Interrupción Pendiente: El juego espera una interrupción (V-Blank, Timer, Serial) que nuestra implementación no está generando correctamente.
El análisis de la autopsia mostró que:
- PC: 0x2B30 - La CPU avanzó desde 0x02B4 hasta 0x2B30 (casi 10KB de código) y se detuvo.
- VRAM: 00 - No se ha escrito nada en VRAM, lo que sugiere que el juego no ha llegado a la fase de carga de gráficos.
- IE: 0x08 - Bit 3 habilitado (Serial Interrupt), algo inusual para el arranque de Tetris que normalmente espera V-Blank (0x01).
- cycles_: 9,590,921 - El emulador funciona, pero el juego ha decidido detenerse.
La única forma de entender qué está pasando es ver la instrucción exacta en 0x2B30 y los registros asociados en tiempo real.
Implementación
Activamos un "Francotirador" (debug quirúrgico) que imprime información detallada solo cuando el PC está en la zona crítica (0x2B2A - 0x2B35). Esto nos permite ver:
- El opcode exacto que se está ejecutando.
- El siguiente byte (por si es una instrucción de 2 bytes como LDH o CP).
- El estado completo de los registros (AF, BC, DE, HL).
Componentes modificados
src/core/cpp/CPU.cpp: Agregado bloque de debug quirúrgico antes del fetch_byte() en step().src/viboy.py: Desactivada la Autopsia (Step 0235) para limpiar la consola y ver solo los logs del Francotirador.
Decisiones de diseño
Zona de Debug: Elegimos un rango de direcciones (0x2B2A - 0x2B35) en lugar de solo 0x2B30 para capturar instrucciones que puedan estar justo antes o después del punto de bloqueo. Esto nos ayuda a entender el contexto del bucle.
Lectura Directa de Memoria: Leemos el opcode directamente desde la MMU antes de fetch_byte() para evitar que el fetch modifique el PC antes de imprimir. Esto nos da una vista precisa del estado en el momento exacto.
Desactivación de Autopsia: La autopsia genera mucha salida que puede ocultar los logs del Francotirador. Al desactivarla, tenemos una consola limpia que muestra solo la información crítica.
Archivos Afectados
src/core/cpp/CPU.cpp- Agregado#include <cstdio>y bloque de debug quirúrgico en step()src/viboy.py- Comentado el bloque completo de la Autopsia (Step 0235)
Tests y Verificación
Para verificar el funcionamiento del debug quirúrgico:
- Recompilar:
.\rebuild_cpp.ps1 - Ejecutar:
python main.py roms/tetris.gb - Observar: Los logs
[SNIPER]deberían aparecer cuando el PC entre en la zona 0x2B2A-0x2B35
Lo que buscamos:
- Si vemos
JR NZ(Salto relativo si no cero) saltando a sí mismo o un poco atrás, es un bucle de espera. - Si vemos
CALLa una función que nunca vuelve, puede ser un problema de stack o de retorno. - Si vemos lecturas a
0xFF00(Joypad) o0xFF01(Serial), el juego está esperando entrada del usuario o comunicación serial. - Si vemos
BIToCPseguido deJR, el juego está verificando un flag o registro que nunca cambia.
Fuentes Consultadas
- Pan Docs: CPU Instruction Set
- Análisis de autopsia:
autopsy_step_0235_20251222_191910.txt
Integridad Educativa
Lo que Entiendo Ahora
- Debug Quirúrgico: En lugar de trazar todas las instrucciones (que es lento y genera demasiada salida), podemos activar logs solo en zonas críticas del código. Esto es similar a los breakpoints condicionales en un depurador.
- Busy Wait Loops: Los juegos de Game Boy a menudo esperan eventos de hardware (V-Blank, Timer) mediante bucles que leen registros repetidamente. Si nuestra implementación no genera estos eventos correctamente, el juego se queda atascado.
- Serial Interrupt (IE: 0x08): Es inusual que un juego habilite la interrupción serial durante el arranque. Esto podría indicar que el juego está esperando comunicación serial que nunca llegará, o que hay un bug en nuestra implementación de interrupciones.
Lo que Falta Confirmar
- Instrucción Exacta: Necesitamos ver qué opcode está en 0x2B30 para entender qué condición está verificando.
- Estado de Flags: Los flags (Z, N, H, C) en el momento del bloqueo nos dirán por qué una instrucción condicional siempre salta o nunca salta.
- Registros de I/O: Si el juego está leyendo un registro de hardware (STAT, DIV, Serial), necesitamos verificar que nuestra implementación está actualizando esos registros correctamente.
Hipótesis y Suposiciones
Hipótesis Principal: El juego está en un bucle de espera que verifica un registro de hardware (probablemente STAT para V-Blank o un registro de Serial) que nuestra implementación no está actualizando correctamente. La presencia de IE: 0x08 (Serial) es sospechosa y sugiere que el juego podría estar esperando una interrupción serial que nunca se generará.
Suposición: Asumimos que el opcode en 0x2B30 es una instrucción condicional (JR, JP, CALL condicional) o una lectura de I/O seguida de una verificación. Si fuera una instrucción incondicional (NOP, LD, etc.), el juego continuaría ejecutándose.
Próximos Pasos
- [ ] Ejecutar el emulador y capturar los logs del Francotirador
- [ ] Analizar el opcode en 0x2B30 y determinar qué condición está verificando
- [ ] Verificar si el registro de hardware que el juego está leyendo se está actualizando correctamente
- [ ] Si es una interrupción serial, investigar por qué el juego la habilita durante el arranque
- [ ] Implementar el fix necesario basado en los hallazgos del debug