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

Python Rendering Research

Date:2025-12-25 StepID:0305 State: DRAFT

Summary

Extensive investigation of the rendering code in Python to identify why green streaks appear when the PPU C++ framebuffer only contains 0 indices. Implemented 3 additional monitors to track the palette, the PixelArray and palette modifications during runtime.

Aim: Identify the root cause of the green streaks that appear after ~2 minutes of running, when the PPU C++ framebuffer only contains 0 indices. The problem is NOT in PPU C++, but in Python rendering.

Evaluated hypotheses:

  • Hypothesis A: The palette is modified during execution
  • Hypothesis B: There is other code that renders using the wrong palette
  • Hipótesis C: PixelArray or scaling issue causing visual artifacts
  • Hypothesis D: There is a palette that was not corrected in the previous steps

Hardware Concept

Rendering Flow

The rendering flow in the emulator follows these steps:

  1. PPU C++ generates framebuffer: The framebuffer contains color indices (0-3) in 1D format (23040 elements = 160x144)
  2. Python renderer get framebuffer: Zero-Copy via memoryview from PPU C++
  3. Renderer maps indices to RGB: Use a palette to convert indices (0-3) to RGB colors
  4. Renderer writes to PixelArray: Write RGB pixels to a Pygame surface (160x144)
  5. PixelArray is scaled and blit: The surface is scaled to the window (480x432) and blit to the screen

Possible Flow Problems

If the PPU C++ framebuffer only contains 0 (white) indices, but green streaks appear, the problem must be one of these points:

  • Modified palette: Palette is changed during execution, causing 0 indices to be mapped to green
  • Multiple pallets: There is another palette in use that was not corrected
  • Problems with PixelArray: PixelArray or scaling causes visual artifacts
  • Additional code: There is other code that renders using the wrong palette

Fountain: Pan Docs - "Framebuffer", "Palette", "Pixel Mapping"

Implementation

Comprehensive Pallet Search

An exhaustive search was performed for all palettes in the code:

  • Search for green values: No green values ​​found (224, 248, 208), (136, 192, 112), (52, 104, 86)
  • Finding Palette Definitions: 40 matches found, all verified and corrected

Found pallets:

  • self.COLORS(line 191): Renderer base palette - ✅ Corrected
  • debug_palette_map(lines 502, 614, 990): Debug palette - ✅ Fixed
  • palette0andpalette1(lines 999, 1005): Sprite Palettes - ✅ Fixed

Rendering Code Search

All functions and rendering operations were searched:

  • Rendering functions: 4 functions found (update_tile_cache, render_vram_debug, render_frame, render_sprites)
  • Render operations: 17 operations found (blit, fill, set_at)
  • Conclusion: There is no additional code that renders. The flow is clear and unique.

Implemented Monitors

Implemented 3 additional monitors to track rendering:

Monitor 1: [PALETTE-VERIFY]

Check the palette used in each frame:

  • Location: render_frame()after definingpalette
  • Frequency: Every 1000 frames or first 100 frames
  • Functionality: Prints the RGB values ​​of the palette (Palette[0], Palette[1], Palette[2], Palette[3])
if self._palette_verify_count % 1000 == 0 or self._palette_verify_count< 10:
    if self._palette_verify_count < 100:
        print(f"[PALETTE-VERIFY] Frame {self._palette_verify_count} | "
              f"Palette[0]={palette[0]} Palette[1]={palette[1]} "
              f"Palette[2]={palette[2]} Palette[3]={palette[3]}")
    self._palette_verify_count += 1

Monitor 2: [PIXEL-VERIFY]

Check the center pixel before mapping to PixelArray:

  • Location: render_frame()before writing inPixelArray
  • Frequency: First 10 frames
  • Functionality: Check the center pixel (line 72, column 80) before and after mapping
if self._pixel_verify_count< 10:
    center_idx = (72 * 160 + 80)  # Línea 72, columna 80
    center_color_idx = frame_indices[center_idx] & 0x03
    center_color_rgb = palette[center_color_idx]
    print(f"[PIXEL-VERIFY] Frame {self._pixel_verify_count} | "
          f"Center pixel: idx={center_idx} color_idx={center_color_idx} "
          f"rgb={center_color_rgb}")
    self._pixel_verify_count += 1

Monitor 3: [PALETTE-MODIFIED]

Detects if the palette is modified during execution:

  • Location: render_frame()after definingdebug_palette_map
  • Functionality: Compares the current palette with the last checked palette and shows stack trace if it changes
if self._last_palette_checked is not None:
    if self._last_palette_checked != debug_palette_map:
        print(f"[PALETTE-MODIFIED] Modified palette detected!")
        print(f" Original: {self._original_debug_palette}")
        print(f" Current: {debug_palette_map}")
        import traceback
        traceback.print_stack(limit=10)
