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

La Caja Azul: Debugging de Pygame Surface

Fecha: 2025-12-22 Step ID: 0218 Estado: Draft

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étodo render_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étodo render_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 for falla (mira el log interno).
    • NO: Entonces self.screen no se está actualizando. Problema de Pygame initialization o doble buffering.
  • Log Interno: Verifica que First Pixel Value inside render_frame sea 3.

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

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.gb y 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