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

Cerrar Ambigüedad PPU vs Paletas vs Presentación

Fecha: 2026-01-06 Step ID: 0489 Estado: VERIFIED

Resumen

Step 0489 implementa instrumentación en tres puntos críticos del pipeline de renderizado para cerrar la ambigüedad sobre dónde ocurre el problema de pantalla en blanco: FB_INDEX (buffer de índices generado por PPU), FB_RGB (buffer RGB después de mapear paletas), y FB_PRESENT_SRC (buffer exacto pasado a SDL/ventana). Adicionalmente, se añaden instrumentaciones para tracking de writes a paletas CGB y contadores de lecturas de tile data DMG. Los resultados revelan que el PPU no está leyendo tile data (TileBytesTotal=0), lo que explica por qué el framebuffer está completamente en blanco.

Concepto de Hardware

El pipeline de renderizado del Game Boy tiene múltiples etapas donde puede ocurrir un problema de pantalla en blanco:

  1. FB_INDEX: El PPU genera un buffer de 160x144 píxeles con índices de color (0-3) basado en datos de tiles de VRAM
  2. FB_RGB: Los índices se mapean a colores RGB usando paletas (DMG: BGP, CGB: paletas CGB)
  3. FB_PRESENT_SRC: El buffer RGB se transfiere a la textura SDL para presentación en pantalla

Para diagnosticar dónde ocurre el problema, necesitamos capturar estadísticas (CRC32, conteos de píxeles no-blancos) en cada uno de estos tres puntos. Si FB_INDEX está en blanco pero FB_RGB no, el problema está en el mapeo de paletas. Si FB_RGB tiene datos pero FB_PRESENT está en blanco, el problema está en el blit/presentación. Si los tres están en blanco, el problema está en el PPU (fetch/decode de tiles).

Referencia: Pan Docs - LCD Control Register (FF40 - LCDC), Background Palette (FF47 - BGP), CGB Palettes (FF68-FF6B), Tile Data (0x8000-0x97FF).

Implementación

Se implementan 5 fases de instrumentación para cerrar la ambigüedad:

Fase A: ThreeBufferStats

Se añade la estructura ThreeBufferStats a PPU.hpp con estadísticas para los tres buffers:

  • FB_INDEX: idx_crc32, idx_unique, idx_nonzero
  • FB_RGB: rgb_crc32, rgb_unique_colors_approx, rgb_nonwhite_count
  • FB_PRESENT_SRC: present_crc32, present_nonwhite_count, present_fmt, present_pitch, present_w, present_h

La función compute_three_buffer_stats() se ejecuta cuando LY=144 y frame_ready_ es true. Para FB_RGB, se maneja tanto modo CGB (usa buffer RGB directamente) como DMG (convierte índices a RGB usando BGP). FB_PRESENT_SRC se captura desde Python en renderer.py justo antes de pygame.display.flip().

Gate: VIBOY_DEBUG_PRESENT_TRACE=1

Fase B: Dump PPM RGB

Se implementa _dump_rgb_framebuffer_to_ppm() en rom_smoke_0442.py que:

  • Lee el framebuffer de índices desde el PPU
  • Convierte índices a RGB usando BGP (DMG) o paletas CGB
  • Escribe un archivo PPM (Netpbm P6) con el framebuffer RGB

Gate: VIBOY_DUMP_RGB_FRAME y VIBOY_DUMP_RGB_PATH

Fase C: CGB Palette Write Stats

Se añade la estructura CGBPaletteWriteStats a MMU.hpp para rastrear writes a paletas CGB:

  • bgpd_write_count, last_bgpd_write_pc, last_bgpd_value, last_bgpi
  • obpd_write_count, last_obpd_write_pc, last_obpd_value, last_obpi

El tracking se realiza en MMU::write() cuando se escriben registros 0xFF68-0xFF6B.

Gate: VIBOY_DEBUG_CGB_PALETTE_WRITES=1

Fase D: DMG Tile Fetch Stats

Se añade la estructura DMGTileFetchStats a PPU.hpp para rastrear lecturas de tile data:

  • tile_bytes_read_total_count: Total de bytes de tile data leídos
  • tile_bytes_read_nonzero_count: Bytes no-cero leídos

El tracking se realiza en PPU::decode_tile_line() cuando se leen bytes de VRAM para decodificar tiles.

Gate: VIBOY_DEBUG_DMG_TILE_FETCH=1

Fase E: Ejecución y Reporte

Se ejecuta rom_smoke_0442.py con todas las flags activas y ROM tetris.gb (DMG). Se genera reporte completo en docs/reports/reporte_step0489.md.

