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
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):
- 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).
- 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.
- 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). - Aplicación de Paleta BGP: El registro BGP (0xFF47) mapea cada color_index a un índice final:
final_color = (BGP >> (color_index * 2)) & 0x03. - Escritura en Framebuffer: El valor final (0-3) se escribe en
framebuffer_back_[ly * 160 + x]. - Swap de Buffers: Al completar el frame (LY=144), se intercambian
framebuffer_back_↔framebuffer_front_. - 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 agregadassrc/core/cpp/PPU.hpp: Declaraciones de funciones agregadassrc/core/cython/ppu.pyx: Funciónget_framebuffer_snapshot()agregadasrc/viboy.py: Verificación Python del pipeline agregada
Archivos Afectados
src/core/cpp/PPU.cpp- Funciones de diagnóstico agregadassrc/core/cpp/PPU.hpp- Declaraciones de funciones agregadassrc/core/cython/ppu.pyx- Función get_framebuffer_snapshot() agregadasrc/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?