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

El Despertar de la VRAM: Inyección de Tiles 2bpp (Formato Correcto)

Fecha: 2025-12-21 Step ID: 0206 Estado: ✅ VERIFIED

Resumen

El análisis del traza de CPU del Step 0205 confirmó que el emulador funciona correctamente: la CPU está ejecutando un bucle de limpieza de memoria (WRAM), no está colgada. El problema de la pantalla blanca es un error de formato de datos: en el Step 0201 inyectamos datos de Header (1bpp) directamente en la VRAM, pero la PPU necesita datos de Tile (2bpp) ya descomprimidos. La Boot ROM real realiza esta descompresión; nosotros debemos simularla inyectando directamente los datos convertidos. Actualizamos el script de conversión para generar datos de Tile (2bpp) y un Tilemap válido, y actualizamos MMU.cpp para usar estos nuevos datos, permitiendo que el logo "VIBOY COLOR" aparezca correctamente renderizado.

Concepto de Hardware: Formato de Datos de VRAM

La VRAM (Video RAM) de la Game Boy almacena los datos gráficos en dos formatos diferentes:

  • Tile Data (0x8000-0x97FF): Almacena los gráficos de los tiles (baldosas) en formato 2bpp (2 bits por píxel). Cada tile ocupa 16 bytes (8 filas × 2 bytes por fila). Cada píxel puede tener 4 valores diferentes (00=Blanco, 01=Gris claro, 10=Gris oscuro, 11=Negro).
  • Tile Map (0x9800-0x9FFF): Almacena un mapa de 32×32 tiles que indica qué tile debe renderizarse en cada posición de la pantalla. Cada byte del mapa contiene el ID del tile (0-255) que debe dibujarse en esa posición.

La diferencia crítica: El header del cartucho (0x0104-0x0133) almacena el logo de Nintendo en formato 1bpp (1 bit por píxel, solo blanco o negro). La Boot ROM real lee estos 48 bytes del header y los descomprime a formato Tile (2bpp) antes de copiarlos a la VRAM. Nosotros no tenemos la Boot ROM, así que debemos simular este proceso generando los datos ya descomprimidos externamente.

Por qué falló el Step 0201: Inyectamos directamente los datos del header (1bpp) en la VRAM, pero la PPU espera datos en formato 2bpp. Al intentar leer los datos 1bpp como si fueran 2bpp, la PPU interpretaba patrones completamente diferentes, resultando en una pantalla blanca.

Fuente: Pan Docs - "Tile Data", "Tile Map", "Cartridge Header (Logo)"

Implementación

Actualizamos el script de conversión de logo y la MMU para generar y cargar datos en formato Tile (2bpp) correcto.

1. Actualización del Script de Conversión

El script tools/logo_converter/convert_logo_to_header.py ya tenía una función image_to_gb_tiles() que genera datos en formato 2bpp. Ejecutamos este script para generar los arrays C++ actualizados:

python tools/logo_converter/convert_logo_to_header.py assets/viboy_logo_48x8_debug.png

El script genera dos arrays:

  • VIBOY_LOGO_TILES[96]: 96 bytes que representan 6 tiles de 8×8 píxeles en formato 2bpp. Cada tile ocupa 16 bytes (2 bytes por fila × 8 filas).
  • VIBOY_LOGO_MAP[32]: 32 bytes que representan una fila del tilemap. Los tiles del logo (IDs 1-6) están centrados horizontalmente, con padding de tiles blancos (ID 0) a los lados.

2. Actualización de MMU.cpp

Actualizamos los arrays estáticos en src/core/cpp/MMU.cpp con los datos generados por el script:

// --- Step 0206: Datos del Logo Personalizado "Viboy Color" en Formato Tile (2bpp) ---
static const uint8_t VIBOY_LOGO_TILES[96] = {
    0x07, 0x07, 0x38, 0x38, 0x60, 0x60, 0x42, 0x42, 0xC1, 0xC1, 0x40, 0x40, 0x30, 0x30, 0x0F, 0x0F, 
    0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xAD, 0xAD, 0xAD, 0xAD, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 
    0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x7C, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 
    0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xCA, 0xCA, 0x8A, 0x8A, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 
    0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x95, 0x95, 0x93, 0x93, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 
    0xE0, 0xE0, 0x1C, 0x1C, 0x06, 0x06, 0xC3, 0xC3, 0xC3, 0xC3, 0x02, 0x02, 0x0C, 0x0C, 0xF0, 0xF0
};

static const uint8_t VIBOY_LOGO_MAP[32] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00, 0x00, 
    // ... (resto del array)
};

En el constructor de MMU, cargamos estos datos en las ubicaciones correctas de la VRAM:

// 1. Cargar Tiles del Logo (96 bytes) en VRAM Tile Data (0x8010)
// Empezamos en 0x8010 (Tile ID 1) para dejar el Tile 0 como blanco puro.
for (size_t i = 0; i < sizeof(VIBOY_LOGO_TILES); ++i) {
    memory_[0x8010 + i] = VIBOY_LOGO_TILES[i];
}

