Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Fix Corrupción CGB RGB + Métricas TileData por Bancos
Resumen
Corrección de corrupción visual en CGB tras el pipeline RGB (error "array must match surface dimensions")
y mejora de métricas VRAM para considerar ambos bancos (bank 0 y bank 1) en Game Boy Color.
Se implementaron helpers duales para conteo de TileData por banco y se actualizó is_gameplay_state()
para considerar el banco con más datos. Tests con Tetris DX (baseline) y Oro.gbc confirmaron que el fix
RGB funciona correctamente y que Oro.gbc NO carga tiles en ninguno de los dos bancos.
Concepto de Hardware
VRAM Dual-Bank (CGB)
Fuente: Pan Docs - VRAM Banks (CGB Only), VBK Register
Game Boy Color tiene 16KB de VRAM divididos en 2 bancos de 8KB cada uno:
- Banco 0 (0x8000-0x9FFF):
- TileData (0x8000-0x97FF): 384 tiles × 16 bytes = 6144 bytes
- TileMaps (0x9800-0x9FFF): 2 tilemaps de 32×32 = 2048 bytes
- Banco 1 (0x8000-0x9FFF):
- TileData adicional (CGB)
- BG Map Attributes (atributos de tiles para el tilemap)
El registro VBK (0xFF4F, bit 0) selecciona el banco activo para lectura/escritura:
VBK = 0: Acceso al banco 0VBK = 1: Acceso al banco 1
BG Map Attributes (VRAM Bank 1)
Para cada tile en el tilemap (0x9800-0x9FFF en bank 0), existe un byte de atributos en la misma posición en bank 1:
- Bit 0-2: Palette number (0-7, selecciona una de las 8 paletas BG)
- 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
Conversión BGR555 → RGB888
Fuente: Pan Docs - CGB Registers, Color Palettes
Las paletas CGB usan formato BGR555 (5 bits por canal, 15 bits total, 1 bit sin usar):
Color = GGGRRRRR XBBBBBGG(Little Endian, X = bit sin usar)- Conversión a RGB888:
R5 = (color >> 0) & 0x1F,R8 = (R5 * 255) / 31G5 = (color >> 5) & 0x1F,G8 = (G5 * 255) / 31B5 = (color >> 10) & 0x1F,B8 = (B5 * 255) / 31
Implementación
Tarea 1: Corrección de render CGB (rgb_view) en Python
Problema identificado: pygame.surfarray.blit_array() recibía un array
con dimensiones correctas (160, 144, 3) pero se intentaba blit a self.screen (ventana escalada)
en lugar de self.surface (superficie base 160×144), causando el error
"array must match surface dimensions".
Archivo modificado: src/gpu/renderer.py, función render_frame()
Solución aplicada:
- Reshape directo a
(144, 160, 3)eliminando paso intermedio innecesario - Swap axes (0, 1) para convertir a
(160, 144, 3)que pygame espera - Asegurar contiguidad del array con
np.ascontiguousarray() - Blit a
self.surface(160×144) en lugar deself.screen - Escalar
self.surfaceaself.screenconpygame.transform.scale()
Tarea 2: Verificación de timing estable de conversión RGB
Se verificó que convert_framebuffer_to_rgb() ya se ejecuta correctamente en
swap_framebuffers() (una vez por frame, después del swap de índices).
El rgb_view representa un frame completo estable, no una mezcla parcial.
No se requirieron cambios.
Tarea 3: Métricas TileData por bancos (CGB)
Archivos modificados:
src/core/cpp/PPU.hpp: Declaraciones de nuevos helperssrc/core/cpp/PPU.cpp: Implementaciones de helpers y actualización de métricas
Nuevos helpers implementados:
int count_vram_nonzero_bank1_tiledata() const: Cuenta bytes no-cero en TileData de VRAM bank 1 (0x8000-0x97FF)int count_complete_nonempty_tiles_bank(int bank) const: Cuenta tiles completos (≥8 bytes no-cero) en un banco específico (0 o 1)
Actualización de métricas [VRAM-REGIONS]:
Ahora reporta:
[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
Actualización de is_gameplay_state():
Modificado para considerar el banco con más datos:
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
}
Archivos Afectados
src/gpu/renderer.py- Corrección pipeline RGB (surfarray dimensions, escalado)src/core/cpp/PPU.hpp- Declaraciones de helpers dual-banksrc/core/cpp/PPU.cpp- Implementación de helpers + actualización métricas + is_gameplay_state()build_log_step0408.txt- Log de compilación exitosalogs/step0408_tetris_dx_v2.log- Test Tetris DX con métricas dual-banklogs/step0408_oro_v2.log- Test Oro.gbc con métricas dual-bankdocs/informe_fase_2/parte_00_steps_0370_0402.md- Documentación del stepdocs/informe_fase_2/index.md- Actualización de rango de Stepsdocs/bitacora/index.html- Actualización del índicedocs/bitacora/entries/2026-01-01__0408__fix-cgb-rgb-corrupcion-y-metricas-bank1.html- Esta entrada
Tests y Verificación
Compilación
cd /media/fabini/8CD1-4C30/ViboyColor
python3 setup.py build_ext --inplace > build_log_step0408.txt 2>&1
# ✅ BUILD SUCCESS
Tests ejecutados
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
Resultados - Tetris DX (baseline)
- ✅ Renderizado RGB: Sin errores, frames renderizados correctamente
- ✅ TileData: bank0=56.6% (frame 840+), bank1=0% (no usado)
- ✅ Gameplay state: YES detectado correctamente (frame 720+)
- ✅ Sin regresión: Funcionamiento perfecto
[Renderer-RGB-CGB] Frame renderizado correctamente desde 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
Resultados - Oro.gbc (objetivo)
- ✅ Renderizado RGB: Sin errores, frames renderizados correctamente
- ❌ TileData: bank0=0%, bank1=0% (NO carga tiles en ningún banco)
- ⚠️ Tilemap: 100% lleno pero unique_tile_ids=1 (todos apuntan al mismo ID)
- ❌ Gameplay state: NO (condiciones de hardware no cumplidas)
[Renderer-RGB-CGB] Frame renderizado correctamente desde 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
Verificación de módulo compilado
✅ Validación de módulo compilado C++: Los nuevos helpers
count_vram_nonzero_bank1_tiledata() y count_complete_nonempty_tiles_bank()
se ejecutan correctamente desde Python vía Cython. Las métricas se reportan en logs sin errores.
Fuentes Consultadas
- 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
Integridad Educativa
Lo que Entiendo Ahora
- VRAM Dual-Bank: CGB tiene 2 bancos de 8KB. Bank 0 contiene TileData y TileMaps, Bank 1 contiene TileData adicional y BG attributes.
- BG Map Attributes: Cada tile del tilemap tiene atributos en bank 1 que indican paleta, banco VRAM, flips y prioridad.
- Pipeline RGB en Pygame:
surfarray.blit_array()requiere blit a superficie base (160×144), no a ventana escalada. - Métricas efectivas: En CGB, se debe considerar el banco con más datos (max(bank0, bank1)) para determinar si hay tiles válidos.
Lo que Confirmé
- Fix RGB exitoso: El error "array must match surface dimensions" se resolvió
usando
self.surface+ escalado posterior. - Oro.gbc confirmado: NO es un problema de banking ni de métricas. El juego simplemente NO carga tiles en ningún banco (bank0=0%, bank1=0%).
- Sin regresión: Tetris DX funciona perfectamente con el nuevo pipeline RGB.
Lo que Falta Confirmar
- Causa raíz de Oro.gbc: Por qué el juego no carga tiles. Posibles causas: timing VBLANK/STAT incorrecto, stub de RTC incompleto, wait loops no cumplidos, rutinas de descompresión fallidas.
- Otros juegos CGB: Verificar con otros juegos CGB si las métricas dual-bank detectan correctamente tiles en bank 1.
Hipótesis
- H1 (confirmada): La corrupción CGB no era por el formato del buffer RGB, sino por blit a superficie incorrecta (screen vs surface).
- H2 (confirmada): Oro.gbc NO carga tiles en bank 1. El problema persiste del Step 0407.
- H3 (pendiente): Oro.gbc requiere condiciones de hardware específicas no cumplidas actualmente (timing, RTC, etc.).
Próximos Pasos
- [ ] Investigar condiciones de hardware específicas para Oro.gbc (timing, RTC, wait loops)
- [ ] Verificar métricas dual-bank con otros juegos CGB que usen bank 1
- [ ] Considerar implementación de RTC (Real-Time Clock) completo para MBC3
- [ ] Optimizar rendimiento del pipeline RGB (si es necesario)