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

Fix CGB RGB Corruption + TileData Metrics by Banks

Date:2026-01-01 StepID:0408 State: VERIFIED

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 0
  • VBK = 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) / 31
    • G5 = (color >> 5) & 0x1F, G8 = (G5 * 255) / 31
    • B5 = (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 withnp.ascontiguousarray()
  • blit aself.surface(160×144) instead ofself.screen
  • Climbself.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 declarations
  • src/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 statements
  • src/core/cpp/PPU.cpp- Helper implementation + metrics update + is_gameplay_state()
  • build_log_step0408.txt- Successful compilation log
  • logs/step0408_tetris_dx_v2.log- Tetris DX test with dual-bank metrics
  • logs/step0408_oro_v2.log- Test Oro.gbc with dual-bank metrics
  • docs/report_phase_2/part_00_steps_0370_0402.md- Step documentation
  • docs/report_phase_2/index.md- Steps rank update
  • docs/bitacora/index.html- Index update
  • docs/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

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 usingself.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)