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.
Corrección de Arquitectura del Game Loop
Resumen
Se corrigió la arquitectura del bucle principal (Game Loop) del emulador. El problema crítico era que clock.tick(60) estaba dentro del bucle que ejecuta una instrucción por iteración, limitando la ejecución a 60 instrucciones por segundo en lugar de ~4 millones. La solución fue reestructurar el bucle en dos niveles: un bucle externo por frame (60 FPS) y un bucle interno que ejecuta todas las instrucciones necesarias para completar un frame (~70,224 T-Cycles). El control de FPS y la gestión de eventos ahora se ejecutan una vez por frame, fuera del bucle interno de instrucciones.
Concepto de Hardware
La Game Boy funciona a 4.194304 MHz (4.194.304 ciclos por segundo). Un fotograma (frame) dura aproximadamente 70.224 ciclos de reloj para mantener 59.7 FPS. El bucle principal de un emulador debe ejecutar todas las instrucciones necesarias para completar un frame antes de sincronizar con el tiempo real.
Arquitectura correcta del Game Loop:
- Bucle externo: Por cada frame (60 FPS). Aquí se gestionan eventos y se sincroniza el tiempo.
- Bucle interno: Ejecuta todas las instrucciones necesarias para completar un frame (~70,224 T-Cycles). La CPU ejecuta instrucciones continuamente hasta que la PPU indica que un frame está listo.
Si el control de FPS (clock.tick(60)) se coloca dentro del bucle de instrucciones, se limita la ejecución a 60 instrucciones por segundo, lo que resulta en un rendimiento extremadamente lento (6000 veces más lento de lo normal).
Fuente: Pan Docs - System Clock, Timing, Frame Rate
Implementación
Se reestructuró el método run() en src/viboy.py para separar claramente el bucle externo (por frame) del bucle interno (instrucciones).
Estructura del bucle corregido:
# BUCLE EXTERNO: Por cada frame (60 FPS)
while True:
# 1. Gestionar eventos (una vez por frame)
pygame.event.pump()
handle_pygame_events()
# 2. BUCLE INTERNO: Ejecutar un frame completo de CPU/PPU
frame_cycles = 0
frame_rendered = False
max_cycles_per_frame = CYCLES_PER_FRAME * 4 * 2 # Límite de seguridad
while not frame_rendered and frame_cycles < max_cycles_per_frame:
# Ejecutar una instrucción
cycles = self.tick()
frame_cycles += cycles * 4
# Si la PPU indica que un frame está listo, renderizar
if self._ppu.is_frame_ready():
frame_rendered = True
self._renderer.render_frame()
# 3. Control de FPS - FUERA del bucle interno
self._clock.tick(60) # Una vez por frame, no por instrucción
Componentes modificados
src/viboy.py: Métodorun()completamente reestructurado con bucle externo/interno separado.
Decisiones de diseño
- Límite de seguridad: Se añadió un límite máximo de 2 frames de ciclos para evitar bucles infinitos si la PPU nunca indica que un frame está listo.
- Renderizado periódico: Si no se renderiza un frame (LCD apagado), se mantiene el renderizado periódico para el heartbeat visual.
- Eventos una vez por frame: La gestión de eventos de Pygame se ejecuta una vez por frame, no por instrucción, para mejorar el rendimiento.
Archivos Afectados
src/viboy.py- Reestructuración completa del métodorun()con arquitectura de bucle externo/interno
Tests y Verificación
Verificación pendiente: Esta corrección debe probarse ejecutando el emulador con una ROM y verificando que:
- El contador de ciclos en el log pase de ~169 ciclos/segundo a ~4,000,000 ciclos/segundo
- El registro LY avance rápidamente (de 0 a 144 en milisegundos, no en minutos)
- El juego se ejecute a velocidad real (60 FPS)
- El monitor de signos vitales muestre ciclos acumulándose correctamente
Estado: Draft - Pendiente de verificación con ejecución real del emulador.
Fuentes Consultadas
- Pan Docs: System Clock, Timing, Frame Rate
- Conocimiento general de arquitectura de emuladores y game loops
Integridad Educativa
Lo que Entiendo Ahora
- Arquitectura del Game Loop: Un emulador necesita separar claramente la ejecución de instrucciones (bucle interno) de la sincronización con el tiempo real (bucle externo). El control de FPS debe estar fuera del bucle de instrucciones.
- Rendimiento: Colocar
clock.tick(60)dentro del bucle de instrucciones limita la ejecución a 60 instrucciones por segundo, lo que resulta en un rendimiento extremadamente lento (6000 veces más lento de lo normal). - Timing de frames: Un frame de Game Boy dura ~70,224 T-Cycles. El bucle interno debe ejecutar todas las instrucciones necesarias para completar un frame antes de sincronizar con el tiempo real.
Lo que Falta Confirmar
- Verificación de rendimiento: Necesito ejecutar el emulador y verificar que el contador de ciclos muestre ~4 millones de ciclos por segundo en lugar de ~169.
- Velocidad de LY: Verificar que LY avance rápidamente (de 0 a 144 en milisegundos).
- FPS real: Verificar que el juego se ejecute a 60 FPS reales.
Hipótesis y Suposiciones
Asumo que la reestructuración del bucle resolverá el problema de rendimiento extremo. Sin embargo, es posible que haya otros factores que afecten el rendimiento (por ejemplo, operaciones costosas en el renderizado o en la gestión de eventos). La verificación real con ejecución del emulador confirmará si esta corrección es suficiente.
Próximos Pasos
- [ ] Ejecutar el emulador con una ROM y verificar que el rendimiento sea correcto (~4 millones de ciclos/segundo)
- [ ] Verificar que LY avance rápidamente (de 0 a 144 en milisegundos)
- [ ] Verificar que el juego se ejecute a 60 FPS reales
- [ ] Si el rendimiento sigue siendo lento, investigar otros factores (renderizado, gestión de eventos, etc.)