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

CGB Post-Boot Clean-Room y Diagnóstico

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

Resumen

Implementación clean-room de separación explícita entre modos DMG (Game Boy clásico) y CGB (Game Boy Color) para inicialización correcta de registros I/O según Pan Docs Power Up Sequence. Añadido enum HardwareMode (DMG/CGB) en MMU con detección automática leyendo byte 0x0143 del header de la ROM. Implementada función initialize_io_registers() que configura registros PPU, APU, interrupciones y registros CGB-específicos (VBK, KEY1, SVBK, BCPS/BCPD, OCPS/OCPD, HDMA) según el modo actual. Wrappers Cython exponen set_hardware_mode(), get_hardware_mode() e initialize_io_registers() a Python. Detección automática funciona correctamente: Tetris DX detectado como CGB (flag=0x80), registros inicializados para modo CGB. Sistema preparado para instrumentación diagnóstica dirigida (próximos pasos) para identificar qué registros/condiciones bloquean Zelda DX/Pokémon Red en inicialización.

Concepto de Hardware (Pan Docs - Power Up Sequence, CGB Registers)

Diferencias DMG vs CGB en Power Up Sequence

Game Boy clásico (DMG) y Game Boy Color (CGB) tienen secuencias de inicialización diferentes. La Power Up Sequence documentada en Pan Docs especifica los valores iniciales de los registros I/O después de que el sistema arranca (con o sin Boot ROM).

Registros Comunes (DMG y CGB)

  • LCDC (0xFF40): 0x91 (LCD ON, BG ON, Window OFF, BG Tilemap 0x9800)
  • STAT (0xFF41): 0x85 (bits 3-7 escribibles, bits 0-2 controlados por PPU)
  • SCY/SCX (0xFF42/0xFF43): 0x00 (scroll en posición inicial)
  • BGP (0xFF47): 0xFC (paleta DMG: 11221100, blanco a negro)
  • OBP0/OBP1 (0xFF48/0xFF49): 0xFF (paletas de sprites)
  • WY/WX (0xFF4A/0xFF4B): 0x00 (Window en posición inicial)
  • APU Registers (0xFF10-0xFF26): Valores específicos por canal
  • IF (0xFF0F): 0x01 (VBlank interrupt request inicial)
  • IE (0xFFFF): 0x00 (sin interrupciones habilitadas inicialmente)

Registros Específicos de CGB

CGB introduce registros adicionales que NO existen en DMG:

  • VBK (0xFF4F): VRAM Bank Select. Valor inicial: 0x00 (banco 0 por defecto). Permite seleccionar entre 2 bancos de VRAM de 8KB cada uno.
  • KEY1 (0xFF4D): Prepare Speed Switch. Valor inicial: 0x00 (modo normal, no double-speed). Permite cambiar entre 4.19 MHz (normal) y 8.38 MHz (double-speed).
  • SVBK (0xFF70): WRAM Bank Select. Valor inicial: 0x01 (banco 1 por defecto). CGB tiene 8 bancos de WRAM (4KB cada uno), banco 0 siempre mapeado en 0xC000-0xCFFF.
  • BCPS/BCPD (0xFF68/0xFF69): BG Palette Specification/Data. CGB tiene 8 paletas BG, cada una con 4 colores de 15 bits (BGR555 format). Valor inicial: 0x00 (índice 0, no auto-increment).
  • OCPS/OCPD (0xFF6A/0xFF6B): OBJ Palette Specification/Data. Similar a paletas BG pero para sprites. Valor inicial: 0x00.
  • HDMA1-HDMA5 (0xFF51-0xFF55): Horizontal/General DMA. Permite copiar datos de ROM/RAM a VRAM de forma eficiente. Valor inicial: 0xFF (inactivo).

Detección de Modo CGB desde el Header de la ROM

El byte 0x0143 del header del cartucho indica la compatibilidad CGB:

Valor Significado Modo Emulador
0x80 CGB funcionalidad (funciona en DMG también) CGB (preferido)
0xC0 CGB only (solo funciona en CGB) CGB (obligatorio)
Otros DMG only (Game Boy clásico) DMG

Fuente: Pan Docs - Cartridge Header, 0143 - CGB Flag.

Por qué esto es crítico para Zelda DX/Pokémon Red

Los juegos CGB esperan que los registros específicos de Color estén inicializados correctamente. Sin una separación clara DMG/CGB, el emulador puede:

  • Inicializar registros CGB con valores incorrectos (o no inicializarlos).
  • Depender excesivamente de BGP (0xFF47) cuando juegos CGB usan paletas CGB (BCPS/BCPD).
  • No configurar banking de VRAM/WRAM correctamente, causando corrupción de datos.
  • No soportar características como double-speed mode o HDMA.

La implementación de este paso asegura que, al cargar una ROM CGB, todos los registros CGB se inicializan a valores correctos según Pan Docs, eliminando una posible causa de bloqueo en inicialización.

