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

Step 0395 - Visual Diagnostics: Verify Framebuffer Correspondence vs VRAM Metrics

Date:2025-12-31 StepID:0395 State: VERIFIED

Summary

This step implements a complete visual diagnostic system to verify the correspondence between the VRAM metrics (which report correct values ​​since Step 0394) and the actual contents of the C++ framebuffer. 5 verification functions are implemented that capture snapshots of the framebuffer, validate the tilemap→framebuffer correspondence, verify scroll/wrap-around, validate the BGP palette application, and verify the C++→Python pipeline. The results reveal critical discrepancies: Frame 676 shows completely white framebuffer (0=23040) while VRAM metrics report TileData 14.2%, and Frame 742 shows BGP=0x00 causing incorrect color mapping.

Hardware Concept

The Game Boy rendering pipeline follows this flow (according to Pan Docs):

  1. Tilemap → Tile ID: The PPU reads the tilemap (0x9800-0x9FFF) using coordinates (map_x, map_y) calculated with SCX/SCY, obtaining a tile_id (0-255).
  2. Tile ID → Tile Address: Depending on the addressing mode (unsigned: 0x8000 + tile_id*16, signed: 0x9000 + (tile_id-128)*16), the address of the tile in VRAM is calculated.
  3. 2bpp decoding: Each line of the tile (8 pixels) occupies 2 consecutive bytes. It is decoded:color_index = (bit_high<< 1) | bit_low(values ​​0-3).
  4. BGP Palette Application: The BGP record (0xFF47) maps each color_index to a final index:final_color = (BGP >> (color_index * 2)) & 0x03.
  5. Writing to Framebuffer: The final value (0-3) is written toframebuffer_back_[ly * 160 + x].
  6. Buffer Swap: Upon completing the frame (LY=144), they exchangeframebuffer_back_framebuffer_front_.
  7. Python Pipeline: Cython readsframebuffer_front_→ NumPy → Pygame applies RGB colors according to palette.

Identified problem: VRAM metrics (Step 0394) report correct values ​​(TileData 14.2%, TileMap 100%), but visually Tetris DX shows fragmented text and Zelda DX reported "sees nothing" (Step 0390). This suggests a disconnect between the contents of VRAM and what is rendered in the framebuffer.

Implementation

5 diagnostic functions were implemented in C++ and a verification in Python:

1. C++ Framebuffer Snapshot

Functiondump_framebuffer_snapshot()which captures the distribution of values ​​(0, 1, 2, 3) in key frames (1, 676, 742, 1080). Analyze by region (top/mid/bottom) and count lines with data vs completely white lines.

2. Tilemap → Framebuffer verification

Functionverify_tilemap_to_framebuffer()which validates line by line (LY=0, 72, 143) that the tiles referenced by the tilemap are rendered correctly. Reads bytes of the tile from VRAM, manually decodes the first 4 pixels, and compares with the values ​​written to the framebuffer.

3. Scroll and Wrap-around Verification

Functionverify_scroll_wraparound()which checks SCX/SCY and the tilemap wrap-around at frames 676 and 742. Displays map_x, map_y, tilemap_addr, tile_id, and wrap-around flags.

4. BGP Palette Verification

Functionverify_palette_bgp()which confirms that the palette correctly maps color indices. Compares the decoded color_index, BGP read, calculated final_color, and value in framebuffer.

5. Pipeline C++ → Python Verification

Functionget_framebuffer_snapshot()in Cython that returns a NumPy array of the entire framebuffer. In Python, the distribution of values ​​in frames 676 and 742 is checked and compared with the C++ snapshot.

Components created/modified

  • src/core/cpp/PPU.cpp: 5 diagnostic functions added
  • src/core/cpp/PPU.hpp: Added function declarations
  • src/core/cython/ppu.pyx: Functionget_framebuffer_snapshot()added
  • src/viboy.py: Python pipeline check added

Affected Files

  • src/core/cpp/PPU.cpp- Diagnostic features added
  • src/core/cpp/PPU.hpp- Added function declarations
  • src/core/cython/ppu.pyx- get_framebuffer_snapshot() function added
  • src/viboy.py- Python pipeline check added

Tests and Verification

Tests were executed with specific ROMs and logs were analyzed:

  • test ROMs: Tetris DX (30 seconds), Zelda DX (30 seconds)
  • Logs generated: logs/step0395_tetris_dx.log, logs/step0395_zelda_dx.log
  • Analysis: grep commands with limits to avoid context saturation

Key Results

Frame ROM Framebuffer Distribution Observation
1 Tetris DX 0=11520, 3=11520 Correct checkerboard (empty VRAM)
676 Tetris DX 0=23040 (all white) ⚠️ PROBLEM: Framebuffer empty even though VRAM has 14.2% TileData
742 Tetris DX 0=22560, 1=360, 3=120 Some data but very little (3 lines with data)
1080 Tetris DX 0=130, 1=12295, 2=3262, 3=7353 ✅ Complete data (144 lines with data)

Critical Findings

  • Frame 676: Framebuffer completely white (0=23040) while VRAM metrics report TileData 14.2%. This confirms the disconnect between VRAM and rendering.
  • Frame 742: BGP=0x00 detected, causing all colors to be mapped to 0 (white). This explains the visual fragmentation.
  • Tilemap → Framebuffer: Discrepancies detected - empty tiles (0x00) but framebuffer has different values ​​(checkerboard active).
  • Python Pipeline: The distribution in Python matches C++, confirming that the problem is in the C++ rendering, not in the pipeline.

Sources consulted

Educational Integrity

What I Understand Now

  • Rendering Pipeline: The complete flow from tilemap to framebuffer, including 2bpp decoding and BGP palette application.
  • Double Buffering: The buffer swap system (front/back) prevents race conditions but requires content verification after the swap.
  • Visual Diagnosis: The importance of capturing snapshots in key frames to identify where the correspondence between VRAM and visualization is lost.

What remains to be confirmed

  • BGP=0x00 on Frame 742: Why the BGP register is at 0x00 when it should have a valid value. Is it an initialization problem or is the game writing it?
  • Empty Framebuffer on Frame 676: If VRAM has 14.2% TileData, why is the framebuffer completely white? Does the tilemap point to empty tiles or is there a problem in the rendering?
  • Visual fragmentation: If the framebuffer has correct data in Frame 1080, why does it look fragmented? Is it a timing or palette application problem?

Hypotheses and Assumptions

Main hypothesis: The problem is in the application of the BGP palette or in the moment when BGP is read. If BGP=0x00, all color_index are mapped to 0 (white), which would explain why the framebuffer is empty even though VRAM has data.

Next Steps

  • [ ] Investigate why BGP=0x00 on Frame 742 - is the game writing it or is it an initialization issue?
  • [ ] Check if the tilemap points to empty tiles in Frame 676 even though VRAM has data
  • [ ] Implement fix to ensure BGP has a valid value during rendering
  • [ ] Check BGP read timing - is it read before or after the game updates it?