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

Corrección de Bug de Scope en Verificación de Pantalla

Fecha: 2025-12-29 Step ID: 0350 Estado: VERIFIED

Resumen

Se corrigió un bug de scope en el código de verificación de actualización de pantalla. El problema era que frame_indices se definía dentro del bloque condicional if self.use_cpp_ppu and self.cpp_ppu is not None:, pero el código de verificación de pantalla estaba fuera de ese bloque (después de pygame.display.flip()), por lo que frame_indices no estaba disponible cuando se ejecutaba la verificación. La solución fue guardar frame_indices en una variable de instancia (self._current_frame_indices) cuando se obtiene, y actualizar el código de verificación para usar esta variable de instancia. Además, se movió el código de verificación de pantalla dentro del bloque de PPU C++ (antes del return) para que se ejecute cuando se usa PPU C++.

Concepto de Hardware

Scope de Variables en Python: Las variables definidas dentro de un bloque condicional (if, else, try, except) solo están disponibles dentro de ese bloque. Para usar una variable fuera del bloque donde se define, se debe: (1) guardarla en una variable de instancia (self.variable), (2) definirla antes del bloque condicional, o (3) pasar la variable como parámetro. En este caso, frame_indices se definía dentro del bloque if self.use_cpp_ppu and self.cpp_ppu is not None:, pero el código de verificación de pantalla estaba fuera de ese bloque, por lo que frame_indices no estaba disponible cuando se ejecutaba la verificación.

Actualización de Pantalla: pygame.display.flip() actualiza la pantalla con el contenido del buffer. Debe llamarse después de dibujar todo el contenido. El contenido en pantalla debe coincidir con el framebuffer dibujado. Para verificar que la pantalla se actualiza correctamente, se debe leer el contenido de la pantalla después de flip() y compararlo con el framebuffer original. Si el código de verificación está después de un return, nunca se ejecutará.

Implementación

Se implementaron tres cambios principales para corregir el bug de scope:

1. Guardar frame_indices en Variable de Instancia

Se agregó código para guardar frame_indices en self._current_frame_indices cuando se obtiene, tanto cuando se proporciona framebuffer_data como parámetro como cuando se obtiene desde PPU C++.

# --- Step 0350: Guardar frame_indices en Variable de Instancia ---
# Guardar frame_indices en una variable de instancia para que esté disponible en todo el método
self._current_frame_indices = frame_indices
# --- Step 0350: Log de Guardado de frame_indices ---
if not hasattr(self, '_frame_indices_saved_count'):
    self._frame_indices_saved_count = 0

self._frame_indices_saved_count += 1

if self._frame_indices_saved_count <= 10:
    logger.info(f"[Renderer-Frame-Indices-Saved] Frame {self._frame_indices_saved_count} | "
               f"frame_indices guardado en self._current_frame_indices (length={len(self._current_frame_indices)})")
    print(f"[Renderer-Frame-Indices-Saved] Frame {self._frame_indices_saved_count} | "
          f"frame_indices guardado en self._current_frame_indices (length={len(self._current_frame_indices)})")
# -------------------------------------------

2. Actualizar Código de Verificación de Pantalla

Se actualizó el código de verificación de pantalla para usar self._current_frame_indices en lugar de verificar frame_indices en locals(). Además, se movió el código de verificación dentro del bloque de PPU C++ (antes del return) para que se ejecute cuando se usa PPU C++.

# --- Step 0348: Verificación de Actualización de Pantalla (dentro del bloque PPU C++) ---
# Verificar que la pantalla se actualiza correctamente después de flip()
# NOTA: Este código debe ejecutarse ANTES del return para que se ejecute cuando se usa PPU C++
if hasattr(self, 'screen') and self.screen is not None and \
   hasattr(self, '_current_frame_indices') and self._current_frame_indices is not None and \
   self._screen_update_check_count < 10:
    self._screen_update_check_count += 1
    
    # Verificar algunos píxeles en la pantalla después de flip()
    test_pixels = [(0, 0), (80, 72), (159, 143)]
    
    logger.info(f"[Renderer-Screen-Update] Frame {self._screen_update_check_count} | "
               f"Verificando pantalla después de flip():")
    print(f"[Renderer-Screen-Update] Frame {self._screen_update_check_count} | "
          f"Verificando pantalla después de flip():")
    
    # Usar self._current_frame_indices en lugar de frame_indices
    frame_indices = self._current_frame_indices
    
    for x, y in test_pixels:
        # Calcular posición escalada
        scale_x = int(x * self.screen.get_width() / 160)
        scale_y = int(y * self.screen.get_height() / 144)
        
        # Color en la pantalla después de flip()
        if scale_x < self.screen.get_width() and scale_y < self.screen.get_height():
            screen_color = self.screen.get_at((scale_x, scale_y))
            
            # Obtener índice original del framebuffer
            idx = y * 160 + x
            if idx < len(frame_indices):
                framebuffer_idx = frame_indices[idx] & 0x03
                expected_rgb = palette[framebuffer_idx]
                
                logger.info(f"[Renderer-Screen-Update] Pixel ({x}, {y}): "
                           f"Framebuffer index={framebuffer_idx}, Expected RGB={expected_rgb}, "
                           f"Screen RGB={screen_color}")
                print(f"[Renderer-Screen-Update] Pixel ({x}, {y}): "
                      f"Framebuffer index={framebuffer_idx}, Expected RGB={expected_rgb}, "
                      f"Screen RGB={screen_color}")
                
                # Verificar que los colores coinciden (tolerancia para interpolación)
                if abs(screen_color[0] - expected_rgb[0]) > 10 or \
                   abs(screen_color[1] - expected_rgb[1]) > 10 or \
                   abs(screen_color[2] - expected_rgb[2]) > 10:
                    logger.warning(f"[Renderer-Screen-Update] ⚠️ Color en pantalla no coincide!")
                    print(f"[Renderer-Screen-Update] ⚠️ Color en pantalla no coincide!")