Implementación

1. Enum de Modo de Hardware (MMU.hpp)

/**
 * Step 0404: Hardware Mode - Modo de hardware (DMG vs CGB)
 * Fuente: Pan Docs - Power Up Sequence, CGB Registers
 */
enum class HardwareMode {
    DMG,  // Game Boy clásico (monocromo)
    CGB   // Game Boy Color
};

2. Métodos de Gestión de Modo (MMU.hpp)

// Miembro privado
HardwareMode hardware_mode_;  // Modo de hardware actual (DMG o CGB)

// Métodos públicos
void set_hardware_mode(HardwareMode mode);
HardwareMode get_hardware_mode() const;
void initialize_io_registers();

3. Inicialización de Registros I/O (MMU.cpp)

Función initialize_io_registers() que configura registros según el modo:

void MMU::initialize_io_registers() {
    bool is_cgb = (hardware_mode_ == HardwareMode::CGB);
    
    // ===== PPU / Video =====
    memory_[0xFF40] = 0x91; // LCDC
    memory_[0xFF41] = 0x85; // STAT
    memory_[0xFF42] = 0x00; // SCY
    memory_[0xFF43] = 0x00; // SCX
    memory_[0xFF45] = 0x00; // LYC
    memory_[0xFF46] = 0xFF; // DMA
    memory_[0xFF47] = 0xFC; // BGP
    memory_[0xFF48] = 0xFF; // OBP0
    memory_[0xFF49] = 0xFF; // OBP1
    memory_[0xFF4A] = 0x00; // WY
    memory_[0xFF4B] = 0x00; // WX
    
    // ===== CGB-Specific Registers =====
    if (is_cgb) {
        memory_[0xFF4F] = 0x00; // VBK
        memory_[0xFF4D] = 0x00; // KEY1
        memory_[0xFF70] = 0x01; // SVBK
        memory_[0xFF68] = 0x00; // BCPS
        memory_[0xFF69] = 0x00; // BCPD
        memory_[0xFF6A] = 0x00; // OCPS
        memory_[0xFF6B] = 0x00; // OCPD
        memory_[0xFF51] = 0xFF; // HDMA1
        memory_[0xFF52] = 0xFF; // HDMA2
        memory_[0xFF53] = 0xFF; // HDMA3
        memory_[0xFF54] = 0xFF; // HDMA4
        memory_[0xFF55] = 0xFF; // HDMA5
    }
    
    // ===== Sonido (APU) =====
    // ... (registros NR10-NR52) ...
    
    // ===== Interrupciones =====
    memory_[0xFF0F] = 0x01; // IF
    memory_[0xFFFF] = 0x00; // IE
}

4. Detección Automática de Modo CGB (MMU.cpp - load_rom)

// Leer byte 0x0143 del header (CGB Flag)
uint8_t cgb_flag = (size > 0x0143) ? data[0x0143] : 0x00;
bool is_cgb_rom = (cgb_flag == 0x80 || cgb_flag == 0xC0);

if (is_cgb_rom) {
    set_hardware_mode(HardwareMode::CGB);
    printf("[MMU] ROM CGB detectada (flag=0x%02X). Modo hardware: CGB\n", cgb_flag);
} else {
    set_hardware_mode(HardwareMode::DMG);
    printf("[MMU] ROM DMG detectada (flag=0x%02X). Modo hardware: DMG\n", cgb_flag);
}

5. Wrappers Cython (mmu.pxd, mmu.pyx)

Exposición de funciones a Python:

# mmu.pxd
cdef enum class HardwareMode:
    DMG
    CGB

cdef cppclass MMU:
    # ... métodos existentes ...
    void set_hardware_mode(HardwareMode mode)
    HardwareMode get_hardware_mode()
    void initialize_io_registers()

# mmu.pyx
def set_hardware_mode(self, str mode):
    """Configura el modo de hardware (DMG o CGB)"""
    if mode.upper() == "DMG":
        self._mmu.set_hardware_mode(mmu.HardwareMode.DMG)
    elif mode.upper() == "CGB":
        self._mmu.set_hardware_mode(mmu.HardwareMode.CGB)
    else:
        raise ValueError(f"Modo inválido: {mode}")

def get_hardware_mode(self):
    """Obtiene el modo de hardware actual"""
    cdef mmu.HardwareMode mode = self._mmu.get_hardware_mode()
    return "CGB" if mode == mmu.HardwareMode.CGB else "DMG"

def initialize_io_registers(self):
    """Inicializa registros I/O según el modo actual"""
    self._mmu.initialize_io_registers()

