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.
Calibración de Precisión: Ajuste Fino del Bucle Principal
Resumen
Se ajustó el bucle principal del emulador para mejorar la precisión de sincronización entre la CPU, el Timer y las Interrupciones. Se redujo el tamaño del batch de 456 a 64 T-Cycles y se eliminó el frame skip (de 2 a 0) para lograr una experiencia de juego más precisa y suave. Estos cambios solucionan problemas de Game Over aleatorio en Tetris (causado por RNG basado en Timer) y lag en los controles.
Concepto de Hardware
En una Game Boy real, todos los subsistemas (CPU, PPU, Timer, Interrupciones) están sincronizados por el mismo reloj del sistema (4.194304 MHz). Cada instrucción de la CPU consume un número específico de M-Cycles (Machine Cycles), que se convierten a T-Cycles (T-Cycles = M-Cycles × 4) para sincronizar con la PPU y el Timer.
El problema del batching agresivo: Cuando agrupamos demasiados ciclos (456 T-Cycles = 1 scanline completa), la CPU ejecuta muchas instrucciones antes de actualizar el Timer y las Interrupciones. Esto causa:
- Desincronización del Timer: Juegos como Tetris usan el registro DIV (Timer) para generar números aleatorios. Si el Timer no se actualiza con suficiente frecuencia, el RNG genera valores incorrectos, causando piezas inválidas y Game Over.
- Lag en Interrupciones: Las interrupciones (VBlank, Timer, Joypad) se procesan tarde, causando retraso en la respuesta a los controles.
- Glitches visuales: El frame skip hace que el renderizado se vea entrecortado, perdiendo suavidad visual.
La solución: Reducir el tamaño del batch a 64 T-Cycles (~16 M-Cycles) mantiene un buen rendimiento (reduce overhead de Python) pero es lo suficientemente pequeño para que el Timer y las Interrupciones se actualicen con precisión. Eliminar el frame skip (SKIP_FRAMES = 0) restaura la suavidad visual a 60 FPS reales.
Fuente: Pan Docs - System Clock, Timing, Timer, Interrupciones
Implementación
Se modificaron dos constantes en el método run() de la clase Viboy
en src/viboy.py:
Cambio 1: Reducción del Batch Size
Antes: BATCH_SIZE_T_CYCLES = 456 (1 scanline completa)
Después: BATCH_SIZE_T_CYCLES = 64 (~16 M-Cycles)
Esto reduce el tamaño del batch de 456 a 64 T-Cycles, haciendo que el Timer y las Interrupciones se actualicen aproximadamente 7 veces más frecuentemente. El overhead de Python sigue siendo bajo (menos llamadas a función que sin batching), pero la precisión mejora significativamente.
Cambio 2: Eliminación del Frame Skip
Antes: SKIP_FRAMES = 2 (renderizar 1 de cada 3 frames)
Después: SKIP_FRAMES = 0 (renderizar todos los frames)
Esto elimina el frame skip, restaurando el renderizado a 60 FPS reales. La lógica del juego ya corría a 60Hz, solo se saltaba el dibujo visual. Ahora se renderiza cada frame para máxima suavidad.
Componentes modificados
src/viboy.py: Métodorun()- Ajuste de constantes de rendimiento (BATCH_SIZE_T_CYCLES y SKIP_FRAMES)
Decisiones de diseño
¿Por qué 64 T-Cycles? Es un balance entre rendimiento y precisión:
- 64 T-Cycles = ~16 M-Cycles, suficiente para reducir overhead de Python
- Es 7 veces más pequeño que 456, mejorando la precisión del Timer
- Mantiene el batching (no volvemos a actualizar por instrucción), preservando rendimiento
¿Por qué eliminar frame skip? Queremos verificar si el PC aguanta 60 FPS reales sin saltar cuadros. Si va lento, se puede subir a 1, pero empezamos con calidad máxima para evaluar el rendimiento real.
Archivos Afectados
src/viboy.py- Modificación de constantes BATCH_SIZE_T_CYCLES (456 → 64) y SKIP_FRAMES (2 → 0) en el métodorun()
Tests y Verificación
Estado: Pendiente de verificación con ROM de Tetris.
Prueba de Fuego (Tetris):
- ROM: Tetris (ROM aportada por el usuario, no distribuida)
- Modo de ejecución: UI con pygame, sin frame skip, batch size 64
- Criterio de éxito:
- Las piezas rotan correctamente (sin Game Over aleatorio)
- Los controles responden sin lag
- El movimiento se ve fluido (60 FPS reales)
- El juego permite jugar sin matarse solo
- Observación: Pendiente de ejecutar
python main.py tetris.gbpara verificar que los cambios solucionan los problemas de precisión. - Resultado: Draft - Pendiente de verificación
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: System Clock, Timing, Timer, Interrupciones
- Implementación basada en análisis del comportamiento del Timer y su uso en RNG de juegos
Integridad Educativa
Lo que Entiendo Ahora
- Batching vs Precisión: El batching reduce overhead de Python agrupando instrucciones, pero un batch demasiado grande desincroniza los subsistemas. El tamaño óptimo depende del balance entre rendimiento y precisión requerida.
- Timer y RNG: Juegos como Tetris usan el registro DIV (Timer) como semilla para generación de números aleatorios. Si el Timer no se actualiza con suficiente frecuencia, el RNG genera valores incorrectos, causando comportamiento errático del juego.
- Frame Skip: El frame skip reduce la carga de renderizado saltando cuadros, pero sacrifica suavidad visual. Para una experiencia Game Boy 100% real, es preferible renderizar todos los frames si el hardware lo permite.
Lo que Falta Confirmar
- Rendimiento real: Verificar si el PC aguanta 60 FPS reales con batch size 64 y sin frame skip. Si va lento, ajustar SKIP_FRAMES a 1.
- Precisión del Timer: Confirmar que el batch size 64 es suficiente para que Tetris genere piezas correctamente sin Game Over aleatorio.
- Lag de controles: Verificar que los inputs responden sin retraso perceptible con el nuevo batch size.
Hipótesis y Suposiciones
Hipótesis: Un batch size de 64 T-Cycles es suficiente para mantener la precisión del Timer y las Interrupciones mientras preserva el rendimiento del batching. Esta hipótesis se basa en que 64 es aproximadamente 7 veces más pequeño que 456, mejorando significativamente la frecuencia de actualización de periféricos.
Suposición: El PC del usuario tiene suficiente potencia para renderizar a 60 FPS reales sin frame skip. Si no es así, se ajustará SKIP_FRAMES a 1 como compromiso.
Próximos Pasos
- [ ] Ejecutar
python main.py tetris.gby verificar que los cambios solucionan los problemas de precisión - [ ] Si el rendimiento es insuficiente con SKIP_FRAMES=0, ajustar a 1 como compromiso
- [ ] Si el batch size 64 sigue causando problemas, reducir a 32 o 16 T-Cycles (aunque esto aumentará el overhead de Python)
- [ ] Documentar el batch size óptimo encontrado para futuras referencias