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
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 buffersrc/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)