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

Step 0395 - Diagnóstico Visual: Verificar Correspondencia Framebuffer vs Métricas VRAM

Fecha: 2025-12-31 Step ID: 0395 Estado: VERIFIED

Resumen

Este step implementa un sistema completo de diagnóstico visual para verificar la correspondencia entre las métricas VRAM (que reportan valores correctos desde Step 0394) y el contenido real del framebuffer C++. Se implementan 5 funciones de verificación que capturan snapshots del framebuffer, validan la correspondencia tilemap→framebuffer, verifican scroll/wrap-around, validan la aplicación de paleta BGP, y verifican el pipeline C++→Python. Los resultados revelan discrepancias críticas: Frame 676 muestra framebuffer completamente blanco (0=23040) mientras las métricas VRAM reportan TileData 14.2%, y Frame 742 muestra BGP=0x00 causando mapeo incorrecto de colores.

Concepto de Hardware

El pipeline de renderizado del Game Boy sigue este flujo (según Pan Docs):

  1. Tilemap → Tile ID: El PPU lee el tilemap (0x9800-0x9FFF) usando coordenadas (map_x, map_y) calculadas con SCX/SCY, obteniendo un tile_id (0-255).
  2. Tile ID → Tile Address: Según el modo de direccionamiento (unsigned: 0x8000 + tile_id*16, signed: 0x9000 + (tile_id-128)*16), se calcula la dirección del tile en VRAM.
  3. Decodificación 2bpp: Cada línea del tile (8 píxeles) ocupa 2 bytes consecutivos. Se decodifica: color_index = (bit_high << 1) | bit_low (valores 0-3).
  4. Aplicación de Paleta BGP: El registro BGP (0xFF47) mapea cada color_index a un índice final: final_color = (BGP >> (color_index * 2)) & 0x03.
  5. Escritura en Framebuffer: El valor final (0-3) se escribe en framebuffer_back_[ly * 160 + x].
  6. Swap de Buffers: Al completar el frame (LY=144), se intercambian framebuffer_back_framebuffer_front_.
  7. Pipeline Python: Cython lee framebuffer_front_ → NumPy → Pygame aplica colores RGB según paleta.

Problema identificado: Las métricas VRAM (Step 0394) reportan valores correctos (TileData 14.2%, TileMap 100%), pero visualmente Tetris DX muestra texto fragmentado y Zelda DX reportó "no ve nada" (Step 0390). Esto sugiere una desconexión entre el contenido de VRAM y lo que se renderiza en el framebuffer.

Implementación

Se implementaron 5 funciones de diagnóstico en C++ y una verificación en Python:

1. Snapshot del Framebuffer C++

Función dump_framebuffer_snapshot() que captura la distribución de valores (0, 1, 2, 3) en frames clave (1, 676, 742, 1080). Analiza por regiones (top/mid/bottom) y cuenta líneas con datos vs líneas completamente blancas.

2. Verificación Tilemap → Framebuffer

Función verify_tilemap_to_framebuffer() que valida línea por línea (LY=0, 72, 143) que los tiles referenciados por el tilemap se renderizan correctamente. Lee bytes del tile desde VRAM, decodifica manualmente los primeros 4 píxeles, y compara con los valores escritos en el framebuffer.

3. Verificación Scroll y Wrap-around

Función verify_scroll_wraparound() que verifica SCX/SCY y el wrap-around del tilemap en frames 676 y 742. Muestra map_x, map_y, tilemap_addr, tile_id, y flags de wrap-around.

4. Verificación de Paleta BGP

Función verify_palette_bgp() que confirma que la paleta mapea correctamente los índices de color. Compara el color_index decodificado, BGP leído, final_color calculado, y valor en framebuffer.

5. Verificación Pipeline C++ → Python

Función get_framebuffer_snapshot() en Cython que retorna un array NumPy del framebuffer completo. En Python, se verifica la distribución de valores en frames 676 y 742 y se compara con el snapshot C++.