# -------------------------------------------

3. Agregar Logs de Depuración

Se agregaron logs de depuración para verificar que el código de verificación se ejecuta correctamente y para diagnosticar problemas de scope.

Archivos Afectados

  • src/gpu/renderer.py - Corrección de bug de scope con frame_indices, guardado en variable de instancia, actualización de código de verificación de pantalla, y movimiento del código de verificación dentro del bloque de PPU C++

Tests y Verificación

Se ejecutó una prueba rápida (10 segundos) con Tetris para verificar que la corrección funciona correctamente:

  • Logs de guardado: Los logs de [Renderer-Frame-Indices-Saved] aparecen correctamente, confirmando que frame_indices se guarda en self._current_frame_indices
  • Logs de verificación de pantalla: Los logs de [Renderer-Screen-Update] aparecen correctamente, confirmando que el código de verificación se ejecuta
  • Correspondencia de colores: Los logs muestran que los colores en pantalla coinciden con el framebuffer (no hay advertencias de "Color en pantalla no coincide")
# Ejemplo de logs exitosos:
[Renderer-Frame-Indices-Saved] Frame 1 | frame_indices guardado en self._current_frame_indices (length=23040)
[Renderer-Screen-Update-Entry] Frame 1 | Entrando a verificación de pantalla
[Renderer-Screen-Update-Debug] Frame 1 | has_screen=True, has_frame_indices=True, check_count=0
[Renderer-Screen-Update] Frame 1 | Verificando pantalla después de flip():
[Renderer-Screen-Update] Pixel (0, 0): Framebuffer index=3, Expected RGB=(8, 24, 32), Screen RGB=Color(8, 24, 32, 255)
[Renderer-Screen-Update] Pixel (80, 72): Framebuffer index=0, Expected RGB=(255, 255, 255), Screen RGB=Color(255, 255, 255, 255)
[Renderer-Screen-Update] Pixel (159, 143): Framebuffer index=0, Expected RGB=(255, 255, 255), Screen RGB=Color(255, 255, 255, 255)

Validación de módulo compilado C++: El código funciona correctamente con el módulo C++ compilado, confirmando que la corrección de scope no afecta la funcionalidad del renderizador.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Scope de Variables: Las variables definidas dentro de bloques condicionales solo están disponibles dentro de ese bloque. Para usar una variable fuera del bloque, se debe guardar en una variable de instancia o definirla antes del bloque.
  • Orden de Ejecución: El código que está después de un return nunca se ejecuta. Si se necesita ejecutar código después de una operación (como pygame.display.flip()), debe estar antes del return.
  • Variables de Instancia: Las variables de instancia (self.variable) están disponibles en todo el método, independientemente de dónde se definan. Esto las hace ideales para compartir datos entre diferentes partes del método.

Lo que Falta Confirmar

  • Pruebas completas: Se ejecutó una prueba rápida, pero no se pudieron ejecutar pruebas completas con las 5 ROMs debido a problemas de espacio en el dispositivo. Las pruebas completas deberían ejecutarse cuando haya espacio disponible.

Hipótesis y Suposiciones

Se asume que el código de verificación de pantalla funciona correctamente para todas las ROMs, basándose en que funciona correctamente para Tetris. Esta suposición debería verificarse con pruebas completas cuando haya espacio disponible.

Próximos Pasos

  • [ ] Ejecutar pruebas completas con las 5 ROMs cuando haya espacio disponible
  • [ ] Analizar los logs de verificación de pantalla para identificar cualquier problema de correspondencia entre framebuffer y pantalla
  • [ ] Si se identifican problemas, implementar correcciones basadas en los hallazgos