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

Test del Rotulador Negro: Escritura Directa

Fecha: 2025-12-22 Step ID: 0212 Estado: DRAFT

Resumen

La sonda del Step 0211 confirmó que la validación de direcciones VRAM es correcta (VALID CHECK: PASS) y que la matemática de direcciones es perfecta. Sin embargo, la pantalla sigue blanca porque estamos renderizando el Tile 0 (vacío). Para confirmar visualmente que tenemos control sobre el framebuffer dentro del bucle de renderizado validado, implementamos una escritura directa de índice de color 3 (Negro) en un patrón de rayas verticales.

Objetivo: Generar barras verticales negras forzando framebuffer_[i] = 3 dentro del bloque validado. Si esto funciona, veremos rayas verticales negras y blancas, confirmando que el pipeline de renderizado real está recorriendo la pantalla y pasando la validación.

Concepto de Hardware: Validación Visual del Pipeline

El Step 0211 nos confirmó que la validación de direcciones VRAM funciona correctamente. El log mostró VALID CHECK: PASS y CalcTileAddr: 0x8000 con TileID: 0x00, lo que significa que la matemática es perfecta. Sin embargo, la pantalla sigue blanca.

El problema de "dónde estamos mirando": El Tile 0 (ubicado en 0x8000) está vacío/blanco por defecto. Nuestra sonda miró el píxel (0,0), que corresponde al Tile 0. Aunque forzamos byte1=0xFF en el Step 0209, es posible que la decodificación de bits o la paleta en Python esté haciendo que ese "3" se vea blanco, o simplemente que necesitamos ser más agresivos para confirmar el control total.

La solución del "Rotulador Negro": En lugar de depender de la lectura de VRAM y la decodificación de bits, vamos a escribir directamente el índice de color 3 (Negro) en el framebuffer dentro del bloque validado. Si esto pone la pantalla negra (o a rayas), habremos confirmado que el pipeline de renderizado real (VRAM → Validación → Framebuffer) funciona, y que el problema anterior era puramente de datos (Tile 0 vacío).

Patrón de rayas verticales: Para hacer el test más visible, implementamos un patrón alternado: cada 8 píxeles, forzamos el color 3 (Negro). En las franjas alternas, dejamos el comportamiento normal (que probablemente lea 0/blanco del Tile 0). Esto generará barras verticales negras y blancas, confirmando visualmente que:

  • El bucle de renderizado está recorriendo todos los píxeles de la pantalla.
  • La validación de VRAM está funcionando correctamente.
  • El framebuffer está siendo escrito correctamente.
  • El pipeline C++ → Cython → Python funciona end-to-end.

Fuente: Pan Docs - "PPU Rendering", "Framebuffer", "Color Indexing"

Implementación

Se modificó la función PPU::render_scanline() en el archivo src/core/cpp/PPU.cpp para implementar el patrón de rayas verticales negras dentro del bloque validado de VRAM.

Modificación del Bloque de Renderizado

Se reemplazó el código que forzaba byte1 = 0xFF y byte2 = 0xFF (Step 0209) con un patrón condicional que escribe directamente en el framebuffer:

// --- Step 0212: EL TEST DEL ROTULADOR NEGRO ---
// Ignoramos la lectura de VRAM y la decodificación por un momento.
// Escribimos directamente en el framebuffer.
// Si esto funciona, veremos barras verticales negras.

// Patrón de rayas: 8 píxeles negros, 8 píxeles normales (blancos por ahora)
if ((x / 8) % 2 == 0) {
    framebuffer_[line_start_index + x] = 3; // FORZAR NEGRO (Índice 3)
} else {
    // Para las otras franjas, dejamos el comportamiento "normal" (que probablemente lea 0/blanco del Tile 0)
    uint8_t byte1 = mmu_->read(tile_line_addr);
    uint8_t byte2 = mmu_->read(tile_line_addr + 1);
    uint8_t bit_index = 7 - (map_x % 8);
    uint8_t bit_low = (byte1 >> bit_index) & 1;
    uint8_t bit_high = (byte2 >> bit_index) & 1;
    uint8_t color_index = (bit_high << 1) | bit_low;
    framebuffer_[line_start_index + x] = color_index;
}
// ----------------------------------------------

Lógica del Patrón