Archivos Afectados

  • src/core/cpp/MMU.hpp - Enum HardwareMode, declaración de métodos, miembro hardware_mode_.
  • src/core/cpp/MMU.cpp - Constructor inicializa modo DMG, implementación de set_hardware_mode(), get_hardware_mode(), initialize_io_registers(), detección automática en load_rom().
  • src/core/cython/mmu.pxd - Declaración del enum y métodos para Cython.
  • src/core/cython/mmu.pyx - Wrappers Python para gestión de modo de hardware.
  • docs/bitacora/entries/2026-01-01__0404__cgb-postboot-cleanroom-y-diagnostico.html - Documentación creada (esta página).
  • docs/bitacora/index.html - Índice actualizado con nueva entrada.
  • docs/informe_fase_2/parte_00_steps_0370_0402.md - Informe actualizado con entrada del Step 0404.

Tests y Verificación

Compilación

$ python3 setup.py build_ext --inplace
=== COMPILACIÓN EXITOSA ===
copying build/lib.linux-x86_64-cpython-312/viboy_core.cpython-312-x86_64-linux-gnu.so ->

Resultado: Compilación exitosa sin errores (solo warnings menores de formato printf).

Test de Detección Automática (Tetris DX - CGB)

$ timeout 5s python3 main.py roms/tetris_dx.gbc > logs/step0404_test_cgb_detection.log 2>&1
$ head -n 50 logs/step0404_test_cgb_detection.log

Salida relevante:

[MMU] Registros I/O inicializados para modo DMG  # Constructor inicial
[MMU] Modo de hardware configurado: CGB          # Detección automática
[MMU] Registros I/O inicializados para modo CGB  # Reinicialización para CGB
[MMU] ROM CGB detectada (flag=0x80). Modo hardware: CGB
[MBC] ROM loaded: 524288 bytes (32 banks) | Type: 0x03

Resultado: ✅ Detección automática correcta. Tetris DX (flag=0x80) detectado como CGB, registros inicializados para modo CGB.

Validación de Módulo Compilado C++

Confirmado que las funciones C++ (set_hardware_mode, get_hardware_mode, initialize_io_registers) son llamadas correctamente desde Python a través de wrappers Cython. Sin errores de segmentación, sin crashes.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Separación DMG/CGB es fundamental: No basta con "soportar CGB". El sistema debe comportarse diferente según el modo.
  • Power Up Sequence varía: CGB tiene registros adicionales (VBK, KEY1, SVBK, paletas, HDMA) que deben inicializarse correctamente.
  • Byte 0x0143 es confiable: El header de la ROM indica claramente si es DMG (otros valores), CGB compatible (0x80), o CGB only (0xC0).
  • Paletas CGB vs BGP: Juegos CGB usan paletas de 15 bits (BGR555) vía BCPS/BCPD, no el registro DMG BGP (0xFF47).
  • Detección automática funciona: Tetris DX (0x80) detectado como CGB, registros inicializados correctamente según Pan Docs.

Lo que Falta Confirmar

  • Instrumentación diagnóstica: Añadir monitors de registros críticos (VBK, KEY1, SVBK, BCPS, HDMA5) para detectar qué condición bloquea Zelda DX/Pokémon (Tarea 0404-2 del plan).
  • Renderizado con paletas CGB: Confirmar que el PPU usa paletas CGB (BCPS/BCPD) en vez de BGP cuando está en modo CGB (Tarea 0404-3 del plan).
  • Tests extensivos: Verificar que Zelda DX/Pokémon Red mejoran objetivamente con esta separación (métricas: unique_tile_ids, tiledata_nonzero, gameplay_state) (Tarea 0404-4 del plan).
  • Double-speed mode: Confirmar comportamiento de KEY1 cuando juegos CGB cambian a modo 8.38 MHz (no crítico para inicialización, pero importante para compatibilidad).

Hipótesis y Suposiciones

  • Hipótesis principal: La separación DMG/CGB eliminará algunos bloqueos de inicialización en Zelda DX/Pokémon, pero aún puede haber problemas con renderizado de paletas CGB (a validar en Tareas 0404-2, 0404-3, 0404-4).
  • Suposición: Los valores de Pan Docs para Power Up Sequence son suficientemente precisos. Si persisten problemas, puede ser necesario ajustar valores específicos según comportamiento de hardware real (requeriría Boot ROM real o tests en hardware físico).

Próximos Pasos

  • [x] Tarea 0404-1: Separar claramente "DMG post-boot" vs "CGB post-boot" (completado en este step).
  • [ ] Tarea 0404-2: Instrumentación para detectar "qué condición espera" Zelda DX/Pokémon (monitors de registros críticos VBK/KEY1/SVBK/BCPS/HDMA5/LYC/IE/IF).
  • [ ] Tarea 0404-3: Ajuste de renderizado para CGB (confirmar que PPU usa paletas CGB en vez de BGP).
  • [ ] Tarea 0404-4: Tests controlados con Tetris DX (baseline), Zelda DX, Pokémon Red (análisis de métricas con límites de contexto).
  • [ ] Tarea 0404-5: Documentación completa del Step 0404 con hallazgos de todas las tareas.