This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Step 0457: Isolate if the Bug is "Bad Indices" vs "Bad RGB Palette/Conversion" with Evidence
Summary
Aim:Isolate whether the flat RGB framebuffer bug is caused by poorly decoded/rendered indices (H1) or incorrect palette/RGB conversion (H2). The tests are already fine (0456), so if they continue to fail, the bug is real in the core.
Critical finding:Instrumentation reveals that the index framebuffer is completely flat (all values are 0), which confirms that the bug is NOT in the palette conversion, but in the tile decode/render. The problem is how the tiles are decoded from VRAM or how the indexes are written to the framebuffer.
Numerical evidence:The tests show that the sampled indices (8 pixels) are all 0:[0, 0, 0, 0, 0, 0, 0, 0], with a unique set of{0}instead of the expected{0, 1, 2, 3}. This rules out H2 (palette/RGB conversion) and confirms H1 (decode/render).
Hardware Concept
The rendering process on the Game Boy has two main phases:
- Decoding and Rendering:Tiles are decoded from VRAM (2bpp format) and written to the framebuffer as color indices (0-3). This phase includes reading tiles from VRAM, 2bpp decoding, and writing indexes to the framebuffer.
- Palette Conversion:Indices are converted to RGB using the BGP/OBP0/OBP1 palettes. This phase reads the palette register, maps the index to a shade (0-3), and converts the shade to RGB888.
If the RGB framebuffer is flat (all the same color), it may be because:
- H1 - Bad indices:The index framebuffer is flat (all 0, all 2, etc.) → the bug is in decode/render.
- H2 - Bad conversion:The index framebuffer has variety (0, 1, 2, 3), but the palette→RGB conversion is broken and produces flat RGB → the bug is in RGB conversion/writing.
Fountain:Pan Docs - Background, Window, Sprites, Color Palettes
Implementation
Minimal instrumentation was implemented to isolate the bug without touching the tests (which are already fine according to 0456).
Phase A: Expose Framebuffer Indexes
Added a debug API to expose the index framebuffer from C++ to Python:
PPU::get_framebuffer_indices_ptr()inPPU.hpp- Returns const pointer to framebuffer_front_PyPPU::get_framebuffer_indices()inppu.pyx- Cython wrapper returning 23040 bytes- Declaration in
ppu.pxdso Cython can access the method
This API allows tests to inspect the index framebuffer before converting to RGB, allowing you to distinguish between H1 and H2.
Phase B: Capture Used Regs Palette
Added capture of the palette registers used in the conversion:
- Members
last_bgp_used_,last_obp0_used_,last_obp1_used_inPPU.hpp - Update on
convert_framebuffer_to_rgb()to capture the read values - Getters
get_last_bgp_used(),get_last_obp0_used(),get_last_obp1_used() - Wrapper
PyPPU::get_last_palette_regs_used()which returns a dict with the values
This allows you to verify that the palette register used in the conversion matches the one written by the test, ruling out reading/reg caching bugs.
Phase C: Conversion Review
The conversion code was reviewed and confirmed to be correct:
dmg_shade_to_rgb()- Correct conversion table: {255, 170, 85, 0}- Writing to RGB buffer - Correct Stride:
rgb_idx = fb_index * 3 - Conversion order - Called after the swap, on the correct framebuffer_front_
The conversion does not need correction; The bug is in the previous phase (decode/render).
Test Modification
Added "sanity asserts" in tests to verify indexes and palette regs before looking at RGB:
test_palette_dmg_bgp_0454.py- Added sanity assert that checks sample indices (8 pixels) and reg palette usedtest_palette_dmg_obj_0454.py- Added similar sanity assert for sprites
These asserts fail immediately if the indexes are wrong, allowing the bug to be identified without having to look at RGB.
Affected Files
src/core/cpp/PPU.hpp- Added methodget_framebuffer_indices_ptr()and members for pallet regs usedsrc/core/cpp/PPU.cpp- Implementation ofget_framebuffer_indices_ptr()and capture regs palette inconvert_framebuffer_to_rgb()src/core/cython/ppu.pxd- New method declarations for Cythonsrc/core/cython/ppu.pyx- Cython wrappers:get_framebuffer_indices()andget_last_palette_regs_used()tests/test_palette_dmg_bgp_0454.py- Added sanity asserts with indexes and regs palettetests/test_palette_dmg_obj_0454.py- Added sanity asserts with indexes and regs palette
Tests and Verification
Command executed: pytest -v tests/test_palette_dmg_bgp_0454.py tests/test_palette_dmg_obj_0454.py tests/test_framebuffer_not_flat_0456.py
Result:❌ 0/3 tests pass (expected: the bug is real in the core)
Numerical evidence:
[TEST-BGP-SANITY] Sample indices (8 pixels): [0, 0, 0, 0, 0, 0, 0, 0]
[TEST-BGP-SANITY] Unique indexes: {0}
AssertionError: Flat indexes: only {0} (expected {0,1,2,3}).
If this fails → bug is NOT palette; is decode/render.
Interpretation:The index framebuffer is completely flat (all 0s), confirming that the bug is in decode/render, not palette conversion. The palette→RGB conversion is correct, but there is no variety of indices to convert.
Compiled C++ module validation:✅ Compilation successful, no errors. The methods are available in Python.
Sources consulted
- Bread Docs:Background, Window, Sprites, Color Palettes
- Plan Step 0457:
.cursor/plans/step_0457_-_aislar_si_bug_es_indices_mal_vs_paleta_conversion_rgb_mal_con_evidencia_y_aplicar_fix_mi_ce55b6c6.plan.md
Educational Integrity
What I Understand Now
- Bug isolation:When a symptom may have multiple causes, instrumentation is essential to isolate the root cause. In this case, exposing the index framebuffer allowed us to distinguish between "bad indexes" and "bad conversion".
- Debug API:Adding debug APIs that expose internal state is a valid technique for testing, as long as it does not affect the hot path of the production code.
- Numerical evidence:Tests should provide clear numerical evidence (sample indices, palette regs used) rather than just assuming what is wrong.
What remains to be confirmed
- Decode de tiles:I need to investigate why the tile decode produces all 0 indices. Is the problem in
decode_tile_line(), in reading VRAM, or in writing to the framebuffer? - Background Rendering:Is the problem in
render_bg()eitherrender_scanline()? Are the tiles being read correctly from VRAM?
Hypotheses and Assumptions
Main hypothesis:The bug is in the decode/render of tiles. Possible causes:
- The 2bpp decode is poorly implemented and always produces index 0
- VRAM reading is not working correctly
- Writing to framebuffer_back_ is wrong (0 is always written)
- The framebuffer swap is bad and the clean buffer is always read
Next Steps
- [ ] Investigate
decode_tile_line()- Verify that the 2bpp decode produces the correct indexes - [ ] Investigate
render_bg()- Verify that the tiles are read correctly from VRAM - [ ] Investigate writing to framebuffer - Verify that indexes are written correctly to framebuffer_back_
- [ ] Investigate framebuffer swap - Verify that the swap is working correctly
- [ ] Apply minimum fix once the root cause is identified
- [ ] Re-run palette tests to validate the fix