self._last_palette_checked = dict(debug_palette_map)

Affected Files

  • src/gpu/renderer.py- Implementation of 3 additional monitors ([PALETTE-VERIFY], [PIXEL-VERIFY], [PALETTE-MODIFIED])
  • ANALYSIS_STEP_0305_RENDERER.md- Analysis document with all findings
  • debug_step_0305_renderer.log- Execution logs (in progress)

Tests and Verification

Running the Emulator: The emulator ran in the background for 2-3 minutes to capture logs.

Analysis Commands:

# Analyze [PALETTE-VERIFY]
Select-String -Path debug_step_0305_renderer.log -Pattern "\[PALETTE-VERIFY\]" | Select-Object -First 20 -Last 20

# Analyze [PIXEL-VERIFY]
Select-String -Path debug_step_0305_renderer.log -Pattern "\[PIXEL-VERIFY\]" | Select-Object -First 10

# Search for palette modifications
Select-String -Path debug_step_0305_renderer.log -Pattern "\[PALETTE-MODIFIED\]" | Select-Object -First 10

# Find patterns before green stripes
Select-String -Path debug_step_0305_renderer.log -Pattern "\[PALETTE-VERIFY\]" | Select-Object -Skip 50 -First 20

C++ Compiled Module Validation: Monitors run in Python and check the framebuffer obtained from PPU C++.

Findings

Palette Search

  • No green values ​​foundin the code
  • All palettes are corrected: self.COLORS, debug_palette_map, palette0, palette1
  • Hypothesis D rejected: There are no palettes pending correction

Rendering Code Search

  • There is no additional code to render: There is only one main rendering stream
  • Hypothesis B rejected: No other code using wrong palette

Implemented Monitors

  • [PALETTE-VERIFY]: Deployed and active
  • [PIXEL-VERIFY]: Deployed and active
  • [PALETTE-MODIFIED]: Deployed and active

Execution Analysis and Screenshot

Observaciones de la captura de pantalla:

  • Visible sprites: Pokémon sprites can be seen (although fragmented) and "RED" text
  • Green stripe problem: No green streaks observedon capture (possibly resolved)
  • ⚠️ Critical performance: FPS 21.8 (should be ~60 FPS) - new issue identified
  • ⚠️ Graphic corruption: Checkerboard pattern in lower section, vertical lines in upper section, fragmented sprites

Conclusion: The original green streaking issue appears to be resolved, but two new critical issues have been identified:

  1. Performance: Extremely low FPS (21.8 vs 60 expected)
  2. Graphic corruption: Anomalous patterns and fragmented sprites

Sources consulted

  • Pan Docs: "Framebuffer", "Palette", "Pixel Mapping"
  • Step 0304: Extended Verification and Framebuffer Monitor
  • Step 0303: Correction of Debug Palette Indexes 1 and 2

Educational Integrity

What I Understand Now

  • Rendering flow: The PPU C++ framebuffer contains indices (0-3), which are mapped to RGB using a palette in Python, then written to the PixelArray and scaled.
  • Comprehensive search: It is important to search all the palettes and rendering code to ensure that nothing is missed.
  • Multiple monitors: Implementing multiple monitors allows you to capture different aspects of the problem (palette, pixels, modifications).
  • Control de contexto: Logs should be redirected to files and analyzed with samples, not read in full to avoid cluttering the context.

What remains to be confirmed

  • Log analysis: Pending complete analysis when logs are available.
  • Root cause: If the monitors do not detect palette modifications, the problem may be with PixelArray, scaling, or how Pygame renders colors.

Hypotheses and Assumptions

Current hypothesis: If the PPU C++ framebuffer only contains 0 indices, but green streaks appear, the problem must be with:

  • Mapping indices to RGB (wrong palette sometime)
  • PixelArray or scaling causing artifacts
  • Pygame rendering colors incorrectly

The monitors in place will help identify which of these hypotheses is correct.

Next Steps

High Priority

  • [ ] Research Performance (FPS 21.8):
    • Profile the render loop
    • Check for crashes in Python code
    • Optimize expensive operations (PixelArray, scaling)
    • Check CPU-PPU synchronization
  • [ ] Investigate Graphic Corruption:
    • Verify framebuffer integrity
    • Investigate checkerboard pattern (possible issue with tiles or VRAM)
    • Check synchronization of tiles and sprites
    • Review sprite rendering code

Medium Priority

  • [ ] Check Green Streak Problem:
    • Run extended session (10-15 minutes) to confirm that the green stripes do not appear
    • If they appear, use the implemented monitors to diagnose
  • [ ] Improve Monitors:
    • Ensure logs are generated correctly
    • Add performance monitors (FPS, frame time)
    • Add graphical corruption monitors