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
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:
- Detecta el overflow: TIMA se incrementa de 0xFF a 0x00
- Recarga TIMA: TIMA se recarga inmediatamente con el valor de TMA (Timer Modulo, 0xFF06)
- 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:
- Incrementar primero: TIMA se incrementa de 0xFF a 0x00 (overflow)
- Detectar el overflow: Si después del incremento TIMA es 0x00, significa que hubo overflow
- 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 correctamentemain.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 -> 00y 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
- Pan Docs: LCD Control Register (LCDC)
- Pan Docs: Timer and Divider Registers
- Pan Docs: Interrupts
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