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

Detectar Clear VRAM y Carga de Tiles

Fecha: 2025-12-29 Step ID: 0492 Estado: VERIFIED

Resumen

Este step implementa tracking detallado de escrituras a VRAM (especialmente tiledata) para diagnosticar por qué el emulador muestra pantalla en blanco en modo DMG y CGB. Se añadieron métricas para detectar cuándo se completa el "clear VRAM" inicial (6144 bytes de tiledata escritos con cero) y si hay writes no-cero después del clear (indicando carga de tiles). Se implementó un A/B test comparando dos perfiles post-boot DMG diferentes (A: LCDC=0x00, B: LCDC=0x91). Resultado clave: CGB SÍ está cargando tiles (first non-zero write en frame 174), pero los framebuffers siguen siendo blancos, lo que indica un problema en el pipeline de renderizado. DMG no muestra carga de tiles en ningún perfil.

Concepto de Hardware

VRAM (Video RAM): Región de memoria 0x8000-0x9FFF donde se almacenan los datos gráficos del Game Boy. Se divide en:

  • Tiledata (0x8000-0x97FF): Datos de píxeles para tiles de 8x8. 384 tiles × 16 bytes = 6144 bytes.
  • Tilemap (0x9800-0x9FFF): Mapa de IDs de tiles que referencia tiledata. 32×32 tiles = 1024 bytes por mapa.

Clear VRAM: Muchos juegos escriben ceros a toda la región de tiledata durante la inicialización para "limpiar" la VRAM antes de cargar los datos gráficos reales. Este proceso típicamente escribe 6144 bytes (todo el tiledata bank 0) con valor 0x00.

Post-Boot State: El estado inicial de los registros I/O después de que la Boot ROM termina. Según Pan Docs, LCDC debe estar OFF (0x00) inicialmente, pero algunos emuladores usan LCDC=0x91 (LCD ON, BG y Window habilitados) como estado alternativo. El A/B test compara ambos estados para determinar cuál permite que el juego progrese correctamente.

Referencia: Pan Docs - "VRAM", "LCDC Register (0xFF40)", "Power Up Sequence"

Implementación

Fase A: Tracking de Clear VRAM

A1: Métricas en VRAMWriteStats

Se añadieron nuevos campos a `struct VRAMWriteStats` en `MMU.hpp`:

  • tiledata_clear_done_frame: Frame en el que se completó el clear (cuando se alcanzan 6144 intentos)
  • tiledata_attempts_after_clear: Contador de intentos de escritura después del clear
  • tiledata_nonzero_after_clear: Contador de writes no-cero después del clear
  • tiledata_first_nonzero_frame, tiledata_first_nonzero_pc, tiledata_first_nonzero_addr, tiledata_first_nonzero_val: Información del primer write no-cero
  • tiledata_write_ring_: Ring buffer de 128 eventos de writes recientes

A2: Lógica de Tracking en MMU::write()

Se implementó la lógica en `MMU::write()` para detectar el clear y trackear writes posteriores:

  • Cuando se escribe a tiledata (0x8000-0x97FF), se incrementa tiledata_attempts_bank0
  • Cuando se alcanzan 6144 intentos, se marca tiledata_clear_done_frame con el frame actual
  • Después del clear, se trackean todos los writes a tiledata y se cuenta cuántos son no-cero
  • El primer write no-cero se registra en los campos tiledata_first_nonzero_*
  • Los writes no-cero se añaden a un ring buffer para análisis posterior

A3: Exposición a Python

Se actualizó PyMMU::get_vram_write_stats() en mmu.pyx para exponer todos los nuevos campos, incluyendo el ring buffer convertido a una lista de diccionarios Python.

A4: Integración en Snapshots

Se modificó rom_smoke_0442.py para incluir las nuevas métricas en los snapshots generados, permitiendo análisis post-mortem de los datos.

Fase B: Modo "Stop Early"

B1: Argumento --stop-early-on-first-nonzero

Se añadió el argumento --stop-early-on-first-nonzero a rom_smoke_0442.py que detiene la emulación cuando se detecta el primer write no-cero a tiledata después del clear, o después de 3000 frames si no se detecta ningún write no-cero.

B2: Sección "AfterClear" en Snapshots

Se añadió una sección "AfterClear" a los snapshots que captura métricas solo después de que se complete el clear VRAM:

  • frames_since_clear: Frames transcurridos desde el clear
  • pc_hotspots_top3: Top 3 direcciones PC más ejecutadas después del clear
  • io_reads_top3: Top 3 registros I/O más leídos después del clear

Fase C: A/B Test Post-Boot DMG

C1: Perfil B (Alternativo)

Se implementó init_post_boot_dmg_state_profile_b() que establece un estado post-boot alternativo con LCDC=0x91 (LCD operativo) en lugar de LCDC=0x00 (LCD OFF).

