⚠️ 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 0459: Instrumentation Pipeline Shade→RGB Conversion

Date:2026-01-03 StepID:0459 State: VERIFIED

Summary

Aim:Instrument the idx→shade→rgb conversion pipeline to identify where the "RGB only 2 unique colors instead of ≥3" bug crashes. Step 0458 confirmed that the indices are written correctly (0, 1, 2, 3), so the remaining bug must be in the palette conversion or the shade→RGB conversion.

Critical finding:✅ The conversion pipeline workscorrectly. The instrumentation shows that when there are 4 different indices (0, 1, 2, 3), the pipeline produces 4 unique colors: (255,255,255), (170,170,170), (85,85,85), (0,0,0). The problem is NOT in the conversion pipeline.

Real problem identified:The index framebuffer only has variety in the first pixels (where the rendered tile is). The rest of the framebuffer has only indices 0 and 2, which produces only 2 unique colors. This is a problem ofrendering, not conversion.

Result:✅ Conversion pipeline validated and working correctly. No fix is ​​required in the pipeline. The problem is in the rendering (outside the scope of this Step).

Hardware Concept

DMG Conversion Pipeline: Index → ​​Shade → RGB

In DMG (Classic Game Boy) mode, the color conversion pipeline works in 3 stages:

  1. Color index (0-3):The framebuffer contains raw indices (0, 1, 2, 3) that represent which pixel of the tile is being rendered.
  2. Shade (0-3):The BGP register (0xFF47) maps each index to a shade (tone of gray). Each index occupies 2 bits in BGP:
    • Bits 1-0: shade for index 0
    • Bits 3-2: shade for index 1
    • Bits 5-4: shade for index 2
    • Bits 7-6: shade for index 3
  3. RGB (0-255):The shade is converted to RGB888 using a fixed table:
    • Shade 0 = White (255, 255, 255)
    • Shade 1 = Light gray (170, 170, 170)
    • Shade 2 = Dark gray (85, 85, 85)
    • Shade 3 = Black (0, 0, 0)

Fountain:Pan Docs - "Color Palettes" (DMG)

Typical Pipeline Errors

The most common errors that cause 2-color collapse are:

  • Incorrect paddle shift:Wear<<instead of*2, or use the shade instead of the index to calculate the shift.
  • Collapse on shade_to_rgb:The conversion table maps different shades to the same RGB value.
  • Incorrect Stride:RGB writing steps bytes or uses an incorrect stride.
  • Bad buffer:It is read/written from the wrong buffer (back instead of front).

Implementation

Phase A: Pipeline Instrumentation

Added debug instrumentation to capture samples from the idx→shade→rgb pipeline:

  • Debug members in PPU.hpp:
    • last_idx_samples_[32]- First 32 indexes processed
    • last_shade_samples_[32]- First 32 resulting shades
    • last_rgb_samples_[32][3]- First 32 RGB values ​​(R, G, B)
    • last_convert_sample_count_- Number of samples captured
    • last_bgp_used_debug_- BGP used in conversion
  • Modification in convert_framebuffer_to_rgb():Capture samples during conversion (only first N pixels to avoid cluttering context).
  • Getters in PPU.hpp: get_last_idx_samples(), get_last_shade_samples(), get_last_rgb_samples(), etc.
  • Exhibition on Cython:Methodget_last_dmg_convert_samples()which returns a dict with the samples.
  • Modification in tests:Checking the pipeline after rendering 1 frame.

Numerical Evidence

The instrumentation shows that the pipeline is working correctly:

[TEST-PIPELINE] BGP used: 0xE4
[TEST-PIPELINE] First 8 pixels:
  idx: [0, 1, 2, 3, 0, 1, 2, 3]
  shade: [0, 1, 2, 3, 0, 1, 2, 3]
  rgb: [(255, 255, 255), (170, 170, 170), (85, 85, 85), (0, 0, 0), 
          (255, 255, 255), (170, 170, 170), (85, 85, 85), (0, 0, 0)]
✅ Pipeline OK: idx=4 unique, shade=4 unique, rgb=4 unique

