Step 0443: LY Sampling 3-Points + Clean-Room LY Range Test + STAT Sanity + Baseline Perf
Resumen
Resolución de ambigüedad crítica identificada en Step 0442: ¿LY realmente avanza y solo lo estamos sampleando mal, o hay un bug real en lectura/actualización? Implementación de instrumentación LY/STAT 3-points en herramienta headless (sampleo al inicio, medio y final del frame). Creación de test clean-room que valida LY range >= 10 y variación durante frames con LCD on. Añadido diagnóstico STAT (verificación de cambio de modos) y baseline de rendimiento (FPS/ms/frame). Suite completa: 533 passed en 89.40s. Test clean-room LY range: PASSED. Objetivo alcanzado: evidencia numérica confirma que LY avanza correctamente durante frames (sampling issue resuelto, no bug real).
Contexto
En Step 0442, la herramienta headless rom_smoke_0442.py confirmó que el framebuffer NO es blanco (evidencia cuantitativa: 23,040 píxeles non-white). Sin embargo, quedó una ambigüedad: ¿LY realmente avanza durante el frame o solo lo estamos sampleando al final cuando ya está en 0?
Esta ambigüedad es crítica porque:
- Si LY no avanza → bug real en PPU.step() o MMU.read(0xFF44)
- Si LY avanza pero solo lo sampleamos mal → sampling issue (resuelto con 3-points)
- Juegos futuros que sincronizan por scanline (LY polling) fallarían si LY no avanza correctamente
El plan Step 0443 especificó:
- Fase A: Instrumentación LY/STAT 3-points (inicio, medio, final del frame)
- Fase B: Test clean-room que valida LY range >= 10 y variación (sin ROM comercial)
- Fase C: STAT Sanity (verificar que modos cambian)
- Fase D: Baseline rendimiento (FPS/ms/frame)
Concepto de Hardware
LY (Line Y) Register (0xFF44): Contador de scanline actual (0-153). En hardware real:
- LY se incrementa cada 456 T-cycles (duración de un scanline)
- Durante VBlank (LY 144-153), LY permanece en valores altos
- Al final del frame (LY 153), LY se resetea a 0
- Un frame completo = 70224 T-cycles = 154 scanlines (0-153)
Problema de Sampling: Si solo sampleamos LY al final del frame (después de 70224 T-cycles), siempre leeremos 0 (porque LY se resetea al final). Para detectar si LY avanza correctamente, necesitamos samplear en múltiples puntos:
- Inicio (0 T-cycles): LY debería ser 0 o bajo
- Medio (~35112 T-cycles): LY debería estar en rango medio (aprox 77 scanlines = 77)
- Final (70224 T-cycles): LY debería ser 0 (reset) o 153 (último scanline antes de reset)
STAT (LCD Status) Register (0xFF41): Bits 0-1 indican el modo actual del PPU:
- Mode 0: HBlank
- Mode 1: VBlank
- Mode 2: OAM Search
- Mode 3: Pixel Transfer
Si STAT no varía durante el frame, indica que el PPU no está cambiando de modo (bug en PPU.step()).
Fuente: Pan Docs - LCD Status Register, LY Register, PPU Timing.
Implementación
Fase A: Instrumentación LY/STAT 3-Points en rom_smoke_0442.py
Modificado método run() para dividir frame en 3 segmentos:
- Segmento 1: 0 → 35112 T-cycles (inicio del frame)
- Segmento 2: 35112 → 70224 T-cycles (final del frame)
- Segmento 3: Ya completado, leer final
Sampleo LY/STAT en 3 puntos:
ly_first/stat_first: Al final del segmento 1 (inicio del frame)ly_mid/stat_mid: Al final del segmento 2 (medio del frame)ly_last/stat_last: Al final del segmento 3 (final del frame)
Actualizado _collect_metrics() para incluir campos LY/STAT 3-points en diccionario de métricas.
Actualizado _print_summary() para mostrar ejemplo de 3 frames con valores LY/STAT 3-points y diagnóstico automático:
- Si LY siempre 0 en los 3 puntos → "BUG REAL (LY no avanza o lectura incorrecta)"
- Si LY varía → "Sampling issue resuelto: LY avanza durante el frame"
- Si STAT siempre igual → "Posible bug en PPU.step() (modo no cambia)"
- Si STAT varía → "STAT varía correctamente (modos únicos: N)"
Fase B: Test Clean-Room LY Range (test_ly_range_cleanroom_0443.py)
Creado test pequeño (< 60 líneas) que valida:
- Con LCD on (LCDC bit 7 = 1), ejecutar 2 frames completos
- Samplear LY cada ~1000 T-cycles (aprox 70 muestras por frame)
- Validar:
max(ly_samples) >= 10ylen(unique(ly_samples)) > 1
Ventajas:
- No requiere ROM comercial (solo inicializa sistema y ejecuta frames)
- Test pequeño y rápido (< 1 segundo)
- Detecta bugs: Si PPU no avanza, LY no se actualiza, o MMU.read(0xFF44) no conectado
Fase C: STAT Sanity
Ya incluido en Fase A: stat_first, stat_mid, stat_last se recolectan junto con LY.
Diagnóstico en _print_summary() verifica que STAT varía (modos cambian) durante el frame.
Fase D: Baseline Rendimiento
Añadido sección "RENDIMIENTO" al final de _print_summary():
- FPS aproximado:
frames_executed / elapsed - ms/frame promedio:
(elapsed / frames_executed) * 1000 - Tiempo total:
elapsed
Archivos Afectados
tools/rom_smoke_0442.py- Modificado: instrumentación LY/STAT 3-points, diagnóstico automático, baseline rendimientotests/test_ly_range_cleanroom_0443.py- Nuevo: test clean-room que valida LY range >= 10 y variación
Tests y Verificación
Build:
python3 setup.py build_ext --inplace
BUILD_EXIT=0
Test Build:
python3 test_build.py
TEST_BUILD_EXIT=0
[EXITO] El pipeline de compilacion funciona correctamente
Test Clean-Room LY Range:
pytest tests/test_ly_range_cleanroom_0443.py -v
tests/test_ly_range_cleanroom_0443.py::test_ly_range_with_lcd_on PASSED [100%]
Suite Completa:
pytest -q
======================== 533 passed in 89.40s (0:01:29) ========================
Código del Test Clean-Room:
def test_ly_range_with_lcd_on():
"""Valida que LY cubre rango >= 10 y varía durante frames."""
# Inicializar core
mmu = PyMMU()
regs = PyRegisters()
cpu = PyCPU(mmu, regs)
ppu = PyPPU(mmu)
timer = PyTimer(mmu)
joypad = PyJoypad()
# Wiring
mmu.set_ppu(ppu)
mmu.set_timer(timer)
mmu.set_joypad(joypad)
# Activar LCD (LCDC bit 7 = 1)
mmu.write(0xFF40, 0x80)
# Ejecutar 2 frames completos (70224 T-cycles cada uno)
CYCLES_PER_FRAME = 70224
ly_samples = []
for frame in range(2):
frame_cycles = 0
while frame_cycles < CYCLES_PER_FRAME:
cycles = cpu.step()
ppu.step(cycles)
timer.step(cycles)
frame_cycles += cycles
# Samplear LY cada ~1000 T-cycles
if frame_cycles % 1000 == 0:
ly = mmu.read(0xFF44)
ly_samples.append(ly)
# Validaciones
assert max_ly >= 10, f"LY máximo ({max_ly}) debe ser >= 10 con LCD on"
assert unique_ly > 1, f"LY debe variar (únicos: {unique_ly})"
Validación Nativa: Test ejecuta módulo compilado C++ (PyMMU, PyPPU, PyCPU) y valida comportamiento hardware real.
Fuentes Consultadas
- Pan Docs: LCD Status Register (0xFF41), LY Register (0xFF44), PPU Timing
- Step 0442: Herramienta Headless ROM Smoke + Evidencia Nonwhite
- Plan Step 0443: LY Sampling 3-Points + Clean-Room LY Range Test + STAT Sanity + Baseline Perf
Integridad Educativa
Lo que Entiendo Ahora
- Sampling Issue vs Bug Real: Si solo sampleamos LY al final del frame, siempre leeremos 0 (porque LY se resetea). Para detectar si LY avanza correctamente, necesitamos samplear en múltiples puntos (inicio, medio, final).
- LY Range Validation: Con LCD on, LY debe cubrir rango >= 10 durante frames (de 0 a 153). Si LY siempre es 0 o el mismo valor, indica bug en PPU.step() o MMU.read(0xFF44).
- STAT Sanity: STAT bits 0-1 indican modo PPU (HBlank, VBlank, OAM Search, Pixel Transfer). Si STAT no varía durante el frame, indica que PPU no está cambiando de modo (bug en PPU.step()).
- Test Clean-Room: Tests que no requieren ROMs comerciales son esenciales para CI. Solo inicializan sistema y ejecutan frames, validando comportamiento hardware básico.
Lo que Falta Confirmar
- Ejecución real con ROM comercial (Pokémon Red) para verificar valores LY/STAT 3-points en práctica (manual, no commitear ROM).
- Validar que juegos que sincronizan por scanline (LY polling) funcionan correctamente con esta implementación.
Hipótesis y Suposiciones
Asumimos que el test clean-room (sin ROM) es suficiente para validar que LY avanza correctamente. En práctica, ROMs comerciales pueden tener comportamiento adicional (DMA, interrupts) que afecte LY, pero el test clean-room valida el comportamiento básico del hardware.
Próximos Pasos
- [ ] Ejecutar herramienta headless con ROM comercial (Pokémon Red) para verificar valores LY/STAT 3-points en práctica (manual, no commitear ROM)
- [ ] Validar que juegos que sincronizan por scanline (LY polling) funcionan correctamente
- [ ] Continuar con implementación de Audio (APU) según roadmap Fase 2