Step 0431: Triage PPU/GPU 10 Fails + Split Clusters
📋 Resumen Ejecutivo
Step de análisis puro (0 cambios de código) para clasificar los 10 tests fallidos de PPU/GPU en 2 clusters aislados con estrategias de corrección distintas. Se establece la decisión arquitectónica de priorizar el C++ PPU como "verdad" y deprecar tests legacy de GPU Python incompatibles con el core nativo.
- Cluster A: PPU C++ sprites (3 tests) → Fix técnico en
PPU.cpp - Cluster B: GPU Python background/scroll (7 tests) → Reescribir o marcar legacy
Resultado del Triage
| Cluster | Tests | Naturaleza del Fallo | Estrategia |
|---|---|---|---|
| A (C++ PPU) | 3 | Sprites NO se renderizan (funcionalidad incompleta) | Step 0432: Fix render_sprites() (X-Flip, paletas, swap) |
| B (GPU Python) | 7 | Tests mal diseñados (mockean MMU C++ read-only) | Step 0433: Reescribir con core C++ o marcar legacy/skip |
🔍 Concepto de Hardware: Arquitectura Dual (Core vs Legacy)
Arquitectura Híbrida v0.0.2
En la migración a C++/Cython, existen dos sistemas de renderizado:
┌─────────────────────────────────────────────┐
│ CORE C++ (src/core/cpp/PPU.cpp) │
│ - Renderiza BG/Window/Sprites al framebuffer│
│ - Sincronización ciclo-precisa (456/línea) │
│ - Expuesto vía Cython: PyPPU │
│ - ✅ Verdad para emulación │
└─────────────────────────────────────────────┘
↓ (framebuffer[23040])
┌─────────────────────────────────────────────┐
│ GPU PYTHON (src/gpu/renderer.py) │
│ - Legacy de v0.0.1 (Python puro) │
│ - Adaptador Pygame (blit, draw.rect) │
│ - Tests antiguos (test_gpu_*.py) │
│ - ⚠️ Duplica lógica LCDC/scroll/paletas │
└─────────────────────────────────────────────┘
Problema de Dualidad
- Cluster A (Sprites C++): Tests correctos,
PPU.cpp::render_sprites()incompleto. - Cluster B (GPU Python): Tests legacy incompatibles con core C++ (intentan mockear métodos Cython read-only).
Decisión arquitectónica: Priorizar PPU.cpp como única fuente de verdad. renderer.py debe consumir el framebuffer C++, no reimplementar reglas de hardware.
🧪 Implementación: Análisis de Evidencia
Cluster A: C++ PPU Sprites (3 tests)
A1: test_sprite_rendering_simple ❌
Assertion: "El sprite debe estar renderizado en la línea 4"
Evidencia del log:
[PPU-FRAMEBUFFER-WRITE] Frame 1 | LY: 0 | Non-zero pixels written: 80/160
[PPU-FRAMEBUFFER-AFTER-SWAP] Frame 1 | Total non-zero pixels: 320/23040
Causa: El test avanza hasta LY=4 pero NO completa el frame,
así que swap_buffers() no se ejecuta automáticamente.
El sprite SÍ se renderiza en back buffer.
Fix: Exponer swap_buffers() vía Cython o completar frame completo.
A2: test_sprite_x_flip ❌
Assertion: assert 0xFFFFFFFF == 0xFF000000
(blanco != negro)
Causa: X-Flip NO está implementado en render_sprites().
El sprite se dibuja sin invertir los píxeles horizontalmente.
Fix: Implementar lógica de flip (attributes & 0x20) en PPU.cpp línea ~4280.
A3: test_sprite_palette_selection ❌
Assertion: assert 0xFFFFFFFF == 0xFFAAAAAAA
(blanco != gris claro con OBP1)
Causa: La paleta OBP1 (0xFF49) NO se aplica correctamente.
render_sprites() siempre usa OBP0 (0xFF48).
Fix: Verificar (attributes & 0x10) y usar OBP1 si bit 4 está activo.
Cluster B: GPU Python Background/Scroll (7 tests)
B1: test_lcdc_control_tile_map_area ❌
Error: AttributeError: 'MMU' object attribute 'read_byte' is read-only
Línea 60: mmu.read_byte = tracked_read # ❌ Cython no permite reasignar
Causa: El test intenta mockear un método C++ (MMU compilado)
para verificar que se lee del tilemap correcto (0x9800 vs 0x9C00).
Fix: Reescribir test usando core C++ (PyMMU + PyPPU) sin mocks,
o usar unittest.mock.patch.object() si es necesario.
B2: test_scroll_x ❌
Assertion: "Debe llamar a pygame.draw.rect para dibujar píxeles"
Causa: El test mockea pygame.draw.rect, pero renderer.py usa
renderizado vectorizado con NumPy (blit de surface preallocada).
Fix: Reescribir test para verificar el framebuffer del core C++
(píxeles esperados según SCX), no llamadas internas de Pygame.
Resto de tests (5): Mismo patrón (mocks incompatibles o expectativas incorrectas).
✅ Tests y Verificación
Comandos Ejecutados
# Cluster A: C++ PPU Sprites
pytest -vv tests/test_core_ppu_sprites.py::TestCorePPUSprites::test_sprite_rendering_simple --maxfail=1 -x
# EXIT: 1 (FAILED)
pytest -vv tests/test_core_ppu_sprites.py::TestCorePPUSprites::test_sprite_x_flip --maxfail=1 -x
# EXIT: 1 (FAILED)
pytest -vv tests/test_core_ppu_sprites.py::TestCorePPUSprites::test_sprite_palette_selection --maxfail=1 -x
# EXIT: 1 (FAILED)
# Cluster B: GPU Python Background/Scroll
pytest -vv tests/test_gpu_background.py --maxfail=1 -x
# EXIT: 1 (AttributeError: 'MMU' object attribute 'read_byte' is read-only)
pytest -vv tests/test_gpu_scroll.py::TestScroll::test_scroll_x --maxfail=1 -x
# EXIT: 1 (AssertionError: Debe llamar a pygame.draw.rect)
Tabla de Mapeo: Tests → Módulo Real
| Test | Módulo que debería renderizar | Comentario |
|---|---|---|
test_sprite_rendering_simple |
PPU.cpp::render_sprites() |
✅ Correcto, pero sin swap automático |
test_sprite_x_flip |
PPU.cpp::render_sprites() |
✅ Correcto, flip no implementado |
test_sprite_palette_selection |
PPU.cpp::render_sprites() |
✅ Correcto, OBP1 no aplicado |
test_lcdc_control_tile_map_area |
renderer.py::render_frame() |
❌ Test mal diseñado (mock read-only) |
test_scroll_x |
renderer.py::render_frame() |
❌ Test mal diseñado (mock pygame) |
Resto test_gpu_* |
renderer.py |
❌ Mismo problema |
Resultado del Triage
✅ Reporte generado: STEP_0431_TRIAGE_REPORT.md
✅ Evidencia completa capturada en /tmp/viboy_0431_*.log
✅ Decisión arquitectónica documentada: Priorizar C++ PPU
✅ Plan de Steps siguientes:
→ Step 0432: Fix sprites C++ (render_sprites, flip, paletas)
→ Step 0433: Migrar tests GPU Python → Core C++ (o marcar legacy)
📁 Archivos Afectados (Análisis)
tests/test_core_ppu_sprites.py→ Cluster A (3 tests)tests/test_gpu_background.py→ Cluster B (6 tests)tests/test_gpu_scroll.py→ Cluster B (1 test)src/core/cpp/PPU.cpp→ Análisis derender_sprites()(línea 4165+)src/gpu/renderer.py→ Análisis derender_frame()(legacy)- Reporte generado:
STEP_0431_TRIAGE_REPORT.md
🎯 Decisión Arquitectónica (CRÍTICA)
Opción 1: Priorizar C++ PPU como "verdad" (ELEGIDA)
PPU.cppes la única fuente de verdad para renderizado.renderer.py(GPU Python) es legacy/adaptador Pygame.- Tests futuros deben usar
PyPPU(core C++) y leerframebuffer. - Tests
test_gpu_*se reescriben o marcan como legacy/skip.
Justificación
- El core C++ ya renderiza background, window y sprites al framebuffer.
- Mantener 2 motores (C++ y Python) duplica lógica LCDC/scroll/paletas → bug-prone.
- Tests
test_gpu_*están desactualizados (intentan mockear MMU C++ read-only). - Objetivo v0.0.2: Migrar TODA la emulación al core C++, no mantener Python puro.
Opción 2: Mantener GPU Python independiente (RECHAZADA)
📝 Próximos Pasos
Step 0432: Fix C++ PPU Sprites (Cluster A)
Archivos:
- src/core/cpp/PPU.cpp::render_sprites() (líneas 4165-4350)
- src/core/wrappers/ppu_wrapper.pyx (si hace falta exponer swap_buffers())
- tests/test_core_ppu_sprites.py (añadir swap antes de leer framebuffer)
Tareas:
1. Verificar que render_sprites() se ejecuta en render_scanline()
2. Implementar X-Flip/Y-Flip (attributes & 0x20, 0x40)
3. Aplicar paleta OBP0/OBP1 según (attributes & 0x10)
4. Exponer swap_buffers() vía Cython si tests lo necesitan
Entregable: 3/3 tests de sprites pasan.
Step 0433: Migrar tests GPU Python → Framebuffer C++ (Cluster B)
Archivos:
- tests/test_gpu_background.py
- tests/test_gpu_scroll.py
- src/gpu/renderer.py (marcar como legacy si no se usa más)
Opción A (Reescribir tests):
- Cambiar tests para usar PyMMU + PyPPU (core C++)
- Leer framebuffer del core directamente (sin mockear read_byte)
- Verificar píxeles esperados según LCDC/SCX/SCY
Opción B (Marcar legacy/skip) ✅ RECOMENDADA:
- Documentar que test_gpu_* son legacy de v0.0.1
- Skip con mensaje: "Tests legacy - usar test_core_ppu_*"
- Mantener renderer.py solo para Pygame UI
Entregable: 7 tests marcados legacy o reescritos.
🏆 Conclusión
Step 0431 es un paso de análisis puro que divide los 10 fallos PPU/GPU en 2 problemas distintos con estrategias claras:
- Cluster A (3 tests): Fix técnico en
PPU.cpp::render_sprites()→ Step 0432. - Cluster B (7 tests): Reescribir o deprecar tests legacy incompatibles con core C++ → Step 0433.
Decisión arquitectónica crítica: Priorizar C++ PPU como única fuente de verdad, deprecando GPU Python como motor de renderizado (solo adaptador Pygame).
STEP_0431_TRIAGE_REPORT.md (5.4KB, 220 líneas).