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.
La Caja Azul: Debugging de Pygame Surface
Resumen
A pesar de que todos los datos confirman que debería verse rojo (C++ envía 3, Python recibe 3,
la paleta mapea 3 a Rojo), la pantalla sigue mostrando verde (color 0). Esto significa que
el método render_frame no está haciendo su trabajo: o no está dibujando, o no
está actualizando la ventana correctamente.
Este paso implementa un diagnóstico definitivo del renderizador mediante tres estrategias:
(1) Imprimir qué recibe exactamente el método para asegurar que el framebuffer no llega vacío,
(2) Forzar un cuadro AZUL en la mitad de la pantalla (ignorando el framebuffer) para verificar
la conectividad entre la superficie interna y la ventana de Pygame, y (3) Usar el método estándar
de blit en lugar de la optimización de scale de 3 argumentos que a veces
falla silenciosamente.
Concepto de Hardware
En Pygame, el renderizado funciona mediante una jerarquía de superficies:
- Superficie Interna (self.surface): Superficie de 160×144 píxeles donde se dibuja el framebuffer píxel a píxel.
- Superficie Escalada: Superficie temporal creada al escalar la superficie interna al tamaño de la ventana (480×432 con scale=3).
- Ventana Principal (self.screen): La ventana visible del usuario, donde se hace blit de la superficie escalada.
Si cualquiera de estos pasos falla silenciosamente, la pantalla mostrará el color de fondo por defecto (verde claro) en lugar de los datos reales. El "Test de la Caja Azul" verifica que la superficie interna se conecta correctamente con la ventana: si vemos un cuadro azul, sabemos que el pipeline de renderizado funciona; si no lo vemos, el problema está en la conexión entre superficies.
El método pygame.transform.scale() con 3 argumentos (superficie, tamaño, destino)
a veces falla silenciosamente en algunas versiones de Pygame. El método estándar de crear
una superficie escalada temporal y luego hacer blit es más seguro y confiable.
Implementación
Se modificó el método render_frame en src/gpu/renderer.py para
incluir diagnóstico exhaustivo y un método de renderizado más seguro.
Componentes modificados
src/gpu/renderer.py: Métodorender_frame()- Diagnóstico de entrada, cuadro azul de prueba, y cambio a blit estándar
Cambios técnicos
1. Diagnóstico de Entrada: Se añadió un bloque de diagnóstico que se ejecuta
una sola vez (usando hasattr(self, '_debug_render_printed')) que imprime:
- Tipo del framebuffer recibido
- Valor del primer píxel dentro de
render_frame - Tamaño de la superficie interna
- Tamaño de la ventana
2. Cuadro Azul de Prueba: Después de dibujar el framebuffer, se sobrescribe
un cuadro de 20×20 píxeles en el centro de la pantalla (coordenadas 70-90 en X, 60-80 en Y)
con color azul puro (0, 0, 255). Si este cuadro se ve, confirma que:
- La superficie interna se está escribiendo correctamente
- La superficie se escala correctamente
- La ventana se actualiza correctamente
3. Blit Estándar: Se reemplazó el método de escalado directo con 3 argumentos por el método estándar de dos pasos:
# Antes (puede fallar silenciosamente):
pygame.transform.scale(surface, size, dest)
# Después (más seguro):
scaled_surface = pygame.transform.scale(self.surface, self.screen.get_size())
self.screen.blit(scaled_surface, (0, 0))
pygame.display.flip()
Archivos Afectados
src/gpu/renderer.py- Modificación del métodorender_frame()para diagnóstico y blit estándar (líneas 438-540)
Tests y Verificación
Comando ejecutado: python main.py roms/tetris.gb
Análisis de Resultados:
-
¿Ves un cuadro AZUL en el centro?
- SÍ: ¡Bien! La conexión con la ventana funciona. Si el resto es Rojo, arreglado. Si el resto es Verde, el bucle
forfalla (mira el log interno). - NO: Entonces
self.screenno se está actualizando. Problema de Pygame initialization o doble buffering.
- SÍ: ¡Bien! La conexión con la ventana funciona. Si el resto es Rojo, arreglado. Si el resto es Verde, el bucle
-
Log Interno: Verifica que
First Pixel Value inside render_framesea3.
Código del Test:
# En src/gpu/renderer.py, método render_frame()
# 1. Diagnóstico de entrada (solo una vez)
if not hasattr(self, '_debug_render_printed'):
print(f"\n--- [RENDER_FRAME INTERNAL DEBUG] ---")
print(f"Framebuffer Type: {type(frame_indices)}")
print(f"First Pixel Value inside render_frame: {frame_indices[0]}")
print(f"Surface size: {self.surface.get_size() if hasattr(self, 'surface') else 'N/A'}")
print(f"Window size: {self.screen.get_size()}")
print(f"-------------------------------------\n")
self._debug_render_printed = True
# 2. Renderizado Manual
px_array = pygame.PixelArray(self.surface)
WIDTH, HEIGHT = 160, 144
for y in range(HEIGHT):
for x in range(WIDTH):
idx = y * WIDTH + x
color_index = frame_indices[idx] & 0x03
color_rgb = palette[color_index]
px_array[x, y] = color_rgb
# 3. PRUEBA DE VIDA: Cuadro AZUL en el centro
for y in range(60, 80):
for x in range(70, 90):
px_array[x, y] = (0, 0, 255) # AZUL PURO
px_array.close()
# 4. CAMBIO A BLIT ESTÁNDAR
scaled_surface = pygame.transform.scale(self.surface, self.screen.get_size())
self.screen.blit(scaled_surface, (0, 0))
pygame.display.flip()
Validación Nativa: Validación de módulo compilado C++ mediante diagnóstico de entrada y prueba visual de cuadro azul.
Fuentes Consultadas
- Pygame Documentation: pygame.transform.scale
- Pygame Documentation: Surface.blit
- Pygame Documentation: pygame.display.flip
Integridad Educativa
Lo que Entiendo Ahora
- Jerarquía de Superficies en Pygame: El renderizado en Pygame funciona mediante una cadena de superficies: superficie interna → superficie escalada → ventana principal. Cada paso debe funcionar correctamente para que la visualización funcione.
- Test de Conectividad Visual: Inyectar un artefacto visual conocido (cuadro azul) en la superficie interna permite verificar que toda la cadena de renderizado funciona. Si el cuadro azul se ve, el problema está en el procesamiento del framebuffer; si no se ve, el problema está en la conexión entre superficies.
- Blit Estándar vs Scale Directo: El método estándar de crear una superficie escalada temporal y luego hacer blit es más confiable que usar
pygame.transform.scale()con 3 argumentos, que puede fallar silenciosamente en algunas versiones.
Lo que Falta Confirmar
- Resultado del Test: Verificar si el cuadro azul se ve en la pantalla. Esto determinará si el problema está en el procesamiento del framebuffer o en la conexión entre superficies.
- Valor del Primer Píxel: Confirmar que el log interno muestra que el primer píxel tiene valor 3, confirmando que el framebuffer llega correctamente a
render_frame.
Hipótesis y Suposiciones
Hipótesis Principal: El método pygame.transform.scale() con 3 argumentos
está fallando silenciosamente, o hay un problema de sincronización entre la superficie interna y la
ventana. El cambio a blit estándar y la inyección del cuadro azul ayudarán a aislar el problema.
Próximos Pasos
- [ ] Ejecutar
python main.py roms/tetris.gby verificar si se ve el cuadro azul - [ ] Analizar el log interno para confirmar que el framebuffer llega correctamente
- [ ] Si el cuadro azul se ve pero el resto es verde, investigar el bucle de renderizado
- [ ] Si el cuadro azul NO se ve, investigar la inicialización de Pygame o el doble buffering
- [ ] Una vez identificado el problema, aplicar la corrección definitiva