⚠️ Clean-Room / Educativo

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.

Trazado de Ejecución "Triggered" (Trap Trace)

Fecha: 2025-12-18 Step ID: 0052 Estado: Draft

Resumen

Se implementó un sistema de trazado de ejecución "triggered" (disparado) que se activa automáticamente cuando el juego escribe `LCDC=0x80` (LCD ON sin fondo). El sistema captura las primeras 100 instrucciones ejecutadas después de este evento crítico, mostrando información detallada de cada instrucción: PC, opcode, registros, flags, estado de interrupciones (IF/IE), y estado de la PPU (LY/STAT). Este trazado permitirá identificar si el juego entra en un bucle de polling esperando V-Blank y por qué no sale de ese bucle.

Concepto de Hardware

Cuando un juego de Game Boy enciende el LCD escribiendo `LCDC=0x80`, está activando la pantalla pero sin el fondo (bit 0 = 0). Este es un estado transitorio común en la inicialización: el juego enciende el LCD y luego espera a que ocurra V-Blank para configurar el resto de los gráficos (activar fondo, sprites, etc.).

Si el juego tiene las interrupciones deshabilitadas (`IE=00`), no puede usar el mecanismo automático de interrupciones. En su lugar, debe hacer polling (preguntar manualmente) leyendo el registro `IF` (0xFF0F) y comprobando el bit 0 (V-Blank). Si el bit está activo, el juego sabe que está en V-Blank y puede continuar. Si no, debe esperar en un bucle.

El problema que estamos investigando es que el juego se queda atascado después de encender el LCD. El trazado nos permitirá ver exactamente qué instrucciones se ejecutan y si hay un bucle de polling que nunca detecta V-Blank, lo cual indicaría que nuestro emulador no está activando correctamente el flag V-Blank en `IF` cuando corresponde.

Fuente: Pan Docs - LCD Control Register, Interrupt Flag Register, V-Blank Polling

Implementación

Se añadió un sistema de trazado en la clase `Viboy` que se activa automáticamente cuando se detecta un cambio en el registro LCDC de cualquier valor a `0x80`. El sistema captura el estado de la CPU antes de cada instrucción y muestra información detallada en consola.

Componentes modificados

  • src/viboy.py: Añadidos atributos `_trace_active`, `_trace_counter`, y `_prev_lcdc` para controlar el trazado. La lógica de detección y trazado se implementó en el método `run()`.

Decisiones de diseño

Detección de activación: El trazado se activa cuando LCDC cambia de cualquier valor a `0x80`. Esto captura el momento exacto en que el juego enciende el LCD, que es cuando comienza el problema que estamos investigando.

Límite de 100 instrucciones: Se limita el trazado a 100 instrucciones para evitar inundar la consola con información. Este número es suficiente para ver varios ciclos de un bucle de polling típico.

Información capturada: El trazado muestra PC, opcode, todos los registros (A, BC, DE, HL, SP), flags (Z, N, H, C), registros de interrupciones (IF, IE), y estado de la PPU (LY, STAT). Esta información es suficiente para entender qué está haciendo el juego y por qué se queda atascado.

Captura antes del tick: El PC y opcode se capturan antes de ejecutar la instrucción (`tick()`), para mostrar la instrucción que se va a ejecutar, no la siguiente. Esto es más útil para debugging.

Archivos Afectados

  • src/viboy.py - Añadido sistema de trazado "triggered" con detección de cambio de LCDC a 0x80

Tests y Verificación

Estado: Ejecutado con ROM de prueba (pkmn.gb).

Comando ejecutado: python main.py pkmn.gb

Entorno: Windows, Python 3.13.5

Resultado observado: El trace se activó correctamente cuando el juego escribió `LCDC=0x80`. Se capturaron 100 instrucciones mostrando un bucle de polling claro:

