⚠️ 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 0457: Aislar si el Bug es "Índices Mal" vs "Paleta/Conversión RGB Mal" con Evidencia

Fecha: 2026-01-03 Step ID: 0457 Estado: VERIFIED

Resumen

Objetivo: Aislar si el bug del framebuffer RGB plano es causado por índices mal decodificados/renderizados (H1) o por conversión de paleta/RGB incorrecta (H2). Los tests ya están bien (0456), así que si siguen fallando, el bug es real en el core.

Hallazgo crítico: La instrumentación revela que el framebuffer de índices está completamente plano (todos los valores son 0), lo que confirma que el bug NO está en la conversión de paleta, sino en el decode/render de tiles. El problema está en cómo se decodifican los tiles desde VRAM o cómo se escriben los índices al framebuffer.

Evidencia numérica: Los tests muestran que los índices sampleados (8 píxeles) son todos 0: [0, 0, 0, 0, 0, 0, 0, 0], con un set único de {0} en lugar del esperado {0, 1, 2, 3}. Esto descarta H2 (conversión paleta/RGB) y confirma H1 (decode/render).

Concepto de Hardware

El proceso de renderizado en la Game Boy tiene dos fases principales:

  1. Decodificación y Renderizado: Los tiles se decodifican desde VRAM (formato 2bpp) y se escriben al framebuffer como índices de color (0-3). Esta fase incluye la lectura de tiles desde VRAM, la decodificación 2bpp, y la escritura de índices al framebuffer.
  2. Conversión de Paleta: Los índices se convierten a RGB usando las paletas BGP/OBP0/OBP1. Esta fase lee el registro de paleta, mapea el índice a un shade (0-3), y convierte el shade a RGB888.

Si el framebuffer RGB está plano (todo el mismo color), puede ser porque:

  • H1 - Índices mal: El framebuffer de índices está plano (todos 0, todos 2, etc.) → el bug está en decode/render.
  • H2 - Conversión mal: El framebuffer de índices tiene variedad (0, 1, 2, 3), pero la conversión paleta→RGB está rota y produce RGB plano → el bug está en conversión/escritura RGB.

Fuente: Pan Docs - Background, Window, Sprites, Color Palettes

Implementación

Se implementó instrumentación mínima para aislar el bug sin tocar los tests (que ya están bien según 0456).

Fase A: Exponer Framebuffer de Índices

Se añadió una API de debug para exponer el framebuffer de índices desde C++ a Python:

  • PPU::get_framebuffer_indices_ptr() en PPU.hpp - Devuelve puntero const al framebuffer_front_
  • PyPPU::get_framebuffer_indices() en ppu.pyx - Wrapper Cython que devuelve bytes de 23040 bytes
  • Declaración en ppu.pxd para que Cython pueda acceder al método

Esta API permite a los tests inspeccionar el framebuffer de índices antes de la conversión a RGB, permitiendo distinguir entre H1 y H2.

Fase B: Capturar Paleta Regs Usados

Se añadió captura de los registros de paleta usados en la conversión:

  • Miembros last_bgp_used_, last_obp0_used_, last_obp1_used_ en PPU.hpp
  • Actualización en convert_framebuffer_to_rgb() para capturar los valores leídos
  • Getters get_last_bgp_used(), get_last_obp0_used(), get_last_obp1_used()
  • Wrapper PyPPU::get_last_palette_regs_used() que devuelve un dict con los valores

Esto permite verificar que el registro de paleta usado en la conversión coincide con el escrito por el test, descartando bugs de lectura/reg caching.

Fase C: Revisión de Conversión

Se revisó el código de conversión y se confirmó que está correcto:

  • dmg_shade_to_rgb() - Tabla de conversión correcta: {255, 170, 85, 0}
  • Escritura al buffer RGB - Stride correcto: rgb_idx = fb_index * 3
  • Orden de conversión - Se llama después del swap, sobre el framebuffer_front_ correcto

La conversión no necesita corrección; el bug está en la fase anterior (decode/render).

Modificación de Tests