Archivos Afectados

  • src/core/cpp/PPU.hpp - Estructuras ThreeBufferStats y DMGTileFetchStats
  • src/core/cpp/PPU.cpp - Métodos compute_three_buffer_stats() y tracking en decode_tile_line()
  • src/core/cython/ppu.pxd / ppu.pyx - Exposición de estructuras y métodos a Python
  • src/core/cpp/MMU.hpp - Estructura CGBPaletteWriteStats
  • src/core/cpp/MMU.cpp - Tracking de writes a paletas CGB
  • src/core/cython/mmu.pxd / mmu.pyx - Exposición de CGBPaletteWriteStats a Python
  • src/gpu/renderer.py - Captura de FB_PRESENT_SRC desde Pygame Surface
  • tools/rom_smoke_0442.py - Integración de todas las estadísticas y función _dump_rgb_framebuffer_to_ppm()
  • docs/reports/reporte_step0489.md - Reporte completo con análisis de resultados

Tests y Verificación

Se ejecutó rom_smoke_0442.py con ROM tetris.gb (DMG) y flags:

VIBOY_DEBUG_PRESENT_TRACE=1
VIBOY_DEBUG_CGB_PALETTE_WRITES=1
VIBOY_DEBUG_DMG_TILE_FETCH=1
VIBOY_DUMP_RGB_FRAME=5
VIBOY_DUMP_RGB_PATH=docs/reports/dumps/tetris_frame_####.ppm

Resultados de snapshots:

  • Frame 0: ThreeBufferStats=IdxCRC32=0x00000000 IdxUnique=1 IdxNonZero=0 | RgbCRC32=0x00000000 RgbUnique=1 RgbNonWhite=2304 | PresentCRC32=0x00000000 PresentNonWhite=0
  • Frame 1: Similar a Frame 0
  • Frame 2: RgbCRC32=0x4F0D7000 pero RgbNonWhite=0 (inconsistencia detectada)
  • CGBPaletteWriteStats: Sin writes (esperado para DMG)
  • DMGTileFetchStats: TileBytesTotal=0 TileBytesNonZero=0 ⚠️ CRÍTICO

Dump PPM generado: docs/reports/dumps/tetris_frame_0005.ppm (68KB)

Compilación: ✅ Sin errores, test_build.py pasa correctamente

Fuentes Consultadas

  • Pan Docs: LCD Control Register (FF40 - LCDC)
  • Pan Docs: Background Palette (FF47 - BGP)
  • Pan Docs: CGB Palettes (FF68-FF6B)
  • Pan Docs: Tile Data (0x8000-0x97FF)

Integridad Educativa

Lo que Entiendo Ahora

  • Pipeline de renderizado: El proceso de renderizado tiene múltiples etapas (fetch → decode → palette → present) y cada una puede ser el origen de un problema de pantalla en blanco.
  • Instrumentación de tres buffers: Capturar estadísticas en FB_INDEX, FB_RGB y FB_PRESENT permite identificar exactamente dónde ocurre el problema.
  • Tracking de tile fetch: Si el PPU no está leyendo tile data, el framebuffer estará en blanco independientemente de las paletas o la presentación.

Lo que Falta Confirmar

  • Por qué decode_tile_line() no se ejecuta: El tracking muestra TileBytesTotal=0, lo que indica que decode_tile_line() no se está llamando o las lecturas de VRAM están bloqueadas.
  • Locks de VRAM durante modo 3: Necesito verificar si hay locks en MMU.cpp que bloqueen lecturas de VRAM durante el modo Pixel Transfer.
  • Inconsistencia en conversión RGB: Frame 2 muestra RgbCRC32≠0 pero RgbNonWhite=0, lo que sugiere un bug en el muestreo o la conversión.

Hipótesis y Suposiciones

Hipótesis principal: El PPU no está leyendo tile data porque VRAM está vacía (confirmado: 0/6144 bytes no-cero) o porque hay locks que bloquean las lecturas durante el modo 3. Esto explica por qué el framebuffer está completamente en blanco.

Próximos Pasos

  • [ ] Investigar por qué decode_tile_line() no se ejecuta o las lecturas de VRAM están bloqueadas
  • [ ] Revisar locks de VRAM en MMU.cpp durante modo 3 (Pixel Transfer)
  • [ ] Corregir inconsistencia en conversión RGB (muestreo cada 10 píxeles puede estar causando el problema)
  • [ ] Verificar timing: el PPU podría intentar renderizar antes de que VRAM esté lista
  • [ ] Ejecutar con ROM CGB (tetris_dx.gbc) para comparar comportamiento