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

Hardware Fix: LCD Apagado y Reset de LY

Fecha: 2025-12-22 Step ID: 0227 Estado: DRAFT

Resumen

La autopsia del Step 0225 reveló un comportamiento crítico incorrecto: la PPU seguía incrementando `LY` (valor 97) a pesar de que el LCD estaba apagado (`LCDC Bit 7 = 0`). Según la especificación del hardware (Pan Docs), cuando el LCD se deshabilita, la PPU debe detenerse inmediatamente y el registro `LY` debe reiniciarse y mantenerse en 0. Este fix corrige la implementación para asegurar que los contadores internos (`ly_`, `clock_`, `mode_`) se reseteen correctamente cuando el LCD está apagado, permitiendo que los juegos sincronicen correctamente el reinicio de la pantalla.

Concepto de Hardware

El registro LCDC (LCD Control) en la dirección 0xFF40 controla el estado de la pantalla del Game Boy. El bit 7 de este registro es especialmente crítico: cuando está en 0, el LCD está completamente deshabilitado; cuando está en 1, el LCD está encendido y la PPU opera normalmente.

Según la documentación oficial (Pan Docs), cuando el LCD se deshabilita (bit 7 de LCDC = 0), ocurre lo siguiente:

  • La PPU se detiene inmediatamente: No se procesan líneas de escaneo, no se renderizan píxeles.
  • El registro LY se resetea a 0: El valor de LY (0xFF44) se establece en 0 y permanece fijo en ese valor mientras el LCD esté deshabilitado.
  • El reloj interno se detiene: Los contadores de ciclos de la PPU se resetean.

Este comportamiento es crítico porque los juegos utilizan esta característica para sincronizar el reinicio de la pantalla. Un patrón típico es:

  1. El juego apaga el LCD (escribe 0x00 o similar en LCDC, con bit 7 = 0)
  2. El juego copia datos gráficos a la VRAM (tiles, mapas, etc.)
  3. El juego vuelve a encender el LCD (escribe en LCDC con bit 7 = 1)
  4. El juego asume que LY es 0 cuando el LCD se enciende

Si LY no está en 0 cuando el LCD se vuelve a encender, el juego puede confundirse sobre en qué línea se encuentra y fallar al renderizar correctamente. En nuestra autopsia, detectamos `LY=97` con `LCDC=0x08` (bit 7 apagado), lo que indicaba que la PPU seguía "corriendo fantasma" aunque debería estar completamente detenida.

Fuente: Pan Docs - "LCD Control Register (LCDC)" y "LCD Y-Coordinate (LY)"

Implementación

Modificamos el método `PPU::step()` para que cuando detecte que el LCD está apagado, no solo retorne sin procesar, sino que también resetee explícitamente los contadores internos de la PPU.

Componentes modificados

  • PPU.cpp: Actualizado el bloque de verificación de LCD apagado en `step()` para resetear `ly_ = 0`, `clock_ = 0` y `mode_ = MODE_0_HBLANK` antes de retornar.

Cambio implementado

En lugar de simplemente retornar cuando el LCD está apagado:

if (!lcd_enabled) {
    // LCD apagado: PPU detenida, LY se mantiene en 0
    // No acumulamos ciclos ni avanzamos líneas
    return;
}

Ahora reseteamos explícitamente los contadores:

// --- Step 0227: FIX LCD DISABLE BEHAVIOR ---
// Pan Docs: When LCD is disabled (LCDC bit 7 = 0), the PPU stops immediately
// and the LY register is reset to 0 and remains fixed at 0. The internal clock
// is also reset. This is critical for proper synchronization when the game
// turns the LCD back on, as it expects LY to be 0.
if (!lcd_enabled) {
    // Resetear contadores y estado cuando el LCD está apagado
    ly_ = 0;
    clock_ = 0;
    mode_ = MODE_0_HBLANK;  // H-Blank es el modo más seguro cuando está apagado
    
    // No acumulamos ciclos ni avanzamos líneas
    // La PPU está completamente detenida hasta que el LCD se vuelva a encender
    return;
}
// -------------------------------------------

Decisiones de diseño

Elegimos resetear `mode_` a `MODE_0_HBLANK` (H-Blank) porque es el modo más "neutro" cuando la PPU está apagada. En el hardware real, cuando el LCD se apaga, el modo técnicamente puede ser cualquiera, pero H-Blank es el más seguro y común. Lo importante es que `ly_` y `clock_` se reseteen a 0.

Archivos Afectados

  • src/core/cpp/PPU.cpp - Modificado `step()` para resetear contadores cuando LCD está apagado

Tests y Verificación

Para verificar que el fix funciona correctamente:

  1. Recompilar: .\rebuild_cpp.ps1 o python setup.py build_ext --inplace
  2. Ejecutar: python main.py roms/tetris.gb
  3. Verificar en la autopsia: Cuando el LCD está apagado (LCDC bit 7 = 0), el valor de LY debe ser 0, no 97 ni ningún otro valor.

Resultado esperado: La autopsia debería mostrar `LY: 0` cuando `LCDC: 0x08` (o cualquier valor con bit 7 = 0), indicando que la PPU respeta correctamente el estado de apagado del LCD.

Validación de módulo compilado C++: Este fix afecta directamente el comportamiento del módulo C++ de la PPU. La verificación se realiza mediante la autopsia del sistema, que lee directamente el estado interno de la PPU compilada.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Comportamiento del LCD Apagado: Cuando el bit 7 de LCDC es 0, la PPU se detiene completamente y LY se resetea a 0. Esto no es solo una optimización, sino un comportamiento crítico del hardware que los juegos utilizan para sincronización.
  • Importancia de LY=0: Los juegos asumen que cuando encienden el LCD, LY comienza en 0. Si LY tiene un valor diferente, puede causar problemas de renderizado o sincronización.
  • Reset de Contadores: No es suficiente con no procesar ciclos cuando el LCD está apagado; debemos resetear activamente los contadores para reflejar el estado real del hardware.

Lo que Falta Confirmar

  • Comportamiento del registro STAT: Cuando el LCD está apagado, ¿qué valor debe tener el registro STAT? La documentación no es completamente clara sobre esto, pero en nuestro caso, al resetear el modo a H-Blank, el STAT debería reflejar ese modo.
  • Efecto en otros registros: ¿Hay otros registros o estados de la PPU que deban resetearse cuando el LCD se apaga? Por ahora, solo reseteamos LY, clock y mode, pero podría haber más.

Hipótesis y Suposiciones

Asumimos que resetear `mode_` a `MODE_0_HBLANK` es el comportamiento correcto cuando el LCD está apagado. La documentación no especifica explícitamente qué modo debe reportarse cuando el LCD está apagado, pero H-Blank es el modo más seguro y neutral. Si futuros tests revelan que esto causa problemas, podríamos necesitar ajustar esta decisión.

Próximos Pasos

  • [ ] Ejecutar el emulador con el fix aplicado y verificar que LY=0 cuando LCD está apagado
  • [ ] Verificar si el juego ahora puede reiniciar la pantalla correctamente
  • [ ] Si sigue habiendo problemas, investigar el comportamiento del registro STAT cuando LCD está apagado