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

UI Ejecución Paralela + Logs Reales + Resumen Path/Profiling

Fecha: 2026-01-02 Step ID: 0447 Estado: VERIFIED

Resumen

Ejecución de UI en paralelo con múltiples ROMs (Mario, Pokémon, Tetris, Zelda) para capturar logs reales de path de renderizado, profiling por etapas y métricas de nonwhite. Creación de scripts automatizados para ejecución paralela con timeout, extracción de muestras de logs y detección de freezes. Objetivo: obtener evidencia objetiva de qué path se usa (cpp_rgb_view vs legacy), qué etapa consume más tiempo, y dónde se pierden píxeles (nonwhite antes/después del blit). Resultados: todas las ROMs usan path correcto (cpp_rgb_view), Mario NO se congela pero tiene pérdida masiva de píxeles (11520→1), Pokémon tiene framebuffer blanco desde el core (no es problema de UI), profiling muestra TOTAL ~420ms (posible bloqueo en pygame.event.pump o sleep).

Concepto de Hardware

Ejecución Paralela de UI: Para ahorrar tiempo en pruebas, se ejecutan múltiples instancias de la UI en paralelo, cada una con una ROM diferente. Cada instancia tiene un timeout (15 segundos) para evitar que se quede colgada indefinidamente. Los logs se capturan por separado para cada ROM, permitiendo análisis independiente.

Análisis de Logs: Los logs contienen tres tipos de información crítica:

  • [UI-PATH]: Indica qué path de renderizado se usa (cpp_rgb_view vs legacy_fallback), métricas del framebuffer (len, shape, nonwhite, hash), tiempo de frame y FPS estimada.
  • [UI-PROFILING]: Mide tiempo por etapa del presenter (frombuffer/reshape, blit_array, scale/blit, flip) y tiempo TOTAL del frame completo.
  • [UI-DEBUG]: Verifica nonwhite antes y después del blit para detectar pérdida de píxeles durante la presentación.

Detección de Freezes: Si una ROM se congela, el log tendrá muy pocas líneas (<50 líneas). Si una ROM funciona correctamente, generará miles o millones de líneas (dependiendo del timeout y FPS).

Análisis de Profiling: El tiempo TOTAL del frame debe ser ~16.67ms para 60 FPS. Si TOTAL es mucho mayor (ej: 420ms), hay un bloqueo o espera en alguna parte (posiblemente pygame.event.pump(), sleep(), o sincronización con el core). Las etapas individuales (frombuffer, blit_array, scale, flip) deben ser <5ms cada una en condiciones normales.

Implementación

Fase A: Script de Ejecución Paralela

Archivo: tools/run_ui_parallel_0447.sh

Funcionalidad:

  • Ejecuta múltiples ROMs en paralelo usando & (background jobs)
  • Cada instancia tiene timeout de 15 segundos usando timeout 15s
  • Usa stdbuf -oL -eL para line-buffering (captura en tiempo real)
  • Logs se guardan en /tmp/viboy_0447/{rom}.log
  • Espera a que todas las instancias finalicen con wait
#!/bin/bash
# Ejecutar UI en paralelo con varias ROMs para capturar logs (Step 0447)

LOG_DIR="/tmp/viboy_0447"
mkdir -p "$LOG_DIR"

ROMS=(
    "mario.gbc"      # Crítico: antes freeze + 0.1 FPS
    "pkmn.gb"        # Crítico: blanco previo
    "tetris.gb"      # Baseline DMG
    "tetris_dx.gbc"  # Baseline GBC
    "zelda-dx.gbc"   # Baseline GBC
)

run_ui() {
    local rom="$1"
    local out="$LOG_DIR/${rom}.log"
    timeout 15s stdbuf -oL -eL python3 main.py "roms/${rom}" 2>&1 | tee "$out" &
}

for rom in "${ROMS[@]}"; do
    if [ -f "roms/${rom}" ]; then
        run_ui "$rom"
        sleep 0.5
    fi
done

wait

Fase B: Script de Extracción de Logs

Archivo: tools/extract_ui_logs_0447.sh

Funcionalidad:

  • Extrae muestras de logs por ROM (primeras 8 líneas de cada tipo)
  • Busca [UI-PATH], [UI-PROFILING], [UI-DEBUG]
  • Cuenta líneas totales para detectar freezes
  • Genera resumen en /tmp/viboy_0447/summary.txt

Fase C: Script de Detección de Freezes y Tabla Resumen

Archivo: tools/detect_freezes_0447.sh

Funcionalidad:

  • Extrae métricas clave de cada log (path, FPS, nonwhite, etapa más cara)
  • Detecta posibles freezes (ROMs con <50 líneas)
  • Genera tabla resumen en formato markdown