C2: Gate por Variable de Entorno

Se modificó MMU::initialize_io_registers() para seleccionar entre Perfil A (default) y Perfil B basándose en la variable de entorno VIBOY_POST_BOOT_DMG_PROFILE.

Fase D: CGB Present-Trace

Se ejecutó rom_smoke_0442.py para tetris_dx.gbc con las variables de entorno necesarias para diagnosticar el problema de presentación en CGB:

  • VIBOY_DEBUG_PRESENT_TRACE=1: Habilita tracing del pipeline de presentación
  • VIBOY_DUMP_RGB_FRAME=180: Dump del framebuffer RGB en frame 180
  • VIBOY_DUMP_IDX_FRAME=180: Dump del framebuffer de índices en frame 180
  • VIBOY_DUMP_PRESENT_FRAME=180: Dump del framebuffer de presentación en frame 180

Archivos Afectados

  • src/core/cpp/MMU.hpp - Añadidos campos a VRAMWriteStats y método init_post_boot_dmg_state_profile_b()
  • src/core/cpp/MMU.cpp - Implementado tracking de clear VRAM y perfil B post-boot
  • src/core/cython/mmu.pxd - Actualizado struct VRAMWriteStats y añadido TiledataWriteEvent
  • src/core/cython/mmu.pyx - Exposición de nuevos campos a Python
  • tools/rom_smoke_0442.py - Modo "stop early" y sección "AfterClear" en snapshots
  • docs/reports/reporte_step0492.md - Reporte comparativo completo

Tests y Verificación

Se ejecutaron tres tests principales:

  • DMG Profile A: rom_smoke_0442.py roms/tetris.gb --frames 3000 --stop-early-on-first-nonzero con VIBOY_POST_BOOT_DMG_PROFILE=A
  • DMG Profile B: Mismo comando con VIBOY_POST_BOOT_DMG_PROFILE=B
  • CGB: rom_smoke_0442.py roms/tetris_dx.gbc --frames 3000 --stop-early-on-first-nonzero con tracing de presentación habilitado

Resultados clave:

  • DMG Profile A: Clear en frame 0, sin writes no-cero después del clear
  • DMG Profile B: Clear en frame 0, sin writes no-cero después del clear, pero con tilemap writes (1024 bytes)
  • CGB: Clear en frame 174, first non-zero write en frame 174 (PC:0x12C1, Addr:0x8FFF, Val:0xBF)

Validación de módulo compilado C++: Todos los cambios se compilaron correctamente con Cython y pasaron test_build.py.

Fuentes Consultadas

  • Pan Docs: "VRAM", "LCDC Register (0xFF40)", "Power Up Sequence"
  • GBEDG: Post-Boot State
  • Plan Step 0492: step_0492_-_detectar_clear_vram_y_carga_de_tiles_70afcfb7.plan.md

Integridad Educativa

Lo que Entiendo Ahora

  • Clear VRAM: Muchos juegos escriben ceros a toda la región de tiledata durante la inicialización. Este proceso es detectable contando los intentos de escritura hasta alcanzar 6144 bytes.
  • Post-Boot State: El estado inicial de los registros I/O puede afectar significativamente el comportamiento del juego. El A/B test muestra que cambiar LCDC de 0x00 a 0x91 no resuelve el problema de carga de tiles en DMG.
  • Pipeline de Renderizado CGB: El problema en CGB no está en la carga de datos (los tiles se cargan correctamente), sino en el pipeline de renderizado que no está generando contenido no-blanco en los framebuffers.

Lo que Falta Confirmar

  • DMG: Por qué el juego no progresa después del clear. Posibles causas: loops de espera, problemas con interrupciones, condiciones de carrera en timing de I/O.
  • CGB: Dónde exactamente se pierde la información en el pipeline de renderizado. Los dumps de framebuffer en frame 180 deberían ayudar a identificar el problema.

Hipótesis y Suposiciones

Hipótesis DMG: El juego está esperando alguna condición que no se está cumpliendo (ej: interrupción VBlank, lectura de registro I/O específico, timing específico). Los PC hotspots después del clear deberían revelar qué código se está ejecutando.

Hipótesis CGB: El problema está en la lógica de presentación (RGB -> Present) o en el manejo de paletas CGB. Los framebuffers RGB y Present están ambos en blanco, lo que sugiere que el problema está antes de la presentación.

Próximos Pasos

  • [ ] Analizar PC hotspots después del clear en DMG para identificar qué código se está ejecutando
  • [ ] Investigar loops de espera que puedan estar bloqueando el progreso del juego en DMG
  • [ ] Analizar dumps de framebuffer en CGB (frame 180) para identificar dónde se pierde la información
  • [ ] Revisar la lógica de presentación CGB (RGB -> Present) para identificar el problema
  • [ ] Verificar el manejo de paletas CGB y su impacto en el renderizado