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.
CGB Palette Reality Check (Cerrar el Blanco)
Resumen
Este step implementa un diagnóstico completo del problema de "pantalla blanca" en modo CGB (`tetris_dx.gbc`). Se implementaron herramientas de diagnóstico (CGB Detection, IO Watch para FF68-FF6B, dump de paletas CGB, Pixel Proof) que permitieron identificar que el problema estaba en `PPU::convert_framebuffer_to_rgb()`, que siempre usaba modo DMG incluso cuando el hardware era CGB. El fix aplicado permite que el código use correctamente las paletas CGB cuando está en modo CGB, resolviendo el problema del blanco.
Concepto de Hardware
Detección de Modo CGB: El Game Boy Color puede operar en dos modos: modo CGB nativo (usando paletas CGB) y modo compatibilidad DMG (usando paletas DMG incluso en hardware CGB). El modo se determina por el byte 0x0143 del header ROM (0x80 o 0xC0 = CGB) y por el bit 0 de LCDC (0xFF40): si LCDC bit 0 = 0 (LCD OFF), el hardware opera en modo compatibilidad DMG.
Paletas CGB: En modo CGB, las paletas se configuran escribiendo a BGPI/OBPI (Palette Index, 0xFF68/0xFF6A) y luego a BGPD/OBPD (Palette Data, 0xFF69/0xFF6B). Cada paleta tiene 4 colores, cada color es 2 bytes en formato BGR555 (bits 0-4 = Blue, 5-9 = Green, 10-14 = Red). Hay 8 paletas BG y 8 paletas OBJ, cada una con 4 colores = 64 bytes total por tipo.
Conversión BGR555→RGB888: Los colores CGB están en formato BGR555 (15 bits: 5 bits por componente). Para convertir a RGB888 (24 bits: 8 bits por componente), se extraen los componentes BGR555 y se escalan: `r8 = (r5 * 255) / 31`.
Referencia: Pan Docs - "CGB Palettes", "CGB Registers", "LCDC (0xFF40)", "BGR555 Format"
Implementación
Fase A: CGB Detection
A1-A2: Getters en MMU
Se implementó en MMU.hpp/MMU.cpp:
rom_header_cgb_flag_: Miembro que almacena el byte 0x0143 del header ROMget_rom_header_cgb_flag(): Getter que retorna el flag CGB del header ROMget_dmg_compat_mode(): Getter que retornatruesi LCDC bit 0 = 0 (modo compatibilidad DMG dentro de CGB)
A3: Exposición Cython
Se expusieron los getters en mmu.pyx:
get_rom_header_cgb_flag(): Retorna el flag CGB del header ROMget_dmg_compat_mode(): Retorna boolean indicando si está en modo compatibilidad DMGget_hardware_mode(): Ya existía, retorna "CGB" o "DMG"
A4: Sección CGBDetection en Snapshot
Se añadió sección CGBDetection en rom_smoke_0442.py:
rom_header_cgb_flag: Byte 0x0143 del header ROMmachine_is_cgb: Flag interno del emulador (1 = CGB, 0 = DMG)dmg_compat_mode: Modo compatibilidad DMG dentro de CGB
Fase B: IO Watch para FF68-FF6B
B1: Estructura IOWatchFF68FF6B
Se implementó en MMU.hpp:
- Tracking de writes/reads a FF68 (BGPI/BCPS), FF69 (BGPD/BCPD), FF6A (OBPI/OCPS), FF6B (OBPD/OCPD)
- Para cada registro: contadores de write/read, último PC, último valor
B2: Tracking en MMU
Se implementó tracking en MMU::write() y MMU::read():
- Incrementa contadores y almacena último PC/valor para cada acceso a FF68-FF6B
- Siempre activo (no gateado por variables de entorno)
B3: Exposición Cython y Snapshot
Se expuso el getter en mmu.pyx y se añadió sección IOWatchFF68FF6B en snapshot.
Fase C: Dump Compacto de RAM de Paletas
C1: Sección CGBPaletteRAM en Snapshot
Se añadió sección CGBPaletteRAM en rom_smoke_0442.py:
bg_palette_bytes_hex: Hex dump de 64 bytes de paleta BGobj_palette_bytes_hex: Hex dump de 64 bytes de paleta OBJbg_palette_nonwhite_entries: Contador de entradas no blancas en paleta BGobj_palette_nonwhite_entries: Contador de entradas no blancas en paleta OBJ
Fase D: Pixel Proof
D1: Sección PixelProof en Snapshot
Se añadió sección PixelProof en rom_smoke_0442.py:
- Muestra hasta 5 píxeles no blancos del framebuffer RGB
- Para cada píxel: coordenadas (x, y), índice de color (idx), paleta usada (BG/OBJ), color BGR555 raw, color RGB888 final
Fase E: Fix Mínimo
E1: Problema Identificado
En PPU::convert_framebuffer_to_rgb(), línea 5613:
bool is_dmg = true; // ❌ Siempre DMG, incluso en CGB
Esto forzaba siempre el uso de paletas DMG (BGP/OBP0/OBP1), que en `tetris_dx.gbc` se pone en 0x00 (todo blanco), en lugar de usar las paletas CGB que tienen datos válidos.
E2: Fix Aplicado
Se implementó detección correcta de modo CGB:
HardwareMode hw_mode = mmu_->get_hardware_mode();
bool is_dmg = (hw_mode == HardwareMode::DMG);
// Si estamos en CGB pero en modo compatibilidad DMG (LCDC bit 0 = 0), usar BGP
if (!is_dmg && mmu_->get_dmg_compat_mode()) {
is_dmg = true; // Usar paletas DMG aunque sea hardware CGB
}
También se corrigió la conversión BGR555→RGB888 (extracción correcta de componentes B, G, R).
Fase F: Validación
F1: Ejecución y Resultados
Se ejecutó tetris_dx.gbc por 600 frames y se verificó que el fix funciona:
fb_nonzero=22910✅ (hay índices no cero)PixelProofmuestra píxeles con RGB no blancos:rgb(0,0,0)yrgb(197,197,197)✅CGBDetection_MachineIsCGB=1✅ (emulador detecta CGB correctamente)CGBPaletteRAM_BG_NonWhite=24✅ (paletas CGB tienen datos)
Archivos Afectados
src/core/cpp/MMU.hpp- Estructura IOWatchFF68FF6B, miembro rom_header_cgb_flag_, getterssrc/core/cpp/MMU.cpp- Implementación de tracking FF68-FF6B, getters CGB detectionsrc/core/cpp/PPU.cpp- Fix en convert_framebuffer_to_rgb() para detectar modo CGBsrc/core/cython/mmu.pxd- Declaraciones de estructuras y getterssrc/core/cython/mmu.pyx- Exposición de getters a Pythontools/rom_smoke_0442.py- Secciones CGBDetection, IOWatchFF68FF6B, CGBPaletteRAM, PixelProof en snapshot
Tests y Verificación
Comando ejecutado:
PYTHONPATH=. python3 tools/rom_smoke_0442.py roms/tetris_dx.gbc --frames 600
Resultados (Frame 600):
CGBDetection_MachineIsCGB=1✅ (emulador detecta CGB)CGBPaletteRAM_BG_NonWhite=24✅ (paletas CGB tienen datos)fb_nonzero=22910✅ (framebuffer tiene índices no cero)PixelProof_P0_(0,0)_idx3_palBG_15b0x0000_rgb(0,0,0)_P1_(1,0)_idx1_palBG_15b0x6318_rgb(197,197,197)✅ (hay píxeles RGB no blancos)
Validación de módulo compilado C++:
# El código C++ se compiló correctamente con:
python3 setup.py build_ext --inplace
# Los getters Cython funcionan correctamente:
from viboy_core import PyMMU
mmu = PyMMU()
cgb_flag = mmu.get_rom_header_cgb_flag() # ✅ Funciona
dmg_compat = mmu.get_dmg_compat_mode() # ✅ Funciona
io_watch = mmu.get_io_watch_ff68_ff6b() # ✅ Funciona
Conclusión
El problema del "blanco" en modo CGB estaba causado por que PPU::convert_framebuffer_to_rgb() siempre usaba modo DMG, forzando el uso de BGP (que el juego pone en 0x00 = blanco) en lugar de usar las paletas CGB que tienen datos válidos. El fix permite que el código detecte correctamente el modo CGB y use las paletas CGB cuando corresponde, resolviendo el problema.
Resultado Final: El framebuffer RGB ahora tiene colores no blancos (PixelProof muestra rgb(0,0,0) y rgb(197,197,197)), confirmando que el fix funciona correctamente.
Referencias
- Plan:
step_0495_-_cgb_palette_reality_check_(cerrar_el_blanco)_e693ca2d.plan.md - Reporte:
docs/reports/reporte_step0495.md - Pan Docs: "CGB Palettes", "CGB Registers", "LCDC (0xFF40)", "BGR555 Format"
- GBEDG: "CGB Palette System"