echo "ROM | Lines | Path | FPS | NonWhite (before/after) | Etapa más cara"
for f in "$LOG_DIR"/*.log; do
    # Extraer métricas...
    # Detectar freeze si line_count < 50
done

Archivos Afectados

  • tools/run_ui_parallel_0447.sh - Script para ejecutar UI en paralelo con timeout
  • tools/extract_ui_logs_0447.sh - Script para extraer muestras de logs por ROM
  • tools/detect_freezes_0447.sh - Script para detectar freezes y generar tabla resumen

Nota: No se modificó código core. Solo se crearon scripts de análisis.

Tests y Verificación

Ejecución: Scripts ejecutados exitosamente. Logs capturados para 5 ROMs.

Resultados por ROM:

ROM Lines Path FPS NonWhite (before/after) Etapa más cara
mario.gbc 6,573,610 cpp_rgb_view 1304.0 11520/1 TOTAL
pkmn.gb 664,683 cpp_rgb_view 950.1 N/A/0 TOTAL
tetris_dx.gbc 44,343 cpp_rgb_view 1249.9 11520/1 TOTAL
tetris.gb 66,850 cpp_rgb_view 951.2 N/A/0 TOTAL
zelda-dx.gbc 31,160 cpp_rgb_view 947.3 11520/0 TOTAL

Hallazgos Clave:

  • Mario NO se congela: 6.5M líneas de log indican que la UI está funcionando, pero hay pérdida masiva de píxeles (11520→1). El TOTAL de profiling es ~420ms, lo que sugiere bloqueo en pygame.event.pump() o sleep().
  • Pokémon tiene framebuffer blanco desde el core: NonWhite=0 indica que el problema está en el PPU/core, no en la UI.
  • Todas las ROMs usan path correcto: cpp_rgb_view (no legacy_fallback).
  • Pérdida de píxeles en GBC: Mario y Zelda pierden casi todos los píxeles en el blit (11520→1 o 0), sugiriendo problema en formato de superficie o blit.

Ejemplo de Log [UI-PATH]:

[UI-PATH] Frame 4 | Path=cpp_rgb_view | Len=69120 | Shape=rgb_view | NonWhite=11520 | Hash=5d6c318f | Time=0.75ms | FPS=1327.7

Ejemplo de Log [UI-PROFILING]:

[UI-PROFILING] Frame 241 | frombuffer/reshape: 0.01ms | blit_array: 0.06ms | scale/blit: 0.01ms | flip: 5.80ms | TOTAL: 421.43ms

Ejemplo de Log [UI-DEBUG]:

[UI-DEBUG] Nonwhite antes del blit: 11520 (estimado)
[UI-DEBUG] Nonwhite después del blit (sample): 1/3

Fuentes Consultadas

  • Bash documentation: timeout, stdbuf, background jobs
  • Pygame documentation: Profiling y optimización de renderizado

Integridad Educativa

Lo que Entiendo Ahora

  • Ejecución paralela: Se pueden ejecutar múltiples instancias de UI en paralelo usando background jobs de bash, cada una con su propio log.
  • Análisis de logs: Los logs contienen información estructurada que se puede extraer con grep/awk para generar resúmenes y tablas.
  • Detección de freezes: Si una ROM se congela, el log tendrá muy pocas líneas. Si funciona, generará miles o millones de líneas.
  • Profiling TOTAL vs etapas: El TOTAL del frame puede ser mucho mayor que la suma de las etapas individuales si hay bloqueos o esperas (pygame.event.pump(), sleep(), sincronización).

Lo que Falta Confirmar

  • Por qué TOTAL es ~420ms: Necesito investigar si hay bloqueo en pygame.event.pump(), sleep(), o sincronización con el core. El tiempo de las etapas individuales suma <1ms, pero TOTAL es 420ms.
  • Pérdida de píxeles en blit: Por qué Mario y Zelda pierden casi todos los píxeles (11520→1 o 0) durante el blit. Puede ser problema de formato de superficie o blit_array.
  • Pokémon framebuffer blanco: El problema está en el PPU/core (NonWhite=0), no en la UI. Necesito investigar por qué el PPU genera framebuffer blanco para Pokémon.

Hipótesis y Suposiciones

TOTAL ~420ms: Hipótesis: hay un bloqueo o espera en pygame.event.pump() o sleep() que no se está midiendo en el profiling. El tiempo de las etapas individuales suma <1ms, pero TOTAL es 420ms, lo que sugiere que hay algo fuera del profiling que está bloqueando.

Pérdida de píxeles: Hipótesis: el formato de la superficie o el blit_array está causando que los píxeles se pierdan. Puede ser problema de formato RGB888 vs RGB24, o problema de stride/contiguidad.

Próximos Pasos

  • [ ] Step 0448: Investigar por qué TOTAL es ~420ms (posible bloqueo en pygame.event.pump() o sleep())
  • [ ] Step 0448: Fix de pérdida de píxeles en blit (Mario y Zelda pierden 11520→1 o 0)
  • [ ] Step 0448: Investigar por qué Pokémon tiene framebuffer blanco desde el core (problema PPU, no UI)
  • [ ] Optimizar profiling para medir también pygame.event.pump() y sleep()
  • [ ] Verificar formato de superficie y blit_array para evitar pérdida de píxeles