Componentes creados/modificados

  • src/core/cpp/PPU.cpp: 5 funciones de diagnóstico agregadas
  • src/core/cpp/PPU.hpp: Declaraciones de funciones agregadas
  • src/core/cython/ppu.pyx: Función get_framebuffer_snapshot() agregada
  • src/viboy.py: Verificación Python del pipeline agregada

Archivos Afectados

  • src/core/cpp/PPU.cpp - Funciones de diagnóstico agregadas
  • src/core/cpp/PPU.hpp - Declaraciones de funciones agregadas
  • src/core/cython/ppu.pyx - Función get_framebuffer_snapshot() agregada
  • src/viboy.py - Verificación Python del pipeline agregada

Tests y Verificación

Se ejecutaron tests con ROMs específicas y se analizaron logs:

  • ROMs de test: Tetris DX (30 segundos), Zelda DX (30 segundos)
  • Logs generados: logs/step0395_tetris_dx.log, logs/step0395_zelda_dx.log
  • Análisis: Comandos grep con límites para evitar saturación de contexto

Resultados Clave

Frame ROM Framebuffer Distribution Observación
1 Tetris DX 0=11520, 3=11520 Checkerboard correcto (VRAM vacía)
676 Tetris DX 0=23040 (todo blanco) ⚠️ PROBLEMA: Framebuffer vacío aunque VRAM tiene 14.2% TileData
742 Tetris DX 0=22560, 1=360, 3=120 Algunos datos pero muy pocos (3 líneas con datos)
1080 Tetris DX 0=130, 1=12295, 2=3262, 3=7353 ✅ Datos completos (144 líneas con datos)

Hallazgos Críticos

  • Frame 676: Framebuffer completamente blanco (0=23040) mientras métricas VRAM reportan TileData 14.2%. Esto confirma la desconexión entre VRAM y renderizado.
  • Frame 742: BGP=0x00 detectado, causando que todos los colores se mapeen a 0 (blanco). Esto explica la fragmentación visual.
  • Tilemap → Framebuffer: Discrepancias detectadas - tiles vacíos (0x00) pero framebuffer tiene valores diferentes (checkerboard activo).
  • Pipeline Python: La distribución en Python coincide con C++, confirmando que el problema está en el renderizado C++, no en el pipeline.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Pipeline de Renderizado: El flujo completo desde tilemap hasta framebuffer, incluyendo decodificación 2bpp y aplicación de paleta BGP.
  • Doble Buffering: El sistema de swap de buffers (front/back) previene condiciones de carrera pero requiere verificación del contenido después del swap.
  • Diagnóstico Visual: La importancia de capturar snapshots en frames clave para identificar dónde se pierde la correspondencia entre VRAM y visualización.

Lo que Falta Confirmar

  • BGP=0x00 en Frame 742: Por qué el registro BGP está en 0x00 cuando debería tener un valor válido. ¿Es un problema de inicialización o el juego lo está escribiendo?
  • Framebuffer vacío en Frame 676: Si VRAM tiene 14.2% TileData, ¿por qué el framebuffer está completamente blanco? ¿El tilemap apunta a tiles vacíos o hay un problema en el renderizado?
  • Fragmentación visual: Si el framebuffer tiene datos correctos en Frame 1080, ¿por qué se ve fragmentado? ¿Es un problema de timing o de aplicación de paleta?

Hipótesis y Suposiciones

Hipótesis principal: El problema está en la aplicación de la paleta BGP o en el momento en que se lee BGP. Si BGP=0x00, todos los color_index se mapean a 0 (blanco), lo que explicaría por qué el framebuffer está vacío aunque VRAM tenga datos.

Próximos Pasos

  • [ ] Investigar por qué BGP=0x00 en Frame 742 - ¿el juego lo escribe o es un problema de inicialización?
  • [ ] Verificar si el tilemap apunta a tiles vacíos en Frame 676 aunque VRAM tenga datos
  • [ ] Implementar corrección para asegurar que BGP tenga un valor válido durante el renderizado
  • [ ] Verificar timing de lectura de BGP - ¿se lee antes o después de que el juego lo actualice?