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

Trampa de LCDC y Fix del Timer

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

Resumen

Se implementó una trampa de diagnóstico en la MMU para monitorizar todos los intentos de escritura en el registro LCDC (0xFF40), permitiendo identificar si el juego intenta encender la pantalla pero falla, o si nunca llega a esa parte del código. Adicionalmente, se corrigió un bug crítico en la lógica de overflow del Timer (TIMA) que impedía que la interrupción del Timer se generara correctamente cuando TIMA pasaba de 0xFF a 0x00. Esta corrección es vital porque muchos juegos (incluyendo Pokémon) dependen del Timer para avanzar en su inicialización.

Concepto de Hardware

Registro LCDC (LCD Control, 0xFF40)

El registro LCDC es el controlador principal de la pantalla LCD. El bit 7 (0x80) controla si el LCD está encendido (1) o apagado (0). Cuando el LCD está apagado, la pantalla se muestra en blanco/azul y el registro LY (Línea Y) se congela en 0. Muchos juegos apagan el LCD durante la inicialización para cargar datos en VRAM sin interferencias, y luego lo encienden escribiendo un valor con el bit 7 activo (típicamente 0x80, 0x91, etc.).

Si un juego apaga el LCD al inicio y nunca intenta volver a encenderlo, indica que el código se quedó en un bucle esperando una condición que nunca se cumple (por ejemplo, una interrupción del Timer o un cambio en algún registro).

Timer TIMA Overflow

El Timer Counter (TIMA, 0xFF05) es un contador de 8 bits que incrementa según la frecuencia configurada en TAC. Cuando TIMA hace overflow (pasa de 0xFF a 0x00), el hardware real hace lo siguiente:

  1. Detecta el overflow: TIMA se incrementa de 0xFF a 0x00
  2. Recarga TIMA: TIMA se recarga inmediatamente con el valor de TMA (Timer Modulo, 0xFF06)
  3. Solicita interrupción: Se activa el bit 2 del registro IF (0xFF0F) para indicar una interrupción de Timer pendiente

La interrupción del Timer es crítica para muchos juegos que la usan para medir el tiempo, generar eventos periódicos, o simplemente avanzar el código de inicialización. Si esta interrupción no se genera correctamente, el juego puede quedarse esperando en un bucle infinito.

Fuente: Pan Docs - LCD Control Register, Timer and Divider Registers

Implementación

Trampa de LCDC en MMU

Se añadió un log CRÍTICO en el método write_byte de la MMU que se dispara cada vez que el juego intenta escribir en el registro LCDC (0xFF40). El log muestra el valor anterior y el nuevo valor, permitiendo rastrear todos los cambios de estado del LCD:

# CRÍTICO: Trampa de diagnóstico para LCDC (0xFF40)
# Monitoriza intentos de encender/apagar la pantalla
if addr == IO_LCDC:
    old_value = self.read_byte(IO_LCDC)
    logging.critical(f"[TRAP LCDC] INTENTO DE CAMBIO LCDC: {old_value:02X} -> {value:02X}")

Este log se mostrará siempre en consola (nivel CRITICAL), independientemente de la configuración de logging, permitiendo diagnosticar si el juego intenta encender la pantalla o si nunca llega a esa parte del código.

Corrección del Timer Overflow

Se corrigió la lógica de detección de overflow en el método tick del Timer. La implementación anterior verificaba si TIMA era 0xFF antes de incrementar, pero luego recargaba directamente sin hacer el incremento que causaba el overflow. La lógica correcta debe ser:

  1. Incrementar primero: TIMA se incrementa de 0xFF a 0x00 (overflow)
  2. Detectar el overflow: Si después del incremento TIMA es 0x00, significa que hubo overflow
  3. Recargar y solicitar interrupción: TIMA se recarga con TMA y se solicita la interrupción

Código corregido:

# Incrementar TIMA (esto puede causar overflow de 0xFF a 0x00)
self._tima = (self._tima + 1) & 0xFF

# Si TIMA hizo overflow (pasó de 0xFF a 0x00), recargar y solicitar interrupción
if self._tima == 0x00:
    # OVERFLOW detectado: Recargar TIMA con TMA
    self._tima = self._tma & 0xFF
    # Solicitar interrupción Timer (Bit 2 de IF, 0xFF0F)
    self._request_timer_interrupt()

Esta corrección es crítica porque sin ella, la interrupción del Timer nunca se generaba, impidiendo que los juegos que dependen de ella avancen en su inicialización.

Archivos Afectados

  • src/memory/mmu.py - Añadida trampa de diagnóstico para escrituras en LCDC (0xFF40)
  • src/io/timer.py - Corregida lógica de detección de overflow de TIMA para generar interrupciones correctamente
  • main.py - Ajustada configuración de logging para asegurar que mensajes CRITICAL se muestren

