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
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 cleartiledata_nonzero_after_clear: Contador de writes no-cero después del cleartiledata_first_nonzero_frame,tiledata_first_nonzero_pc,tiledata_first_nonzero_addr,tiledata_first_nonzero_val: Información del primer write no-cerotiledata_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_framecon 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 clearpc_hotspots_top3: Top 3 direcciones PC más ejecutadas después del cleario_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ónVIBOY_DUMP_RGB_FRAME=180: Dump del framebuffer RGB en frame 180VIBOY_DUMP_IDX_FRAME=180: Dump del framebuffer de índices en frame 180VIBOY_DUMP_PRESENT_FRAME=180: Dump del framebuffer de presentación en frame 180
Archivos Afectados
src/core/cpp/MMU.hpp- Añadidos campos aVRAMWriteStatsy métodoinit_post_boot_dmg_state_profile_b()src/core/cpp/MMU.cpp- Implementado tracking de clear VRAM y perfil B post-bootsrc/core/cython/mmu.pxd- Actualizado structVRAMWriteStatsy añadidoTiledataWriteEventsrc/core/cython/mmu.pyx- Exposición de nuevos campos a Pythontools/rom_smoke_0442.py- Modo "stop early" y sección "AfterClear" en snapshotsdocs/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-nonzeroconVIBOY_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-nonzerocon 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