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
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 conframe_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 queframe_indicesse guarda enself._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
- Python Documentation: Instance Objects - Scope de variables de instancia
- Python Documentation: Scopes and Namespaces - Scope de variables en Python
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
returnnunca se ejecuta. Si se necesita ejecutar código después de una operación (comopygame.display.flip()), debe estar antes delreturn. - 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