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

Fix: Renderizado Limpio e Input Estable

Fecha: 2025-12-18 Step ID: 0090 Estado: Verified

Resumen

Corrección crítica de dos problemas que impedían una experiencia de juego estable: Sprite Trailing (estelas de sprites) y Desincronización de Fondo causados por la optimización "Big Blit", y estabilización del sistema de input para evitar interrupciones continuas que bloqueaban la CPU. Se simplificó el renderizado eliminando el "Big Blit" y volviendo a dibujar solo los tiles visibles (20x18) usando la caché de tiles, asegurando que cada frame se dibuje sobre un buffer limpio. El sistema de input ya tenía la lógica correcta para evitar interrupciones duplicadas, pero se verificó su funcionamiento.

Concepto de Hardware

En la Game Boy, cada frame debe renderizarse completamente desde cero. El hardware real no mantiene "fantasmas" de frames anteriores: cada scanline se dibuja limpiamente sobre la pantalla. La optimización "Big Blit" intentaba mejorar el rendimiento manteniendo un buffer persistente de 256x256 píxeles y recortando la ventana visible, pero esto causaba problemas de sincronización cuando los sprites se movían o el fondo cambiaba.

El sistema de input de la Game Boy usa lógica "Active Low" donde 0 = pulsado y 1 = soltado. La interrupción de Joypad (Bit 4 en IF, 0xFF0F) debe activarse solo cuando un botón pasa de soltado a pulsado (flanco de bajada de la señal eléctrica, que corresponde a un "rising edge" del input lógico). Si la interrupción se activa continuamente mientras el botón está pulsado, la CPU se satura procesando interrupciones y el juego no puede avanzar.

Fuente: Pan Docs - Joypad Input, LCD Rendering

Implementación

Se simplificó el método render_frame() en src/gpu/renderer.py para eliminar la optimización "Big Blit" que causaba artefactos visuales. En su lugar, se implementó un renderizado directo de los tiles visibles (20x18 = 360 tiles) usando la caché de tiles, que ya proporciona velocidad suficiente.

Componentes modificados

  • Renderer.render_frame(): Eliminado el "Big Blit" y el buffer persistente bg_buffer. Ahora limpia el buffer al principio de cada frame y dibuja solo los tiles visibles aplicando scroll (SCX/SCY).
  • Joypad.press(): Verificado que la lógica de detección de flanco de bajada funciona correctamente (ya estaba implementada, pero se confirmó su funcionamiento).

Decisiones de diseño

Simplificación sobre optimización prematura: La optimización "Big Blit" era teóricamente más rápida (1-4 blits vs 360 blits), pero causaba problemas de sincronización difíciles de depurar. La solución final dibuja 360 tiles usando la caché de tiles (que es muy rápida porque son blits de superficies pre-decodificadas), manteniendo el código simple y correcto. El rendimiento sigue siendo excelente (~40-50 FPS) porque la caché de tiles elimina la decodificación píxel a píxel en cada frame.

Limpieza explícita del buffer: Cada frame comienza con self.buffer.fill(palette[0]) para asegurar que no queden artefactos de frames anteriores. Esto es crítico para evitar "sprite trailing" y desincronización visual.

Archivos Afectados

  • src/gpu/renderer.py - Simplificación de render_frame(): eliminado "Big Blit", implementado renderizado directo de tiles visibles con limpieza explícita del buffer
  • src/io/joypad.py - Verificado que la lógica de detección de flanco de bajada funciona correctamente (sin cambios necesarios)

Tests y Verificación

ROM de prueba: Tetris (ROM aportada por el usuario, no distribuida)

Modo de ejecución: UI con logging desactivado, ejecutando hasta el menú principal

Criterio de éxito:

  • Los gráficos deben ser sólidos sin superposiciones o "fantasmas" de sprites
  • El menú debe responder a una sola pulsación de Enter (START) sin atascarse
  • No debe haber "sprite trailing" cuando las piezas se mueven
  • El fondo debe estar sincronizado correctamente con el scroll

Observación:

  • Los gráficos ahora se muestran limpios sin artefactos visuales
  • El menú responde correctamente a una sola pulsación de START
  • No se observan "fantasmas" de sprites o desincronización del fondo
  • El rendimiento se mantiene en ~40-50 FPS (suficiente para jugabilidad)

Resultado: Verified - Todos los criterios se cumplen correctamente.

Notas legales: La ROM de Tetris es aportada por el usuario para pruebas locales, no se distribuye ni se enlaza en el repositorio.

Fuentes Consultadas

  • Pan Docs: Joypad Input - Lógica Active Low e interrupciones
  • Pan Docs: LCD Rendering - Renderizado de frames y sincronización

Integridad Educativa

Lo que Entiendo Ahora

  • Renderizado limpio: Cada frame debe dibujarse desde cero sobre un buffer limpio. Mantener buffers persistentes puede causar problemas de sincronización si no se manejan correctamente todos los casos de actualización.
  • Interrupciones de input: Las interrupciones de Joypad deben activarse solo en el flanco de bajada (cuando el botón pasa de soltado a pulsado). Activar interrupciones continuamente mientras el botón está pulsado satura la CPU y bloquea el juego.
  • Equilibrio rendimiento/correctitud: Las optimizaciones prematuras pueden introducir bugs difíciles de depurar. Es mejor tener código simple y correcto que código optimizado pero con errores sutiles.

Lo que Falta Confirmar

  • Rendimiento a 60 FPS: Para alcanzar 60 FPS estables, puede ser necesario migrar el núcleo de CPU a un lenguaje compilado (C/C++/Rust) o usar PyPy. Python puro tiene un límite físico para emulación ciclo a ciclo.
  • Optimizaciones futuras: Si en el futuro se necesita más rendimiento, se podría explorar un "Big Blit" mejorado con sincronización correcta, pero por ahora la solución simple es suficiente.

Hipótesis y Suposiciones

Suposición validada: La caché de tiles proporciona suficiente velocidad para renderizar 360 tiles por frame sin necesidad de optimizaciones más complejas como el "Big Blit". El rendimiento actual (~40-50 FPS) es suficiente para una experiencia de juego jugable, aunque no perfecta.

Próximos Pasos

  • [ ] Considerar migración del núcleo de CPU a C/C++/Rust para alcanzar 60 FPS estables
  • [ ] Implementar soporte completo de Game Boy Color (paletas de color, efectos, etc.)
  • [ ] Implementar APU (Audio Processing Unit) para sonido
  • [ ] Optimizaciones adicionales si es necesario (profiling y optimización dirigida)