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

El Eslabón Perdido: Arreglando render_frame

Fecha: 2025-12-22 Step ID: 0217 Estado: Draft

Resumen

El diagnóstico del Step 0216 confirmó que el pipeline completo funciona correctamente: C++ escribe el valor `3` (Rojo en nuestra paleta debug), Python lo recibe correctamente, y la paleta tiene el `3` mapeado a `(255, 0, 0)` (Rojo). Sin embargo, la pantalla se ve VERDE CLARO (Color 0), lo que indica que el método `render_frame` está ignorando los datos del framebuffer o fallando silenciosamente al dibujarlos.

Este paso implementa una versión robusta y explícita de `render_frame` que itera píxel a píxel sobre el buffer lineal 1D para garantizar que cada valor se procese correctamente. La implementación usa `pygame.PixelArray` con cierre explícito en lugar del context manager para mayor control y depuración.

Concepto de Hardware

El framebuffer de la PPU C++ es un array lineal 1D de 23040 bytes (160 × 144 píxeles), donde cada byte representa un índice de color (0-3). La organización es:

píxel (y, x) está en índice [y * 160 + x]

El renderizador Python debe convertir estos índices a colores RGB usando la paleta BGP (Background Palette Register, 0xFF47) y dibujarlos en una superficie de Pygame de 160×144 píxeles, que luego se escala a la ventana principal.

Si el método de renderizado falla silenciosamente o no procesa correctamente el buffer, la pantalla mostrará el color de fondo por defecto (verde claro, índice 0) en lugar de los datos reales del framebuffer.

Implementación

Se reemplazó la sección del método `render_frame` que procesa el framebuffer C++ con una implementación explícita que usa un bucle doble (y, x) para iterar sobre cada píxel del buffer lineal.

Componentes modificados

  • src/gpu/renderer.py: Método render_frame() - Reemplazo de la lógica de renderizado del framebuffer C++ con bucle explícito

Cambios técnicos

Antes: Se usaba un context manager (`with pygame.PixelArray()`) que podría estar fallando silenciosamente o no aplicando los cambios correctamente.

Después: Se usa `pygame.PixelArray` con cierre explícito mediante `px_array.close()`, garantizando que los cambios se apliquen a la superficie antes de escalarla y mostrarla.

El bucle itera explícitamente sobre cada píxel (y, x), calcula el índice lineal, obtiene el índice de color del framebuffer, lo mapea a RGB usando la paleta, y escribe el píxel en la superficie. Esto es más lento que usar operaciones vectorizadas de NumPy, pero garantiza que cada píxel se procese correctamente.

Archivos Afectados

  • src/gpu/renderer.py - Reemplazo de la lógica de renderizado del framebuffer C++ (líneas 508-530)

Tests y Verificación

Validación Visual: Ejecutar python main.py roms/tetris.gb y verificar que la pantalla muestre ROJO SÓLIDO (o rayas rojas si se mantiene el código de debug del Step 0216).

Si se ve la pantalla roja, confirma que:

  • El framebuffer C++ se lee correctamente
  • Los índices de color se mapean correctamente a RGB
  • Los píxeles se escriben correctamente en la superficie de Pygame
  • La superficie se escala y muestra correctamente

Próximo paso: Una vez confirmado que la pantalla roja se muestra, eliminar los hacks de debug (forzado de color rojo, forzado de byte=0xFF en PPU.cpp) y restaurar la lógica normal de renderizado para ver los gráficos reales del juego.

Fuentes Consultadas

  • Pygame Documentation: PixelArray
  • Pan Docs: LCD Control Register, Background Rendering

Integridad Educativa

Lo que Entiendo Ahora

  • Framebuffer Lineal: El framebuffer C++ es un array 1D donde el píxel (y, x) está en el índice [y * 160 + x]. Esta organización es estándar para buffers de video.
  • PixelArray y Cierre Explícito: `pygame.PixelArray` requiere un cierre explícito (`close()`) para aplicar los cambios a la superficie. El context manager debería hacerlo automáticamente, pero el cierre explícito garantiza el comportamiento.
  • Debugging Visual: Cuando el pipeline de datos funciona pero la visualización falla, el problema está en el renderizador. Un bucle explícito ayuda a aislar el problema.

Lo que Falta Confirmar

  • Rendimiento: El bucle explícito es lento en Python puro. Una vez confirmado que funciona, se puede optimizar con NumPy/Surfarray para mejor rendimiento.
  • Formato del Buffer: Verificar que el framebuffer C++ esté en el formato esperado (uint8_t, índices 0-3, organización lineal).

Hipótesis y Suposiciones

Hipótesis Principal: El context manager `with pygame.PixelArray()` no estaba aplicando los cambios correctamente, o había un problema de sincronización entre el bloqueo de la superficie y la escritura de píxeles. El cierre explícito garantiza que los cambios se apliquen antes de escalar y mostrar la superficie.

Próximos Pasos

  • [ ] Confirmar visualmente que la pantalla roja se muestra correctamente
  • [ ] Eliminar los hacks de debug (forzado de color rojo en renderer.py, forzado de byte=0xFF en PPU.cpp)
  • [ ] Restaurar la lógica normal de renderizado de VRAM en PPU.cpp
  • [ ] Verificar que los gráficos reales del juego se muestren correctamente
  • [ ] Optimizar el bucle de renderizado con NumPy/Surfarray para mejor rendimiento