⚠️ Clean-Room / Educativo

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

Fecha: 2026-01-01 Step ID: 0408 Estado: VERIFIED

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 0
  • VBK = 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) / 31
    • G5 = (color >> 5) & 0x1F, G8 = (G5 * 255) / 31
    • B5 = (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 de self.screen
  • Escalar self.surface a self.screen con pygame.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 helpers
  • src/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-bank
  • src/core/cpp/PPU.cpp - Implementación de helpers + actualización métricas + is_gameplay_state()
  • build_log_step0408.txt - Log de compilación exitosa
  • logs/step0408_tetris_dx_v2.log - Test Tetris DX con métricas dual-bank
  • logs/step0408_oro_v2.log - Test Oro.gbc con métricas dual-bank
  • docs/informe_fase_2/parte_00_steps_0370_0402.md - Documentación del step
  • docs/informe_fase_2/index.md - Actualización de rango de Steps
  • docs/bitacora/index.html - Actualización del índice
  • docs/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

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)