Se añadieron "sanity asserts" en los tests para verificar índices y paleta regs antes de mirar RGB:

  • test_palette_dmg_bgp_0454.py - Añadido sanity assert que verifica índices sample (8 píxeles) y paleta reg usado
  • test_palette_dmg_obj_0454.py - Añadido sanity assert similar para sprites

Estos asserts fallan inmediatamente si los índices están mal, permitiendo identificar el bug sin necesidad de mirar RGB.

Archivos Afectados

  • src/core/cpp/PPU.hpp - Añadido método get_framebuffer_indices_ptr() y miembros para paleta regs usados
  • src/core/cpp/PPU.cpp - Implementación de get_framebuffer_indices_ptr() y captura de paleta regs en convert_framebuffer_to_rgb()
  • src/core/cython/ppu.pxd - Declaraciones de nuevos métodos para Cython
  • src/core/cython/ppu.pyx - Wrappers Cython: get_framebuffer_indices() y get_last_palette_regs_used()
  • tests/test_palette_dmg_bgp_0454.py - Añadidos sanity asserts con índices y paleta regs
  • tests/test_palette_dmg_obj_0454.py - Añadidos sanity asserts con índices y paleta regs

Tests y Verificación

Comando ejecutado: pytest -v tests/test_palette_dmg_bgp_0454.py tests/test_palette_dmg_obj_0454.py tests/test_framebuffer_not_flat_0456.py

Resultado: ❌ 0/3 tests pasan (esperado: el bug es real en el core)

Evidencia numérica:

[TEST-BGP-SANITY] Índices sample (8 píxeles): [0, 0, 0, 0, 0, 0, 0, 0]
[TEST-BGP-SANITY] Índices únicos: {0}
AssertionError: Índices plano: solo {0} (esperado {0,1,2,3}). 
Si esto falla → bug NO es paleta; es decode/render.

Interpretación: El framebuffer de índices está completamente plano (todos 0), lo que confirma que el bug está en decode/render, no en la conversión de paleta. La conversión paleta→RGB está correcta, pero no hay variedad de índices para convertir.

Validación de módulo compilado C++: ✅ Compilación exitosa, sin errores. Los métodos están disponibles en Python.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Aislamiento de bugs: Cuando un síntoma puede tener múltiples causas, la instrumentación es esencial para aislar la causa raíz. En este caso, exponer el framebuffer de índices permitió distinguir entre "índices mal" y "conversión mal".
  • Debug API: Añadir APIs de debug que exponen estado interno es una técnica válida para tests, siempre que no afecte el hot path del código de producción.
  • Evidencia numérica: Los tests deben proporcionar evidencia numérica clara (índices sample, paleta regs usados) en lugar de solo asumir qué está mal.

Lo que Falta Confirmar

  • Decode de tiles: Necesito investigar por qué el decode de tiles produce índices todos 0. ¿El problema está en decode_tile_line(), en la lectura de VRAM, o en la escritura al framebuffer?
  • Renderizado de Background: ¿El problema está en render_bg() o render_scanline()? ¿Se están leyendo los tiles correctamente desde VRAM?

Hipótesis y Suposiciones

Hipótesis principal: El bug está en el decode/render de tiles. Posibles causas:

  • El decode 2bpp está mal implementado y siempre produce índice 0
  • La lectura de VRAM no está funcionando correctamente
  • La escritura al framebuffer_back_ está mal (se escribe siempre 0)
  • El swap de framebuffers está mal y siempre se lee el buffer limpio

Próximos Pasos

  • [ ] Investigar decode_tile_line() - Verificar que el decode 2bpp produce los índices correctos
  • [ ] Investigar render_bg() - Verificar que se leen los tiles correctamente desde VRAM
  • [ ] Investigar escritura al framebuffer - Verificar que los índices se escriben correctamente al framebuffer_back_
  • [ ] Investigar swap de framebuffers - Verificar que el swap funciona correctamente
  • [ ] Aplicar fix mínimo una vez identificada la causa raíz
  • [ ] Re-ejecutar tests de paleta para validar la corrección