⚠️ 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.

Corrección PPU: Verificación LCD Enabled

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

Resumen

Se corrigió un bug crítico en la PPU: la PPU avanzaba incluso cuando el LCD estaba apagado (LCDC bit 7 = 0). Según Pan Docs, cuando el LCD está apagado, la PPU debe detenerse y LY debe mantenerse en 0. Se añadió una verificación al inicio del método `step()` para comprobar si el LCD está encendido antes de avanzar el timing. Adicionalmente, se aumentó el límite del trace de 100 a 1000 instrucciones y se añadió un log informativo cuando se activa V-Blank para diagnóstico.

Concepto de Hardware

En la Game Boy, el registro LCDC (LCD Control, 0xFF40) controla el estado del LCD. El bit 7 (LCDC bit 7) es el "LCD Enable": cuando es 0, el LCD está apagado; cuando es 1, el LCD está encendido.

Comportamiento crítico: Cuando el LCD está apagado (LCDC bit 7 = 0), la PPU se detiene completamente:

  • LY (Línea actual) se mantiene en 0
  • El timing de la PPU no avanza
  • No se generan interrupciones V-Blank
  • El registro STAT (LCD Status) refleja el modo actual (generalmente Mode 0 o Mode 1)

Cuando el LCD está encendido (LCDC bit 7 = 1), la PPU avanza normalmente:

  • LY incrementa de 0 a 153 (un frame completo)
  • El timing avanza según los ciclos de reloj
  • Se generan interrupciones V-Blank cuando LY llega a 144
  • El registro STAT refleja el modo actual de la PPU (Mode 0, 1, 2 o 3)

Este comportamiento es crítico porque muchos juegos encienden el LCD y luego esperan V-Blank para configurar los gráficos. Si la PPU avanza cuando el LCD está apagado, el timing se desincroniza y el juego nunca detecta V-Blank.

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

Implementación

Se añadió una verificación al inicio del método `step()` de la PPU para comprobar si el LCD está encendido antes de avanzar el timing. Si el LCD está apagado, el método retorna inmediatamente sin procesar ciclos.

Componentes modificados

  • src/gpu/ppu.py: Añadida verificación de LCD enabled al inicio de `step()`. Si el LCD está apagado, la PPU no avanza.
  • src/gpu/ppu.py: Añadido log informativo cuando se activa V-Blank para diagnóstico.
  • src/viboy.py: Aumentado el límite del trace de 100 a 1000 instrucciones para capturar más información.

Decisiones de diseño

Verificación temprana: La verificación del LCD se hace al inicio del método `step()`, antes de procesar cualquier ciclo. Esto asegura que la PPU no avance cuando el LCD está apagado, incluso si se llama a `step()` con ciclos.

Retorno inmediato: Si el LCD está apagado, el método retorna inmediatamente sin procesar ciclos ni actualizar el estado. Esto es más eficiente que procesar ciclos y luego ignorarlos.

Log informativo: Se añadió un log informativo (nivel INFO, no DEBUG) cuando se activa V-Blank para que siempre sea visible en consola. Esto ayuda a diagnosticar si la PPU está llegando a LY=144.

Límite del trace aumentado: Se aumentó el límite del trace de 100 a 1000 instrucciones para capturar más información. Aunque aún no es suficiente para llegar a LY=144 (~5,472 instrucciones), permite ver más del bucle de polling.

Archivos Afectados

  • src/gpu/ppu.py - Añadida verificación de LCD enabled y log informativo de V-Blank
  • src/viboy.py - Aumentado límite del trace de 100 a 1000 instrucciones

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 con 1000 instrucciones muestra que:

  • LY avanza correctamente (de 0 a 23 en el trace)
  • El bucle de polling sigue activo (patrón repetitivo: 0xF0, 0xFE, 0x20)
  • IF siempre es 0x00 (el flag V-Blank nunca se activa)
  • No aparece el log 🎯 PPU: V-Blank iniciado (la PPU no llega a LY=144)

Análisis: El trace muestra que la PPU está avanzando correctamente cuando el LCD está encendido (LY incrementa de 0 a 23), pero el bucle de polling consume ciclos muy lentamente. En 1000 instrucciones, solo avanzamos de LY=0 a LY=23. Para llegar a LY=144 se necesitan aproximadamente 5,472 instrucciones, lo cual significa que el trace termina antes de llegar a V-Blank.

Problema identificado: El bucle de polling está consumiendo ciclos muy lentamente. Cada iteración del bucle consume ~8 M-Cycles (3+2+3), lo cual es correcto, pero el problema es que el juego está esperando V-Blank y nunca lo detecta porque el trace termina antes de llegar a LY=144.

Hipótesis: La PPU probablemente está funcionando correctamente y llegará a LY=144 eventualmente, pero el bucle de polling consume tantos ciclos que necesitamos esperar mucho tiempo. El problema real puede ser que el juego se rinde y apaga el LCD antes de llegar a V-Blank, o que hay algún otro problema de timing.

Estado: Draft - La corrección parece funcionar (LY avanza), pero necesitamos verificar si realmente llega a LY=144 o si el juego apaga el LCD antes.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • LCD Enable y PPU: La PPU solo avanza cuando el LCD está encendido (LCDC bit 7 = 1). Cuando el LCD está apagado, la PPU se detiene y LY se mantiene en 0. Esto es crítico para el timing correcto de la pantalla.
  • Bucle de polling: Los juegos que deshabilitan interrupciones (IE=00) deben hacer polling manual del registro IF para detectar V-Blank. El bucle típico lee IF, compara con 0x01 (bit V-Blank), y si no está activo, vuelve atrás.
  • Timing de V-Blank: Para llegar a LY=144 (inicio de V-Blank), se necesitan aproximadamente 5,472 instrucciones (144 líneas × ~38 instrucciones/línea). Un trace de 100 instrucciones solo captura ~1 línea, lo cual es insuficiente para ver si llega a V-Blank.

Lo que Falta Confirmar

  • Verificación de la corrección: Necesito verificar si la corrección resuelve el problema. El trace anterior mostró que LY avanzaba, pero no vimos si llegaba a LY=144. Con el límite del trace aumentado a 1000, deberíamos ver más información.
  • Timing del LCD: Necesito verificar si hay algún problema con el timing del LCD. Por ejemplo, ¿el LCD se apaga antes de llegar a V-Blank? ¿Hay algún problema con la sincronización entre la CPU y la PPU?
  • Log de V-Blank: Necesito verificar si el log 🎯 PPU: V-Blank iniciado aparece cuando la PPU llega a LY=144. Si no aparece, puede haber otro problema.

Hipótesis y Suposiciones

Hipótesis principal: El problema era que la PPU avanzaba incluso cuando el LCD estaba apagado, lo que desincronizaba el timing. Con la corrección, la PPU solo avanzará cuando el LCD esté encendido, lo que debería permitir que llegue a LY=144 y active el flag V-Blank.

Suposición: Asumo que el LCD permanece encendido (LCDC=0x80) durante todo el tiempo necesario para llegar a V-Blank. Si el LCD se apaga antes, el problema persistirá.

Próximos Pasos

  • [ ] Ejecutar el emulador con el límite del trace aumentado a 1000 instrucciones
  • [ ] Verificar si aparece el log 🎯 PPU: V-Blank iniciado cuando la PPU llega a LY=144
  • [ ] Verificar si el flag V-Blank se activa en IF cuando LY llega a 144
  • [ ] Verificar si el juego detecta V-Blank y sale del bucle de polling
  • [ ] Si el problema persiste, investigar otros posibles problemas (timing, sincronización, etc.)