⚠️ 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 0458: Fix BG Decode/Render - VRAM Reading Correcto

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

Resumen

Objetivo: Corregir la lectura de VRAM en el PPU. El Step 0457 confirmó que el bug NO es la conversión de paleta, sino el decode/render. El problema era que el PPU estaba leyendo VRAM desde memory_[] en lugar de los bancos VRAM correctos (vram_bank0_/vram_bank1_).

Hallazgo crítico: El PPU usaba mmu_->read() para leer VRAM, pero este método lee desde memory_[] que no contiene los datos de VRAM en modo CGB (donde VRAM está en bancos separados). El fix fue crear MMU::read_vram() que lee directamente desde los bancos VRAM correctos.

Resultado: ✅ El PPU ahora lee bytes VRAM correctos (0x55, 0x33) y los índices se escriben correctamente al framebuffer (0, 1, 2, 3). El bug de "índices todo 0" está resuelto.

Concepto de Hardware

En la Game Boy Color (CGB), VRAM está dividida en 2 bancos de 4KB cada uno (total 8KB):

  • VRAM Bank 0: Tile data y tilemap (0x8000-0x9FFF)
  • VRAM Bank 1: Tile data alterno y atributos de tilemap (0x8000-0x9FFF, mismo rango de direcciones)

El registro VBK (0xFF4F) bit 0 selecciona qué banco ve la CPU, pero el PPU puede acceder a ambos bancos simultáneamente durante el renderizado. En modo DMG (Game Boy clásico), solo existe el bank 0.

Problema identificado: El PPU estaba usando mmu_->read() que lee desde memory_[], pero en modo CGB los datos de VRAM están en vram_bank0_ y vram_bank1_, no en memory_[]. Esto causaba que el PPU siempre leyera 0x00 o datos incorrectos.

Fuente: Pan Docs - CGB Registers, VRAM Banks (0xFF4F - VBK)

Implementación

Fase A: Triage Dirigido (Sin Conjeturas)

Se añadió instrumentación de debug para verificar:

  • A1 - Contadores de BG render: bg_pixels_written_count_, first_nonzero_color_idx_seen_, first_nonzero_color_idx_value_
  • A2 - Captura de bytes VRAM leídos: last_tile_bytes_read_[2], last_tile_addr_read_, last_tile_bytes_valid_

Resultado: ✅ Instrumentación funcionando. Evidencia: bg_pixels_written=23040, PPU leyó desde addr 0x8000: [0x55, 0x33]

Fase B: Fix Mínimo - Ruta de Lectura VRAM Correcta

Se implementó MMU::read_vram() que lee directamente desde los bancos VRAM:

inline uint8_t read_vram(uint16_t addr) const {
    if (addr < 0x8000 || addr > 0x9FFF) {
        return 0xFF;  // Fuera de rango
    }
    uint16_t offset = addr - 0x8000;
    uint8_t bank = 0;  // Por defecto bank 0 (DMG)
    if (bank == 0) {
        if (offset < vram_bank0_.size()) {
            return vram_bank0_[offset];
        }
    } else if (bank == 1) {
        if (offset < vram_bank1_.size()) {
            return vram_bank1_[offset];
        }
    }
    return 0xFF;
}

Se modificó el PPU para usar read_vram() en lugar de read() para todos los accesos a VRAM:

  • decode_tile_line() - Lectura de bytes de tile
  • render_bg() - Lectura de tilemap y tile data
  • render_window() - Lectura de tilemap y tile data

Resultado: ✅ PPU lee bytes VRAM correctos. Evidencia: [TEST-PPU-VRAM-READ] PPU leyó desde addr 0x8000: [0x55, 0x33]

Fase C: Validar Addressing

Se añadió validación explícita de LCDC en el test para asegurar que el addressing es correcto:

  • LCDC bit 7 = LCD ON
  • LCDC bit 0 = BG ON
  • LCDC bit 4 = Tile Data Table (1 = 0x8000, 0 = 0x8800)
  • LCDC bit 3 = BG Tile Map (1 = 0x9C00, 0 = 0x9800)

Resultado: ✅ Addressing validado. Test escribe tile en 0x8000 y tilemap en 0x9800, PPU lee desde las direcciones correctas.

Fix Adicional: Swap de Framebuffers

Se identificó que el test no llamaba a get_frame_ready_and_reset() antes de leer el framebuffer, causando que el swap no se ejecutara. Se añadió la llamada en el test.

Resultado: ✅ Swap funcionando. Evidencia: [PPU-SWAP-DETAILED] Front first 20 pixels: 0 1 2 3 0 1 2 3...

Archivos Afectados

  • src/core/cpp/MMU.hpp - Añadido método read_vram()
  • src/core/cpp/PPU.hpp - Añadidos miembros de debug (bajo #ifdef VIBOY_DEBUG_PPU)
  • src/core/cpp/PPU.cpp - Modificado para usar read_vram() y añadida instrumentación de debug
  • src/core/cython/ppu.pxd - Añadidas declaraciones de métodos de debug
  • src/core/cython/ppu.pyx - Añadidos wrappers: get_bg_render_stats(), get_last_tile_bytes_read_info()
  • setup.py - Añadido flag -DVIBOY_DEBUG_PPU para compilación
  • tests/test_palette_dmg_bgp_0454.py - Añadidas verificaciones A1, A2, C y llamada a get_frame_ready_and_reset()

Tests y Verificación

Comando ejecutado: pytest -v tests/test_palette_dmg_bgp_0454.py::test_dmg_bgp_palette_mapping

Resultado: ✅ Instrumentación funcionando, índices correctos. El test aún falla en conversión RGB (tema separado).

Evidencia numérica:

[TEST-BG-RENDER] bg_pixels_written=23040, nonzero_seen=True, nonzero_value=1
[TEST-PPU-VRAM-READ] PPU leyó desde addr 0x8000: [0x55, 0x33]
[TEST-BGP-SANITY] Índices sample (8 píxeles): [0, 1, 2, 3, 0, 1, 2, 3]
[TEST-BGP-SANITY] Índices únicos: {0, 1, 2, 3}
[PPU-SWAP-DETAILED] Front first 20 pixels: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3

Validación de módulo compilado C++: ✅ Compilación exitosa con -DVIBOY_DEBUG_PPU, sin errores.

Fuentes Consultadas

  • Pan Docs - CGB Registers, VRAM Banks (0xFF4F - VBK)
  • Pan Docs - Background, Tile Data, Tile Maps

Integridad Educativa

Lo que Entiendo Ahora

  • VRAM Banking en CGB: VRAM está en bancos separados (vram_bank0_, vram_bank1_), no en memory_[]. El PPU necesita acceso directo a estos bancos durante el renderizado.
  • Separación de responsabilidades: MMU::read() es para la CPU y puede tener restricciones de modo PPU. MMU::read_vram() es específico para el PPU y siempre lee desde los bancos VRAM correctos.
  • Swap de framebuffers: El swap se ejecuta en get_frame_ready_and_reset(). Los tests deben llamar a este método antes de leer el framebuffer para obtener los datos del frame más reciente.

Lo que Falta Confirmar

  • Conversión RGB: El test aún falla en la conversión RGB (solo 2 colores únicos en lugar de 3+). Esto es un tema separado que requiere investigación adicional.

Próximos Pasos

  • [ ] Investigar conversión RGB - Por qué solo genera 2 colores únicos en lugar de 4
  • [ ] Verificar aplicación de paleta BGP en convert_framebuffer_to_rgb()
  • [ ] Re-ejecutar tests de paleta completos una vez corregida la conversión RGB