TRACE [007]: PC=0x006B | OP=0xF0 | ... | IF=0x00 IE=0x00 | LY=  0 STAT=0x03 | Cycles=3
TRACE [008]: PC=0x006D | OP=0xFE | ... | IF=0x00 IE=0x00 | LY=  0 STAT=0x03 | Cycles=2
TRACE [009]: PC=0x006F | OP=0x20 | ... | IF=0x00 IE=0x00 | LY=  0 STAT=0x03 | Cycles=3
TRACE [010]: PC=0x006B | OP=0xF0 | ... | IF=0x00 IE=0x00 | LY=  0 STAT=0x03 | Cycles=3
...

Análisis del bucle: El patrón identificado es:

  • 0xF0 (LDH A, (n)) en PC=0x006B - Lee IF (0xFF0F)
  • 0xFE (CP d8) en PC=0x006D - Compara A con un valor inmediato (probablemente 0x01 para el bit V-Blank)
  • 0x20 (JR NZ, e) en PC=0x006F - Salto relativo si no es cero (vuelve atrás si no hay V-Blank)

Problema identificado: El trace muestra que:

  • LY avanza correctamente (0 → 1 → 2), lo que indica que la PPU está funcionando
  • IF siempre es 0x00, lo que significa que el flag V-Blank nunca se activa
  • El trace solo captura 100 instrucciones (~1 línea), pero para llegar a LY=144 se necesitan ~5,472 instrucciones
  • No aparece el log 🎯 PPU: V-Blank iniciado, lo que confirma que la PPU no está llegando a LY=144

Hipótesis: El problema es que la PPU no está avanzando lo suficientemente rápido, o hay algún problema con la verificación del LCD. Se implementó una corrección para verificar que el LCD esté encendido antes de avanzar la PPU.

Modificaciones realizadas:

  • Aumentado el límite del trace de 100 a 1000 instrucciones para capturar más información
  • Añadida verificación en PPU para que solo avance cuando el LCD esté encendido (LCDC bit 7 = 1)
  • Añadido log informativo cuando se activa V-Blank para diagnóstico

Estado: Draft - Pendiente de verificar si la corrección resuelve el problema.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Polling vs Interrupciones: Cuando las interrupciones están deshabilitadas, los juegos deben hacer polling manual del registro IF para detectar eventos como V-Blank. Esto es menos eficiente pero permite control total sobre cuándo se procesan los eventos.
  • Estado transitorio LCDC=0x80: Este valor indica que el LCD está encendido pero el fondo está desactivado. Es un estado común durante la inicialización, donde el juego espera V-Blank para configurar el resto de los gráficos.
  • Trazado "triggered": Un sistema de trazado que se activa automáticamente cuando ocurre un evento específico (en este caso, cambio de LCDC a 0x80) es más útil que un trazado continuo, ya que captura exactamente el momento crítico sin generar ruido innecesario.

Lo que Falta Confirmar

  • Patrón de polling: Necesito verificar si el juego realmente entra en un bucle de polling y qué instrucciones exactas usa. El trazado nos dará esta información.
  • Activación de IF: Necesito confirmar si nuestro emulador está activando correctamente el bit 0 de IF cuando la PPU entra en V-Blank. Si no lo hace, el juego nunca detectará V-Blank y se quedará atascado.
  • Timing de V-Blank: Necesito verificar si el timing de la PPU es correcto y si V-Blank se activa en el momento adecuado según la especificación del hardware.

Hipótesis y Suposiciones

Hipótesis principal: El juego se queda atascado porque entra en un bucle de polling esperando V-Blank, pero nuestro emulador no está activando correctamente el flag V-Blank en IF cuando la PPU entra en modo V-Blank.

Suposición: Asumo que el juego usa polling porque IE=00 (interrupciones deshabilitadas). Si el trazado muestra un bucle de lectura de IF, esto confirmará la suposición.

Próximos Pasos

  • [ ] Ejecutar el emulador con una ROM (pkmn.gb) y capturar el trazado cuando se active
  • [ ] Analizar el trazado para identificar si hay un bucle de polling
  • [ ] Si hay un bucle, verificar si el flag V-Blank en IF se activa correctamente
  • [ ] Si el flag no se activa, revisar la lógica de la PPU para asegurar que activa IF cuando entra en V-Blank
  • [ ] Verificar el timing de la PPU para asegurar que V-Blank ocurre en el momento correcto