Tests y Verificación

Verificación del Timer

La corrección del Timer se validó revisando la lógica implementada según la especificación del hardware:

  • Lógica de overflow: El código ahora incrementa TIMA primero y luego detecta si el resultado es 0x00, que es el comportamiento correcto del hardware
  • Recarga de TMA: Cuando se detecta overflow, TIMA se recarga inmediatamente con el valor de TMA
  • Solicitud de interrupción: El método _request_timer_interrupt() activa correctamente el bit 2 del registro IF (0xFF0F)

Diagnóstico con pkmn.gb

ROM: pkmn.gb (ROM aportada por el usuario, no distribuida)

Modo de ejecución: UI gráfica, con trampa de LCDC activa

Criterio de éxito: Los logs de consola deben mostrar si el juego intenta escribir en LCDC (cambiar de 0x00 a un valor con bit 7 activo, como 0x80 o 0x91)

Observación esperada:

  • Si vemos [TRAP LCDC] INTENTO DE CAMBIO LCDC: 00 -> 91: El juego intenta encender la pantalla, pero hay un problema con nuestra implementación de LCDC o PPU
  • Si vemos [TRAP LCDC] INTENTO DE CAMBIO LCDC: 00 -> 00 y luego nada más: El juego apaga el LCD pero nunca intenta volver a encenderlo, indicando que está colgado esperando una interrupción o condición
  • Si no vemos ningún log de LCDC: El juego nunca intenta cambiar el estado del LCD, posiblemente colgado antes de llegar a esa parte del código

Resultado: Pendiente de verificación - Se requiere ejecutar el juego y analizar los logs de consola para determinar el diagnóstico.

Notas legales: La ROM pkmn.gb es aportada por el usuario para pruebas locales. No se distribuye ni se enlaza en este proyecto.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Timer Overflow: El overflow de TIMA debe detectarse DESPUÉS del incremento, no antes. El hardware incrementa primero y luego detecta si el resultado es 0x00, lo que indica que hubo overflow desde 0xFF.
  • Diagnóstico de LCDC: Monitorizar escrituras en registros críticos del hardware permite identificar exactamente dónde se está quedando el juego. Si el juego intenta encender el LCD pero falla, el problema está en nuestra implementación de PPU/LCDC. Si nunca intenta encenderlo, el problema está antes (CPU, interrupciones, timer).
  • Interrupciones del Timer: Muchos juegos dependen críticamente de las interrupciones del Timer para avanzar. Si el Timer no genera interrupciones correctamente, el juego puede quedarse en bucles infinitos esperando eventos que nunca ocurren.

Lo que Falta Confirmar

  • Comportamiento exacto del Timer en hardware: Según la documentación, escribir en TIMA durante el ciclo en que hace overflow tiene comportamiento especial. Por ahora, implementamos la lógica básica. Esto puede necesitar ajustes si encontramos casos edge.
  • Resultado del diagnóstico: Necesitamos ejecutar el juego y analizar los logs para determinar si el problema es:
    • El juego nunca intenta encender el LCD (problema de CPU/interrupciones/timer)
    • El juego intenta encender el LCD pero falla (problema de PPU/LCDC)
    • El juego enciende el LCD pero la pantalla sigue azul (problema de renderizado/paleta)

Hipótesis y Suposiciones

Hipótesis principal: El juego está colgado esperando una interrupción del Timer que nunca llega debido al bug en la lógica de overflow que acabamos de corregir. Con esta corrección, esperamos que el Timer genere interrupciones correctamente y el juego pueda avanzar hasta el punto de encender el LCD.

Suposición sobre el diagnóstico: Si después de corregir el Timer el juego sigue sin intentar encender el LCD, el problema puede estar en:

  • Las interrupciones no se están procesando correctamente (IME, despachador de interrupciones)
  • El juego está esperando otra condición (joypad, STAT, etc.)
  • Hay otro bug en la CPU que impide que el código avance

Próximos Pasos

  • [ ] Ejecutar pkmn.gb y analizar los logs de consola para ver si aparece el mensaje de trampa de LCDC
  • [ ] Si aparece el mensaje con un cambio de 0x00 a un valor con bit 7 activo, verificar que la PPU reaccione correctamente al cambio de LCDC
  • [ ] Si no aparece ningún mensaje de LCDC, añadir más trampas de diagnóstico (IF, IE, TIMA, TAC) para rastrear el estado de interrupciones y timer
  • [ ] Verificar que los tests existentes del Timer sigan pasando después de la corrección
  • [ ] Si es necesario, crear tests específicos para validar la generación de interrupciones del Timer en overflow