⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

Scope Bug Fix in Screen Verification

Date:2025-12-29 StepID:0350 State: VERIFIED

Summary

Fixed a scope bug in the screen refresh verification code. The problem was thatframe_indiceswas defined within the conditional blockif self.use_cpp_ppu and self.cpp_ppu is not None:, but the screen verification code was outside that block (afterpygame.display.flip()), henceframe_indiceswas not available when the check was run. The solution was to saveframe_indicesin an instance variable (self._current_frame_indices) when obtained, and update the check code to use this instance variable. Additionally, the screen verification code was moved inside the PPU C++ block (before thereturn) to run when using PPU C++.

Hardware Concept

Scope of Variables in Python:Variables defined within a conditional block (if, else, try, except) are only available within that block. To use a variable outside the block where it is defined, you must: (1) save it in an instance variable (self.variable), (2) define it before the conditional block, or (3) pass the variable as a parameter. In this case,frame_indiceswas defined within the blockif self.use_cpp_ppu and self.cpp_ppu is not None:, but the screen verification code was outside that block, soframe_indiceswas not available when the check was run.

Screen Update: pygame.display.flip()updates the screen with the contents of the buffer. It should be called after all content is drawn. The content on the screen must match the drawn framebuffer. To verify that the screen is updated correctly, the content of the screen should be read afterflip()and compare it with the original framebuffer. If the verification code is after areturn, it will never be executed.

Implementation

Three main changes were implemented to fix the scope bug:

1. Save frame_indices in Instance Variable

Added save codeframe_indicesinself._current_frame_indiceswhen it is obtained, both when it is providedframebuffer_dataas a parameter as when obtained from 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. Update Screen Verification Code

Updated the screen verification code to useself._current_frame_indicesinstead of checkingframe_indicesinlocals(). Additionally, the verification code was moved inside the PPU C++ block (before thereturn) to run when using PPU C++.

# --- Step 0348: Screen Update Verification (within the PPU C++ block) ---
# Verify that the screen updates correctly after flip()
# NOTE: This code must be executed BEFORE return to be executed when using 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 on screen does not match!")
                    print(f"[Renderer-Screen-Update] ⚠️ Color on screen does not match!")
# -------------------------------------------

3. Add Debug Logs

Added debug logs to verify that the verification code is running correctly and to diagnose scope problems.

Affected Files

  • src/gpu/renderer.py- Scope bug fixframe_indices, saved in instance variable, updating screen verification code, and moving the verification code within the PPU C++ block

Tests and Verification

A quick test (10 seconds) was run with Tetris to verify that the fix is ​​working correctly:

  • Save logs:The logs of[Renderer-Frame-Indices-Saved]appear correctly, confirming thatframe_indicesis saved inself._current_frame_indices
  • Screen verification logs:The logs of[Renderer-Screen-Update]appear correctly, confirming that the verification code is executed
  • Color correspondence:The logs show that the on-screen colors match the framebuffer (no "On-screen color mismatch" warnings)
# Example of successful logs:
[Renderer-Frame-Indices-Saved] Frame 1 | frame_indices saved in self._current_frame_indices (length=23040)
[Renderer-Screen-Update-Entry] Frame 1 | Entering screen verification
[Renderer-Screen-Update-Debug] Frame 1 | has_screen=True, has_frame_indices=True, check_count=0
[Renderer-Screen-Update] Frame 1 | Checking screen after 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++:The code works correctly with the compiled C++ module, confirming that the scope fix does not affect the functionality of the renderer.

Sources consulted

Educational Integrity

What I Understand Now

  • Scope of Variables:Variables defined within conditional blocks are only available within that block. To use a variable outside the block, it must be saved in an instance variable or defined before the block.
  • Order of Execution:The code that is after areturnit never runs. If code needs to be executed after an operation (such aspygame.display.flip()), must be beforereturn.
  • Instance Variables:The instance variables (self.variable) are available throughout the method, regardless of where they are defined. This makes them ideal for sharing data between different parts of the method.

What remains to be confirmed

  • Complete tests:A quick test was run, but full tests could not be run with all 5 ROMs due to space issues on the device. Full tests should be run when space is available.

Hypotheses and Assumptions

The screen verification code is assumed to work correctly for all ROMs, based on it working correctly for Tetris. This assumption should be verified with full testing when space is available.

Next Steps

  • [ ] Run full tests with all 5 ROMs when space is available
  • [ ] Analyze screen verification logs to identify any framebuffer-screen correspondence issues
  • [ ] If problems are identified, implement corrections based on the findings