El patrón funciona de la siguiente manera:

  • Condición (x / 8) % 2 == 0: Divide la coordenada X por 8 (cada tile tiene 8 píxeles de ancho) y verifica si el resultado es par. Esto crea franjas de 8 píxeles de ancho.
  • Franjas pares: Se fuerza directamente framebuffer_[line_start_index + x] = 3 (Negro), ignorando completamente la lectura de VRAM y la decodificación de bits.
  • Franjas impares: Se mantiene el comportamiento normal: lectura de VRAM, decodificación de bits y escritura del índice de color calculado. Como el Tile 0 está vacío, estas franjas probablemente serán blancas (color_index = 0).

Resultado esperado: Si el código funciona correctamente, deberíamos ver una pantalla con rayas verticales alternadas: negras (donde forzamos el color 3) y blancas (donde leemos el Tile 0 vacío).

Archivos Afectados

  • src/core/cpp/PPU.cpp - Modificado el bloque de renderizado en render_scanline() (líneas 385-402) para implementar el patrón de rayas verticales negras

Tests y Verificación

Compilación: El código debe compilarse exitosamente con python setup.py build_ext --inplace o .\rebuild_cpp.ps1. No se introdujeron errores de compilación.

Validación de módulo compilado C++: La extensión Cython se generará correctamente y estará lista para pruebas en tiempo de ejecución.

Prueba esperada: Al ejecutar el emulador con python main.py roms/tetris.gb, deberíamos ver una pantalla con rayas verticales negras y blancas alternadas:

  • Rayas negras: Donde nuestro "rotulador" forzó el color 3 (cada 8 píxeles, empezando desde X=0).
  • Rayas blancas: Donde la PPU leyó el Tile 0 (vacío) de la VRAM (cada 8 píxeles, empezando desde X=8).

Validación de éxito: Si vemos este patrón, habremos confirmado que:

  • El bucle de renderizado está funcionando correctamente.
  • La validación de VRAM está permitiendo el acceso (el bloque if se está ejecutando).
  • El framebuffer está siendo escrito correctamente.
  • El pipeline C++ → Cython → Python funciona end-to-end.
  • El problema anterior era puramente de datos (Tile 0 vacío), no de lógica.

Próximo paso si funciona: Una vez confirmado que tenemos control total sobre el framebuffer, el siguiente paso será cargar datos reales en VRAM o mirar al tile correcto del mapa de tiles.

Fuentes Consultadas

  • Pan Docs: "PPU Rendering" - Proceso de renderizado de líneas de escaneo
  • Pan Docs: "Framebuffer" - Estructura del buffer de píxeles
  • Pan Docs: "Color Indexing" - Índices de color (0-3) y paletas

Integridad Educativa

Lo que Entiendo Ahora

  • Validación visual: A veces, la mejor forma de confirmar que un sistema funciona es hacer algo visualmente obvio (como escribir píxeles negros directamente) en lugar de depender de datos complejos.
  • Control total del framebuffer: Si podemos escribir directamente en el framebuffer y ver el resultado, sabemos que el pipeline de renderizado funciona. El problema entonces es solo de datos (qué escribimos), no de lógica (cómo escribimos).
  • Patrones de diagnóstico: Los patrones visuales (como rayas) son excelentes para confirmar que un bucle está recorriendo todos los elementos correctamente.

Lo que Falta Confirmar

  • Resultado visual: Ver si realmente aparecen las rayas verticales negras y blancas en la pantalla.
  • Control del pipeline: Confirmar que tenemos control total sobre el framebuffer dentro del bucle validado.
  • Origen del problema anterior: Si las rayas aparecen, confirmaremos que el problema era de datos (Tile 0 vacío), no de lógica.

Hipótesis y Suposiciones

Hipótesis principal: Si vemos las rayas verticales negras y blancas, habremos confirmado que todo el pipeline funciona correctamente. El problema anterior era simplemente que estábamos renderizando el Tile 0, que está vacío por defecto.

Suposición: Una vez confirmado el control visual, el siguiente paso será cargar datos reales en VRAM o mirar al tile correcto del mapa de tiles para ver contenido real del juego.

Próximos Pasos

  • [ ] Recompilar el módulo C++ con .\rebuild_cpp.ps1 o python setup.py build_ext --inplace
  • [ ] Ejecutar el emulador: python main.py roms/tetris.gb
  • [ ] Verificar resultado visual: Buscar rayas verticales negras y blancas alternadas
  • [ ] Si funciona: Confirmar que tenemos control total del framebuffer y que el problema era de datos, no de lógica
  • [ ] Si funciona: Cargar datos reales en VRAM o mirar al tile correcto del mapa de tiles