This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Fix CGB RGB Corruption + TileData Metrics by Banks
Summary
Correction of visual corruption in CGB after the RGB pipeline ("array must match surface dimensions" error)
and improvement of VRAM metrics to consider both banks (bank 0 and bank 1) on Game Boy Color.
Implemented dual helpers for counting TileData per bank and updatedis_gameplay_state()to consider the bank with more data. Tests with Tetris DX (baseline) and Oro.gbc confirmed that the fix
RGB works correctly and Oro.gbc does NOT load tiles in either bank.
Hardware Concept
VRAM Dual-Bank (CGB)
Fountain:Pan Docs - VRAM Banks (CGB Only), VBK Register
Game Boy Color has 16KB of VRAM divided into 2 banks of 8KB each:
- Bank 0 (0x8000-0x9FFF):
- TileData (0x8000-0x97FF): 384 tiles × 16 bytes = 6144 bytes
- TileMaps (0x9800-0x9FFF): 2 32×32 tilemaps = 2048 bytes
- Bank 1 (0x8000-0x9FFF):
- Additional TileData (CGB)
- BG Map Attributes (tile attributes for the tilemap)
The recordVBK (0xFF4F, bit 0)select the active bank for reading/writing:
VBK = 0: Bank access 0VBK = 1: Access to bank 1
BG Map Attributes (VRAM Bank 1)
For each tile in the tilemap (0x9800-0x9FFF in bank 0), there is an attribute byte in the same position in bank 1:
- Bit 0-2: Palette number (0-7, select one of the 8 BG palettes)
- Bit 3: Tile VRAM bank (0=Bank 0, 1=Bank 1)
- Bit 5: X-Flip
- Bit 6: Y-Flip
- Bit 7: BG-to-OAM Priority
BGR555 → RGB888 conversion
Fountain:Pan Docs - CGB Registers, Color Palettes
CGB palettes use BGR555 format (5 bits per channel, 15 bits total, 1 unused bit):
Color = GGGRRRRR XBBBBBGG(Little Endian, X = unused bit)- Conversion to RGB888:
R5 = (color >> 0) & 0x1F,R8 = (R5 * 255) / 31G5 = (color >> 5) & 0x1F,G8 = (G5 * 255) / 31B5 = (color >> 10) & 0x1F,B8 = (B5 * 255) / 31
Implementation
Task 1: Fix CGB render (rgb_view) in Python
Identified problem: pygame.surfarray.blit_array()I received an array
with correct dimensions (160, 144, 3) but they tried to blitself.screen(scaled window)
instead ofself.surface(base area 160x144), causing the error
"array must match surface dimensions".
Modified file: src/gpu/renderer.py, functionrender_frame()
Applied solution:
- Reshape direct to
(144, 160, 3)eliminating unnecessary intermediate step - Swap axes (0, 1) to convert to
(160, 144, 3)what pygame expects - Ensure array contiguity with
np.ascontiguousarray() - blit a
self.surface(160×144) instead ofself.screen - Climb
self.surfacetoself.screenwithpygame.transform.scale()
Task 2: Verification of stable timing of RGB conversion
It was verified thatconvert_framebuffer_to_rgb()it already runs correctly inswap_framebuffers()(once per frame, after the index swap).
Hergb_viewrepresents a complete stable frame, not a partial mix.No changes were required.
Task 3: TileData Metrics by Banks (CGB)
Modified files:
src/core/cpp/PPU.hpp: New helper declarationssrc/core/cpp/PPU.cpp: Helper implementations and metrics update
New helpers implemented:
int count_vram_nonzero_bank1_tiledata() const: Count non-zero bytes in TileData of VRAM bank 1 (0x8000-0x97FF)int count_complete_nonempty_tiles_bank(int bank) const: Counts full tiles (≥8 non-zero bytes) in a specific bank (0 or 1)
Metric Update [VRAM-REGIONS]:
Now reports:
[VRAM-REGIONS] Frame N |
tiledata_bank0=X/6144 (%.1f%%) |
tiledata_bank1=Y/6144 (%.1f%%) |
tiledata_effective=max(X,Y)/6144 (%.1f%%) |
tilemap_nonzero=Z/2048 (%.1f%%) |
unique_tile_ids=W/256 |
complete_tiles=C/384 (%.1f%%) |
vbk=B |
gameplay_state=YES/NO
Update to is_gameplay_state():
Modified to consider the bank with more data:
int tiledata_bank0 = count_vram_nonzero_bank0_tiledata();
int tiledata_bank1 = count_vram_nonzero_bank1_tiledata();
int tiledata_effective = max(tiledata_bank0, tiledata_bank1);
if (tiledata_effective< 200) {
return false; // VRAM vacía en ambos bancos
}
Affected Files
src/gpu/renderer.py- RGB pipeline correction (surfarray dimensions, scaling)src/core/cpp/PPU.hpp- Dual-bank helper statementssrc/core/cpp/PPU.cpp- Helper implementation + metrics update + is_gameplay_state()build_log_step0408.txt- Successful compilation loglogs/step0408_tetris_dx_v2.log- Tetris DX test with dual-bank metricslogs/step0408_oro_v2.log- Test Oro.gbc with dual-bank metricsdocs/report_phase_2/part_00_steps_0370_0402.md- Step documentationdocs/report_phase_2/index.md- Steps rank updatedocs/bitacora/index.html- Index updatedocs/bitacora/entries/2026-01-01__0408__fix-cgb-rgb-corrupcion-y-metricas-bank1.html- This entry
Tests and Verification
Compilation
cd /media/fabini/8CD1-4C30/ViboyColor
python3 setup.py build_ext --inplace > build_log_step0408.txt 2>&1
# ✅ BUILD SUCCESS
Tests executed
timeout 30s python3 main.py roms/tetris_dx.gbc > logs/step0408_tetris_dx_v2.log 2>&1
timeout 30s python3 main.py roms/Oro.gbc > logs/step0408_oro_v2.log 2>&1
Results - Tetris DX (baseline)
- ✅ RGB Rendering:No errors, frames rendered correctly
- ✅TileData:bank0=56.6% (frame 840+), bank1=0% (not used)
- ✅ Gameplay state:YES detected correctly (frame 720+)
- ✅ No regression:Perfect operation
[Renderer-RGB-CGB] Frame rendered correctly from RGB888
[VRAM-REGIONS] Frame 840 |
tiledata_bank0=3479/6144 (56.6%) | tiledata_bank1=0/6144 (0.0%) |
tiledata_effective=3479/6144 (56.6%) |
tilemap_nonzero=2012/2048 (98.2%) | unique_tile_ids=185/256 |
complete_tiles=253/384 (65.9%) | vbk=0 | gameplay_state=YES
Results - Oro.gbc (target)
- ✅ RGB Rendering:No errors, frames rendered correctly
- ❌TileData:bank0=0%, bank1=0% (DOES NOT load tiles in any bank)
- ⚠️ Tilemap:100% full but unique_tile_ids=1 (they all point to the same ID)
- ❌ Gameplay state:NO (hardware conditions not met)
[Renderer-RGB-CGB] Frame rendered correctly from RGB888
[VRAM-REGIONS] Frame 1200 |
tiledata_bank0=0/6144 (0.0%) | tiledata_bank1=0/6144 (0.0%) |
tiledata_effective=0/6144 (0.0%) |
tilemap_nonzero=2048/2048 (100.0%) | unique_tile_ids=1/256 |
complete_tiles=0/384 (0.0%) | vbk=0 | gameplay_state=NO
Compiled module verification
✅ C++ compiled module validation:The new helperscount_vram_nonzero_bank1_tiledata()andcount_complete_nonempty_tiles_bank()They run correctly from Python via Cython. The metrics are reported in logs without errors.
Sources consulted
- Pan Docs - VRAM Banks (CGB Only): https://gbdev.io/pandocs/VRAM_Banks.html
- Pan Docs - VBK Register (0xFF4F): https://gbdev.io/pandocs/Video_Registers.html
- Pan Docs - CGB Registers, Color Palettes: https://gbdev.io/pandocs/Palettes.html
- Pan Docs - BG Map Attributes: https://gbdev.io/pandocs/Tile_Maps.html
Educational Integrity
What I Understand Now
- Dual-Bank VRAM:CGB has 2 banks of 8KB. Bank 0 contains TileData and TileMaps, Bank 1 contains additional TileData and BG attributes.
- BG Map Attributes:Each tile in the tilemap has attributes in bank 1 that indicate paddle, VRAM bank, flips and priority.
- RGB Pipeline in Pygame:
surfarray.blit_array()requires blit a base surface (160×144), not scaled window. - Effective metrics:In CGB, the bank with the most data should be considered (max(bank0, bank1)) to determine if there are valid tiles.
What I confirmed
- Successful RGB Fix:"array must match surface dimensions" error was resolved
using
self.surface+ later scaling. - Oro.gbc confirmed:It is NOT a banking or metrics problem. The game it simply DOES NOT load tiles in any bank (bank0=0%, bank1=0%).
- No regression:Tetris DX works perfectly with the new RGB pipeline.
What remains to be confirmed
- Root Cause of Oro.gbc:Why the game doesn't load tiles. Possible causes: incorrect VBLANK/STAT timing, incomplete RTC stub, unfulfilled wait loops, routines failed decompression.
- Other CGB games:Verify with other CGB games if dual-bank metrics They correctly detect tiles in bank 1.
Hypothesis
- H1 (confirmed):The CGB corruption was not due to the RGB buffer format, but because of blit to the wrong surface (screen vs surface).
- H2 (confirmed):Oro.gbc DOES NOT load tiles in bank 1. The problem persists from Step 0407.
- H3 (pending):Oro.gbc requires no specific hardware conditions currently fulfilled (timing, RTC, etc.).
Next Steps
- [ ] Investigate specific hardware conditions for Oro.gbc (timing, RTC, wait loops)
- [ ] Check dual-bank metrics with other CGB games that use bank 1
- [ ] Consider full RTC (Real-Time Clock) implementation for MBC3
- [ ] Optimize RGB pipeline performance (if necessary)