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.
Verificación Real + Tests "de Verdad" (Post-Fix 0464)
Resumen
Corrección de problemas críticos identificados en el Step 0464: tests que "pasaban" pero no probaban realmente tilemap base ni scroll (solo verificaban MMU.write), uso de mmu.read() en lugar de read_raw() en rom_smoke_0442.py (puede "mentir" por restricciones de acceso), log [ENV] contaminando runtime, y stepping de frame incorrecto (iteraba en lugar de acumular ciclos). Se corrigieron los tests para usar asserts reales de framebuffer indices, se cambió a read_raw() para tilemap stats, se añadió instrumentación gated [IO-SCROLL-WRITE], y se limpió el log [ENV].
Concepto de Hardware
Problema identificado: Los tests del Step 0464 daban "falsa seguridad" porque:
- No verificaban el framebuffer real (solo leían VRAM con
mmu.read()) - No probaban que el PPU seleccionara el tilemap correcto según LCDC bit3
- No verificaban que el scroll (SCX/SCY) se aplicara correctamente al framebuffer
Restricciones de acceso VRAM: Durante ciertos modos del PPU (especialmente Mode 3), leer VRAM via read() puede devolver valores bloqueados o 0xFF. Por eso existe read_raw() que bypass estas restricciones para diagnóstico confiable.
Framebuffer indices: El PPU renderiza tiles al framebuffer como índices de color (0-3), no como RGB. Estos índices se pueden leer con get_framebuffer_indices() para verificar que el renderizado es correcto.
Referencia: Pan Docs - VRAM Access Restrictions, PPU Modes. Step 0457 - Debug API para tests.
Implementación
El fix se implementó en cinco fases:
Fase A: Corregir Tests - Framebuffer Asserts Reales
Se reescribieron los tests para que prueben realmente tilemap base y scroll usando framebuffer indices:
- Añadido helper
run_one_frame()que acumula ciclos correctamente (no itera fijo 70224 veces) - Reescritos tests para usar
ppu.get_framebuffer_indices()con asserts reales sobre los píxeles renderizados - Tests verifican que el patrón renderizado corresponde al tilemap base seleccionado y al scroll aplicado
Problema conocido: Los tests fallan porque el framebuffer devuelve 0 en lugar de los índices esperados. Requiere más investigación para determinar si es un problema de renderizado, timing, o configuración del PPU.
Fase B: Corregir rom_smoke_0442.py - Usar RAW VRAM
Se cambió el muestreo de tilemap/tile IDs a usar read_raw() para evitar "mentiras" por restricciones de acceso:
# ANTES (puede "mentir" por restricciones de acceso en Mode 3):
tilemap_nz_9800 = 0
for addr in range(0x9800, 0x9C00):
if mmu.read(addr) != 0:
tilemap_nz_9800 += 1
# DESPUÉS (RAW, sin restricciones):
tilemap_nz_9800 = 0
for addr in range(0x9800, 0x9C00):
if mmu.read_raw(addr) != 0: # Usar read_raw()
tilemap_nz_9800 += 1
Fase C: Instrumentación Gated - IO Write Trace
Se añadió logging gated de writes a SCX/SCY para demostrar si "stripes bajando" son por writes del juego o por bug:
// En MMU::write(), cuando addr == 0xFF42 o 0xFF43:
if ((debug_ppu || debug_io) && (addr == 0xFF42 || addr == 0xFF43)) {
uint8_t old_val = memory_[addr];
uint8_t new_val = value;
uint8_t ly = ppu_->get_ly();
const char* reg_name = (addr == 0xFF42) ? "SCY" : "SCX";
printf("[IO-SCROLL-WRITE] addr=0x%04X %s old=%d new=%d LY=%d\n",
addr, reg_name, old_val, new_val, ly);
}
Solo aparece cuando VIBOY_DEBUG_PPU=1 o VIBOY_DEBUG_IO=1.
Fase D: Limpieza - Eliminar/Gatear [ENV] Log
Se eliminó el log [ENV] always-on de viboy.py y se movió a tools/rom_smoke_0442.py (solo en herramientas, no en runtime):
# En tools/rom_smoke_0442.py, al inicio de run():
import os
env_vars = [
'VIBOY_DEBUG_INJECTION',
'VIBOY_FORCE_BGP',
'VIBOY_AUTOPRESS',
'VIBOY_FRAMEBUFFER_TRACE',
'VIBOY_DEBUG_UI',
'VIBOY_DEBUG_PPU',
'VIBOY_DEBUG_IO'
]
env_status = []
for var in env_vars:
value = os.environ.get(var, '0')
env_status.append(f"{var}={value}")
print(f"[ENV] {' '.join(env_status)}")
Fase E: Validación Real (Pendiente)
La validación real con grid UI y tabla por ROM se realizará en un step posterior, una vez que los tests funcionen correctamente.
Archivos Afectados
tests/test_bg_tilemap_base_and_scroll_0464.py- Tests reescritos con framebuffer asserts reales y helperrun_one_frame()que acumula ciclos correctamentetools/rom_smoke_0442.py- Cambiado aread_raw()para tilemap stats (líneas 380-393) y añadido log[ENV]al inicio derun()src/core/cpp/MMU.cpp- Añadida instrumentación gated[IO-SCROLL-WRITE](líneas 2538-2560)src/viboy.py- Eliminado log[ENV]always-on (líneas 677-691, 705-721)
Tests y Verificación
Comando ejecutado: pytest -q tests/test_bg_tilemap_base_and_scroll_0464.py
Resultado: ⚠️ Tests fallan - framebuffer devuelve 0 en lugar de índices esperados
Problema conocido: El framebuffer no se está renderizando correctamente en los tests. Posibles causas:
- Framebuffer no se actualiza después de un frame
- Patrón de tile data incorrecto
- Condiciones de renderizado no cumplidas (LCDC bit 0, timing, etc.)
Código del Test:
def run_one_frame(self):
"""Helper: Ejecutar exactamente 70224 ciclos (no 70224 iteraciones)."""
cycles_per_frame = 70224
cycles_accumulated = 0
while cycles_accumulated < cycles_per_frame:
cycles = self.cpu.step()
cycles_accumulated += cycles
self.timer.step(cycles)
self.ppu.step(cycles)
def test_tilemap_base_select_9800(self):
"""Test 1: tilemap base select (0x9800 vs 0x9C00) - Caso 0x9800."""
# Crear tile 0 con patrón P0: [0,1,2,3,0,1,2,3] por línea
for line in range(8):
byte1 = 0x55 # Bits bajos: 0,1,0,1,0,1,0,1
byte2 = 0x33 # Bits altos: 0,0,1,1,0,0,1,1
self.mmu.write(0x8000 + (line * 2), byte1)
self.mmu.write(0x8000 + (line * 2) + 1, byte2)
# Poner en 0x9800: tile IDs = 0
for i in range(32 * 32):
self.mmu.write(0x9800 + i, 0x00)
# Setear LCDC bit3=0 (tilemap base 0x9800)
self.mmu.write(0xFF40, 0x91) # Bit3=0 → 0x9800
# Correr 1 frame
self.run_one_frame()
# Verificar framebuffer: fila0 px[0..7] == P0
indices = self.ppu.get_framebuffer_indices()
expected_p0 = [0, 1, 2, 3, 0, 1, 2, 3]
for i in range(8):
actual_idx = indices[row0_start + i] & 0x03
assert actual_idx == expected_p0[i]
Validación Nativa: Validación de módulo compilado C++ mediante get_framebuffer_indices() que devuelve bytes de 23040 bytes (160×144) con valores 0..3 del front buffer.
Resultados
Implementaciones completadas:
- ✅ Helper
run_one_frame()que acumula ciclos correctamente - ✅ Tests reescritos con asserts reales de framebuffer indices
- ✅
rom_smoke_0442.pyusaread_raw()para tilemap stats - ✅ Instrumentación gated
[IO-SCROLL-WRITE]añadida - ✅ Log
[ENV]eliminado de runtime, movido a tools
Problemas conocidos:
- ⚠️ Tests fallan - framebuffer devuelve 0 (requiere más investigación)
Próximos Pasos
- Investigar por qué el framebuffer devuelve 0 en los tests
- Verificar condiciones de renderizado del BG (LCDC bit 0, timing, etc.)
- Validar la codificación del patrón de tile data
- Ejecutar validación real con grid UI una vez que los tests funcionen