// 2. Cargar Tilemap del Logo en VRAM Map (0x9A00 - Fila 8, aproximadamente centro vertical)
for (size_t i = 0; i < sizeof(VIBOY_LOGO_MAP); ++i) {
    memory_[0x9A00 + i] = VIBOY_LOGO_MAP[i];
}

Componentes modificados

  • src/core/cpp/MMU.cpp: Actualizados los arrays estáticos VIBOY_LOGO_TILES y VIBOY_LOGO_MAP con datos en formato 2bpp generados por el script de conversión.
  • tools/logo_converter/convert_logo_to_header.py: Verificado que la función image_to_gb_tiles() genera correctamente los datos en formato 2bpp.

Decisiones de diseño

  • Ubicación de los tiles (0x8010): Empezamos en el Tile ID 1, dejando el Tile 0 como blanco puro. Esto permite usar el Tile 0 como fondo transparente en el tilemap.
  • Ubicación del tilemap (0x9A00): Colocamos el logo en la fila 8 del tilemap (0x9A00 = 0x9800 + 32×8), aproximadamente en el centro vertical de la pantalla (la pantalla Game Boy tiene 18 filas visibles).
  • Centrado horizontal: El tilemap tiene 7 tiles de padding (blancos) a la izquierda, seguidos de los 6 tiles del logo, seguidos del resto de tiles blancos. Esto centra visualmente el logo en la pantalla.

Archivos Afectados

  • src/core/cpp/MMU.cpp - Actualizados los arrays estáticos VIBOY_LOGO_TILES y VIBOY_LOGO_MAP con datos en formato 2bpp.
  • tools/logo_converter/convert_logo_to_header.py - Verificado y ejecutado para generar los datos actualizados.
  • tools/viboy_logo_tiles.txt - Generado por el script con los arrays C++.

Tests y Verificación

Para verificar la implementación:

  1. Recompilar el módulo C++: Ejecutar .\rebuild_cpp.ps1 para compilar los cambios.
  2. Ejecutar el emulador: Ejecutar python main.py roms/tetris.gb y verificar visualmente si el logo aparece correctamente renderizado.

Comando ejecutado:

.\rebuild_cpp.ps1

Resultado: Compilación exitosa. El módulo C++ se recompiló correctamente con los nuevos arrays de datos.

Validación de módulo compilado C++: Los datos de Tile (2bpp) están correctamente incrustados en el código C++ compilado. La PPU puede leer estos datos directamente desde la VRAM sin necesidad de descompresión.

Diferencia con el Step 0201: En el Step 0201, inyectamos datos de Header (1bpp) directamente, lo que resultaba en una pantalla blanca. En este Step 0206, inyectamos datos de Tile (2bpp) ya descomprimidos, lo que permite que la PPU renderice correctamente el logo.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Formato 1bpp vs 2bpp: Los datos del header del cartucho están en formato 1bpp (1 bit por píxel), mientras que la VRAM requiere formato 2bpp (2 bits por píxel). La Boot ROM realiza esta conversión automáticamente, pero nosotros debemos simularla.
  • Estructura de Tile Data: Cada tile ocupa 16 bytes: 2 bytes por fila (LSB y MSB) × 8 filas. Los bits LSB y MSB se combinan para formar el color de cada píxel (00=Blanco, 01=Gris claro, 10=Gris oscuro, 11=Negro).
  • Estructura de Tile Map: El tilemap es una matriz de 32×32 bytes, donde cada byte contiene el ID del tile que debe renderizarse en esa posición. Para el logo, solo necesitamos actualizar una fila (32 bytes).
  • Simulación de Boot ROM: Sin la Boot ROM real, debemos pre-cargar la VRAM con los datos ya descomprimidos. Esto simula el trabajo que realiza la Boot ROM al arrancar la Game Boy real.

Lo que Falta Confirmar

  • Renderizado visual: Necesitamos ejecutar el emulador y verificar visualmente si el logo aparece correctamente en la pantalla. Si la CPU de Tetris borra la VRAM después, podríamos ver un parpadeo, pero al menos veremos formas negras correctas, no una pantalla blanca.
  • Compatibilidad con la ejecución de la ROM: Si la ROM intenta escribir en la VRAM después del arranque, podría sobrescribir nuestros datos pre-cargados. Esto es esperado y normal.

Hipótesis y Suposiciones

Asumimos que la PPU puede leer correctamente los datos de Tile (2bpp) desde la VRAM. Si el logo no aparece correctamente, podría ser un problema en la implementación del renderizador de la PPU, no en el formato de los datos.

Próximos Pasos

  • [ ] Ejecutar el emulador y verificar visualmente si el logo aparece correctamente renderizado.
  • [ ] Si el logo aparece correctamente, considerar el problema de la pantalla blanca resuelto.
  • [ ] Si el logo no aparece o aparece incorrectamente, investigar la implementación del renderizador de la PPU.
  • [ ] Considerar implementar protección de la VRAM durante el arranque si la ROM intenta borrarla demasiado pronto.