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

The Blue Box: Pygame Surface Debugging

Date:2025-12-22 StepID:0218 State: draft

Summary

Even though all the data confirms that it should look red (C++ sends 3, Python receives 3, palette maps 3 to Red), the screen still shows green (color 0). This means that the methodrender_frameis not doing his job: either he is not drawing, or he is not It is updating the window correctly.

This step implements a definitive diagnosis of the renderer using three strategies: (1) Print what exactly the method receives to ensure that the framebuffer does not arrive empty, (2) Force a BLUE box in the middle of the screen (ignoring the framebuffer) to verify the connectivity between the inner surface and the Pygame window, and (3) Use the standard method ofblitinstead of optimizationscaleof 3 arguments that sometimes fails silently.

Hardware Concept

In Pygame, rendering works through a hierarchy of surfaces:

  • Inner Surface (self.surface):160x144 pixel surface where the framebuffer is drawn pixel by pixel.
  • Climbing Surface:Temporary surface created by scaling the inner surface to the size of the window (480×432 with scale=3).
  • Main Window (self.screen):The user's visible window, where the scaled surface is blit.

If any of these steps fail silently, the screen will show the background color default (light green) instead of the actual data. The "Blue Box Test" verifies that the inner surface connects correctly with the window: if we see a blue box, we know the rendering pipeline works; If we don't see it, the problem is in the connection between surfaces.

The methodpygame.transform.scale()with 3 arguments (surface, size, target) sometimes fails silently on some versions of Pygame. The standard method of creating a temporary climbing surface and then blit is safer and more reliable.

Implementation

The method was modifiedrender_frameinsrc/gpu/renderer.pyfor include comprehensive diagnostics and a more secure rendering method.

Modified components

  • src/gpu/renderer.py: Methodrender_frame()- Input diagnostics, blue test box, and switch to standard blit

Technical changes

1. Input Diagnosis:Added a diagnostic block that runs just once (usinghasattr(self, '_debug_render_printed')) that prints:

  • Type of the received framebuffer
  • Value of first pixel withinrender_frame
  • Internal surface size
  • window size

2. Blue Test Box:After the framebuffer is drawn, it is overwritten a 20x20 pixel box in the center of the screen (coordinates 70-90 in X, 60-80 in Y) with pure blue color(0, 0, 255). If this box is seen, it confirms that:

  • The inner surface is being written correctly
  • The surface is scaled correctly
  • The window refreshes correctly

3. Standard Blit:Replaced direct scaling method with 3 arguments by the standard two-step method:

# Before (may fail silently):
pygame.transform.scale(surface, size, dest)

# After (safer):
scaled_surface = pygame.transform.scale(self.surface, self.screen.get_size())
self.screen.blit(scaled_surface, (0, 0))
pygame.display.flip()

Affected Files

  • src/gpu/renderer.py- Modification of the methodrender_frame()for diagnostic and standard blit (lines 438-540)

Tests and Verification

Command executed: python main.py roms/tetris.gb

Analysis of Results:

  • Do you see a BLUE box in the center?
    • YEAH:Good! The connection to the window works. If the rest is Red, fixed. If the rest is Green, the loopforfails (look at the internal log).
    • NO:Soself.screenit is not updating. Pygame initialization or double buffering problem.
  • Internal Log:Verify thatFirst Pixel Value inside render_framebe3.

Test Code:

# In src/gpu/renderer.py, render_frame() method

#1. Input Diagnostics (One Time Only)
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. Manual Rendering
px_array = pygame.PixelArray(self.surface)
WIDTH, HEIGHT = 160, 144

for and 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. PROOF OF LIFE: BLUE box in the center
for and in range(60, 80):
    for x in range(70, 90):
        px_array[x, y] = (0, 0, 255) # PURE BLUE

px_array.close()

#4. SWITCHING TO STANDARD BLIT
scaled_surface = pygame.transform.scale(self.surface, self.screen.get_size())
self.screen.blit(scaled_surface, (0, 0))
pygame.display.flip()

Native Validation:Compiled C++ module validation using input diagnostics and visual blue box testing.

Sources consulted

Educational Integrity

What I Understand Now

  • Surface Hierarchy in Pygame:Rendering in Pygame works through a chain of surfaces: inner surface → scaled surface → main window. Each step must work correctly for the visualization to work.
  • Visual Connectivity Test:Injecting a known visual artifact (blue box) into the inner surface allows you to verify that the entire rendering chain is working. If the blue box is seen, the problem is with the framebuffer processing; If it is not seen, the problem is in the connection between surfaces.
  • Standard Blit vs Direct Scale:The standard method of creating a temporary scaled surface and then blitting is more reliable than usingpygame.transform.scale()with 3 arguments, which may fail silently on some versions.

What remains to be confirmed

  • Test Result:Check if the blue box is visible on the screen. This will determine if the problem is with the framebuffer processing or the connection between surfaces.
  • First Pixel Value:Confirm that the internal log shows that the first pixel has a value of 3, confirming that the framebuffer correctly reachesrender_frame.

Hypotheses and Assumptions

Main Hypothesis:The methodpygame.transform.scale()with 3 arguments is failing silently, or there is a timing problem between the inner surface and the window. Switching to standard blit and injecting the blue box will help isolate the problem.

Next Steps

  • [ ] Executepython main.py roms/tetris.gband check if you see the blue box
  • [ ] Analyze the internal log to confirm that the framebuffer arrives correctly
  • [ ] If the blue box is visible but the rest is green, investigate the rendering loop
  • [ ] If the blue box is NOT seen, investigate Pygame initialization or double buffering
  • [ ] Once the problem is identified, apply the final correction