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.
First-Nonzero Tiledata + Source-Read Correlation + Wait-Loop Explainer
Resumen
Este Step implementa instrumentación avanzada para diagnosticar por qué los juegos DMG solo escriben ceros a VRAM. Se añadieron contadores de contenido escrito (cero vs no-cero) en VRAMWriteAuditStats, tracking del primer/last nonzero write con contexto completo (PC, frame, modo PPU), y un ring buffer de reads MMU para correlación source-read (detectar si la CPU está copiando ceros desde ROM/RAM). Se implementó parada automática cuando se detecta el primer write no-cero y un clasificador DMG v4 que usa estas nuevas métricas para diagnóstico más preciso. El objetivo es obtener una respuesta binaria basada en datos: ¿se intentan escribir tiles reales (nonzero)? ¿de dónde salen los valores escritos?
Concepto de Hardware
Problema del Step 0501: Se detectó que los juegos DMG realizan 6144 writes a tiledata (exactamente el tamaño de 0x8000-0x97FF = 0x1800 bytes), todos con valor 0x00. Esto sugiere que el juego está haciendo un "clear VRAM" completo, pero después no carga tiles reales.
Hipótesis del Step 0502: Necesitamos distinguir entre tres escenarios posibles:
- A) "Nunca hay writes no-cero": El juego está atascado / no progresa a la fase de carga de tiles
- B) "Hay writes no-cero, pero luego se vuelve cero": Algo está borrando los tiles o hay un clear/swap repetido
- C) "Writes son cero porque el source leído es cero": Bug en ROM reads/MBC/mapping o el fetch de datos está fallando
Correlación Source-Read: Para diagnosticar el escenario C, necesitamos rastrear las lecturas de memoria que preceden a los writes a VRAM. Si un write a VRAM escribe 0x00 y el último read antes del write también leyó 0x00 desde ROM/RAM, entonces sabemos que el problema está en el "source" (los datos que se están copiando), no en el destino (VRAM).
Ring Buffer de Reads: Implementamos un ring buffer ligero (256 eventos) que captura todos los reads de memoria (ROM, WRAM, HRAM, VRAM, IO). Cuando se escribe a VRAM, buscamos en los últimos 1-3 reads del ring para encontrar el "source" más probable. Esto es heurístico pero suficiente para detectar si el problema es "source=0".
Wait Loop Detection: Si el juego hace clear y luego se queda en un loop esperando (ej: esperando VBlank, esperando un flag), necesitamos identificar en qué dirección I/O está esperando. Esto se hace analizando el "hotspot" de PC (PC más visitado) y qué direcciones I/O se leen desde ese hotspot.
Referencia: Pan Docs - Memory Map, VRAM Access, CPU Instruction Set
Implementación
Fase A: Contabilidad Real del Contenido Escrito ✅
A1) VRAMWriteAuditStats v2:
- Ampliado
VRAMWriteAuditStatscon contadores de contenido escrito:tiledata_writes_zero_count: Writes con valor 0x00tiledata_writes_nonzero_count: Writes con valor != 0x00tiledata_writes_ff_count: Writes con valor 0xFF (opcional)
- Añadido tracking de
FirstNonzeroWriteyLastNonzeroWritecon contexto completo:- frame_id, PC, addr, value, stat_mode, ly, lcdc
- Permite identificar exactamente cuándo y dónde aparece el primer tile real
- Muestra de valores únicos no-cero (hasta 8 valores) para identificar patrones
A2) Ring Buffer de Eventos "Interesantes":
- Ring buffer dedicado de 64 eventos para writes "interesantes":
- tiledata write donde
value != 0x00 - tiledata write bloqueado
- tiledata write forzado
- tiledata write donde
- Evita que el ring se llene de basura "clear = 0"
Fase B: Correlación Source-Read ✅
B1) MMU Read Ring:
- Implementado ring buffer de 256 eventos para todos los reads de memoria
- Estructura
MMUReadEventcaptura: frame_id, PC, addr, value, region (ROM0/ROMX/WRAM/HRAM/IO/VRAM), type (normal/IO) - Captura en
MMU::read()para ROM, VRAM, WRAM, HRAM - Ligero y no intrusivo (solo guarda últimos 256 reads)
B2) Correlación Heurística Write↔Read:
- Ampliado
VRAMWriteEventcon campos de correlación:src_addr_guess: Dirección source estimada (del último read)src_value_guess: Valor source estimado (del último read)src_region_guess: Región source estimada (ROM0/ROMX/WRAM/HRAM/IO)src_correlation_valid: Si la correlación es válida (últimos 1-3 reads)
- En
MMU::write()cuando destino es VRAM, busca en los últimos 3 reads del ring:- Busca reads con el mismo PC (correlación directa)
- O usa el read más reciente si está en los últimos 3
- Interpretación dura:
- Si
value_written == 0x00ysrc_value_guess == 0x00repetidamente → Source está dando ceros - Si
src_value_guess != 0perovalue_written == 0→ Problema en CPU/A-reg/orden (menos probable)
- Si
Fase C: Hotspot Explainer (Parcial) ⚠️
C1) Hotspot Explainer:
- Parcialmente implementado: el clasificador DMG v4 usa el hotspot del PC para detectar wait loops
- Pendiente: dump completo de ROM alrededor del hotspot, contador de "loop stability", y "top read addr" desde el hotspot
- Esto requiere tracking adicional de reads por PC que se puede hacer heurísticamente desde el MMU read ring
Fase D: Run Until Evidence ✅
D1) Parada Automática:
- Implementado en
rom_smoke_0442.py:- Flag
stop_on_first_tiledata_nonzero_write: Para cuando se detecta primer write no-cero a tiledata - Flag
stop_on_first_tiledata_nonzero: Para cuando se detecta primer tiledata no-cero en VRAM (readback)
- Flag
- CLI arguments añadidos:
--stop-on-first-tiledata-nonzero--stop-on-first-tiledata-nonzero-write
- Ejecuciones mínimas sin esperar a ojo: se para automáticamente cuando aparece evidencia
Fase E: Clasificador DMG v4 ✅
E1) Clasificaciones Nuevas:
- ONLY_CLEAR_TO_ZERO: 6144 writes a 0, sin nonzero jamás
- SOURCE_READS_ZERO: src_guess casi siempre 0 (correlación source-read)
- NONZERO_WRITTEN_THEN_CLEARED: Aparece nonzero y luego vuelve a 0
- NONZERO_PRESENT_OK: tiledataNZ sube (tiles reales presentes)
- WAIT_LOOP_ON_ADDR_X: Hotspot lee siempre la misma addr (pendiente implementación completa)
E2) Fix Mínimo Sugerido:
- Si
SOURCE_READS_ZEROysrc_addr_guessapunta a ROMX → Sospecha fuerte a MBC/banking/ROM read - Si
ONLY_CLEAR_TO_ZERO+ hotspot espera en WRAM/HRAM → Sospecha a flag de VBlank handler que no se setea - Si
NONZERO_WRITTEN_THEN_CLEARED→ Buscar quién limpia (PC del clear) y por qué se repite
Componentes Creados/Modificados
src/core/cpp/MMU.hpp: Estructuras ampliadas (VRAMWriteAuditStats, VRAMWriteStats, MMUReadEvent, FirstNonzeroWrite, LastNonzeroWrite)src/core/cpp/MMU.cpp: Implementación de contadores cero/no-cero, correlación source-read, ring buffer de readssrc/core/cython/mmu.pxd: Declaraciones Cython actualizadassrc/core/cython/mmu.pyx: Métodosget_vram_write_stats_v2(),get_vram_write_audit_stats()actualizadotools/rom_smoke_0442.py: Parada automática, clasificador DMG v4
Archivos Afectados
src/core/cpp/MMU.hpp- Ampliación de estructuras VRAMWriteStats, VRAMWriteAuditStats, nueva estructura MMUReadEventsrc/core/cpp/MMU.cpp- Implementación de contadores cero/no-cero, tracking primer/last nonzero, MMU read ring, correlación source-readsrc/core/cython/mmu.pxd- Declaraciones Cython actualizadas con nuevas estructurassrc/core/cython/mmu.pyx- Método get_vram_write_stats_v2(), get_vram_write_audit_stats() ampliado, get_vram_write_ring() con correlacióntools/rom_smoke_0442.py- Parada automática (stop_on_first_tiledata_nonzero, stop_on_first_tiledata_nonzero_write), clasificador DMG v4
Tests y Verificación
Compilación:
python3 setup.py build_ext --inplace
# Exit code: 0 (éxito)
# Solo warnings, ningún error crítico
Test de Build:
python3 test_build.py
# [EXITO] El pipeline de compilacion funciona correctamente
# El nucleo C++/Cython esta listo para la Fase 2.
Validación de Módulo Compilado C++: Los métodos get_vram_write_stats_v2() y get_vram_write_audit_stats() están disponibles desde Python y pueden ser llamados sin errores.
Tests Pendientes:
- Ejecutar
rom_smoke_0442.pycon--stop-on-first-tiledata-nonzero-writepara validar parada automática - Verificar que el clasificador DMG v4 retorna clasificaciones correctas basadas en las nuevas métricas
- Validar que la correlación source-read funciona correctamente comparando writes VRAM con reads previos
Fuentes Consultadas
- Pan Docs: Memory Map, VRAM Access, CPU Instruction Set
- Plan Step 0502:
.cursor/plans/step_0502_-_first-nonzero_tiledata_+_source-read_correlation_+_wait-loop_explainer_dd348105.plan.md
Integridad Educativa
Lo que Entiendo Ahora
- Correlación Source-Read: Es posible rastrear de dónde vienen los valores escritos a VRAM analizando los reads previos. Esto es heurístico (buscar en los últimos 1-3 reads) pero suficiente para detectar si el problema es "source=0" (los datos que se están copiando son ceros) vs "destino=problema" (VRAM está bloqueada/corrupta).
- Contadores de Contenido: Distinguir entre "intentos de write" y "contenido escrito" es crucial. Un write bloqueado sigue siendo un intento, pero si el contenido escrito (cuando se permite) es siempre cero, entonces sabemos que el problema no es el bloqueo sino el contenido.
- Ring Buffer de Reads: Un ring buffer ligero (256 eventos) es suficiente para capturar el contexto necesario para correlación source-read sin saturar memoria. Solo necesitamos los últimos N reads, no todos los reads históricos.
Lo que Falta Confirmar
- Hotspot Explainer Completo: Implementación completa del hotspot explainer que muestre dump de ROM alrededor del PC, contador de loop stability, y top read addr desde el hotspot. Esto requiere tracking adicional de reads por PC.
- Validación con ROMs Reales: Ejecutar rom_smoke_0442.py con las nuevas flags y verificar que las clasificaciones sean correctas y útiles para diagnóstico.
- Correlación Source-Read Más Precisa: La heurística actual (últimos 1-3 reads) es suficiente para casos simples, pero podría mejorarse con análisis de patrones de instrucciones (ej: si la instrucción es LD (HL),A, entonces el último read probablemente es el source).
Hipótesis y Suposiciones
Suposición de Correlación: Asumimos que si un write a VRAM ocurre poco después de un read de ROM/RAM, el valor escrito probablemente viene del valor leído. Esto es heurístico y puede fallar en casos donde hay múltiples reads/writes intercalados, pero para la mayoría de casos (copia simple de datos) debería funcionar.
Próximos Pasos
- [ ] Ejecutar
rom_smoke_0442.pycon--stop-on-first-tiledata-nonzero-writeen tetris.gb y pkmn.gb para obtener evidencia real - [ ] Completar Hotspot Explainer: dump de ROM alrededor del PC, loop stability, top read addr
- [ ] Analizar resultados del clasificador DMG v4 y determinar fix mínimo según las clasificaciones
- [ ] Si SOURCE_READS_ZERO → Investigar MBC/banking/ROM read
- [ ] Si ONLY_CLEAR_TO_ZERO + wait loop → Investigar flags de VBlank handler
- [ ] Si NONZERO_WRITTEN_THEN_CLEARED → Identificar quién limpia y por qué