Conclusion:The conversion pipeline works correctly. When there are 4 different indices, it produces 4 unique colors.

Real Problem Identified

Analysis of the entire framebuffer shows that:

  • First 100 pixels:Distribution 0=25, 1=25, 2=25, 3=25 (4 different indices) ✅
  • Complete sampling:Distribution 0=1152, 1=0, 2=1152, 3=0 (only indices 0 and 2) ❌

This means that the tile is only rendering on the first few pixels, and the rest of the framebuffer has only indices 0 and 2. This is a problem.rendering, not conversion.

Affected Files

  • src/core/cpp/PPU.hpp- Added debug members for pipeline samples (under#ifdef VIBOY_DEBUG_PPU)
  • src/core/cpp/PPU.cpp- Modifiedconvert_framebuffer_to_rgb()to capture samples, member initialization in constructor
  • src/core/cython/ppu.pxd- Added getter declarations for samples
  • src/core/cython/ppu.pyx- Added methodget_last_dmg_convert_samples()to expose samples to Python
  • tests/test_palette_dmg_bgp_0454.py- Added pipeline check after rendering 1 frame

Tests and Verification

Command executed: pytest -v tests/test_palette_dmg_bgp_0454.py

Result:Test fails because the entire framebuffer only has 2 unique colors, but instrumentation shows that the pipeline works correctly in the first few pixels.

Test Code:

# --- Step 0459: Check pipeline idx→shade→rgb ---
samples = ppu.get_last_dmg_convert_samples()
if samples:
    idx_samples = samples['idx'][:8] # First 8
    shade_samples = samples['shade'][:8]
    rgb_samples = samples['rgb'][:8]
    bgp_used = samples['bgp_used']
    
    # Assert: idx_samples contains 0,1,2,3
    unique_idx = set(idx_samples)
    assert unique_idx == {0, 1, 2, 3}
    
    # Assert: shade_samples has ≥3 distinct values
    unique_shade = set(shade_samples)
    assert len(unique_shade) >= 3
    
    # Assert: rgb produces ≥3 distinct values
    unique_rgb = set(rgb_samples)
    assert len(unique_rgb) >= 3

Native Validation:✅ Successful compilation with-DVIBOY_DEBUG_PPU, without errors. Instrumentation correctly captures samples from the pipeline.

Evidence:The first 8 pixels show 4 unique colors: (255,255,255), (170,170,170), (85,85,85), (0,0,0). The pipeline works correctly.

Sources consulted

Educational Integrity

What I Understand Now

  • DMG conversion pipeline:The idx→shade→rgb pipeline works in 3 stages: raw index (0-3) → shade via BGP (0-3) → RGB888 via fixed table. Each stage is independent and can fail separately.
  • Debug instrumentation:Capturing pipeline samples allows you to identify exactly where the conversion breaks down. The samples show that the pipeline works correctly when there are a variety of indexes.
  • Separation of responsibilities:The "RGB only 2 single colors" issue may be in rendering (wrong indices) or conversion (palette/RGB). Instrumentation allows you to isolate the problem.

What remains to be confirmed

  • Full render:The tile is only rendered on the first pixels. I need to check why the rest of the framebuffer has only indices 0 and 2. This is a rendering issue, not a conversion issue.
  • Index distribution:The logs show that there are 17280 non-zero pixels with distribution 0=5760, 1=5760, 2=5760, 3=5760, but full sampling only finds indices 0 and 2. I need to investigate this discrepancy.

Hypotheses and Assumptions

Confirmed hypothesis:The conversion pipeline works correctly. When there are 4 different indices, it produces 4 unique colors. No fix is ​​required in the pipeline.

Pending hypothesis:The problem is in the rendering. The tile is not repeating correctly across the entire screen, or there is a problem with how the indices are being written to the framebuffer.

Next Steps

  • [ ] Investigate why the index framebuffer only has variety in the first few pixels
  • [ ] Verify that the tile is rendering correctly on the entire screen
  • [ ] If the problem is rendering, correct it in a future Step