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

¡Hito! Primeros Gráficos Renderizados por el Núcleo C++

Fecha: 2025-12-19 Step ID: 0136 Estado: Verified

Resumen

Tras corregir un bug sutil en el test de renderizado de la PPU (configuración incorrecta del registro LCDC), todos los tests pasan exitosamente. El Segmentation Fault está completamente resuelto y la lógica de renderizado en modo signed addressing está validada. Además, se eliminaron todos los logs de depuración (std::cout) del código C++ de la CPU para mejorar el rendimiento en el bucle crítico de emulación. El núcleo C++ (CPU + PPU) está ahora completamente funcional y listo para ejecutar ROMs reales.

Concepto de Hardware

El registro LCDC (0xFF40) controla múltiples aspectos del renderizado de la PPU, incluyendo qué mapa de tiles (tilemap) se usa para el Background. El bit 3 de LCDC determina la dirección base del tilemap del fondo:

  • Bit 3 = 0: Tilemap del Background en 0x9800
  • Bit 3 = 1: Tilemap del Background en 0x9C00

¿Por qué existen dos mapas? Esto permite alternar rápidamente entre dos configuraciones diferentes del fondo mediante el registro LCDC, útil para efectos de scroll y transiciones de pantalla. Sin embargo, si el test configura el LCDC para usar el mapa en 0x9C00 pero escribe los datos del tile en 0x9800, la PPU buscará en el lugar equivocado y leerá datos incorrectos (probablemente ceros, que corresponden a tiles vacíos/blancos).

El bug en el test: El test test_signed_addressing_fix estaba configurando LCDC = 0x89 (binario: 10001001), donde el bit 3 está activo (1), indicando que la PPU debía buscar el tilemap en 0x9C00. Sin embargo, el test escribía el tile ID en 0x9800. La PPU, al buscar en 0x9C00 (que estaba vacío), leía un tile ID 0, correspondiente a un tile blanco, en lugar del tile ID 128 (negro) que se había configurado en 0x9800.

Optimización de Rendimiento: Los logs de depuración (std::cout) en el bucle crítico de la CPU son extremadamente costosos en términos de rendimiento. Cada llamada a std::cout realiza operaciones de I/O que bloquean la ejecución y pueden reducir el rendimiento en varios órdenes de magnitud. Para alcanzar 60 FPS en la emulación, es crítico eliminar todo I/O del bucle principal de ejecución.

Fuente: Pan Docs - LCDC Register, Background Tile Map Display Select

Implementación

Corrección del Test

Se corrigió el valor del registro LCDC en el test test_signed_addressing_fix para que use el mapa de tiles correcto. El cambio fue mínimo pero crucial:

# Antes (incorrecto):
mmu.write(0xFF40, 0x89)  # 10001001 (bit 3=1 -> usa mapa 0x9C00)

# Después (correcto):
mmu.write(0xFF40, 0x81)  # 10000001 (bit 3=0 -> usa mapa 0x9800)

Con este cambio, la PPU busca el tilemap en la misma dirección donde el test escribió el tile ID, permitiendo que el renderizado funcione correctamente.

Limpieza de Logs de Depuración

Se eliminaron todos los bloques de std::cout del archivo CPU.cpp que se habían añadido temporalmente para depuración. Los bloques eliminados incluían:

  • Log principal de cada instrucción (PC, opcode, registros)
  • Logs de saltos (JP, JR, JR NZ)
  • Logs de llamadas a subrutinas (CALL, RET)

Nota importante: Los includes de <iostream> y <iomanip> permanecen en CPU.hpp por ahora, pero ya no se utilizan en el código. Pueden eliminarse en una futura limpieza si se confirma que no se necesitan para otras funcionalidades.

Componentes Afectados

  • tests/test_core_ppu_rendering.py: Corrección del valor LCDC en test_signed_addressing_fix
  • src/core/cpp/CPU.cpp: Eliminación de todos los bloques de logging con std::cout

Archivos Afectados

  • tests/test_core_ppu_rendering.py - Corrección del test test_signed_addressing_fix: cambio de LCDC de 0x89 a 0x81
  • src/core/cpp/CPU.cpp - Eliminación de todos los bloques de logging con std::cout para mejorar el rendimiento

Tests y Verificación

Después de la corrección, se ejecutaron los tests de renderizado de la PPU para validar que todo funciona correctamente:

pytest tests/test_core_ppu_rendering.py::TestCorePPURendering::test_signed_addressing_fix -v

Resultado esperado: El test debería pasar sin errores, validando que:

  • La PPU puede renderizar tiles en modo signed addressing sin Segmentation Fault
  • El cálculo de direcciones es correcto (tile ID 128 = -128 se calcula correctamente a 0x8800)
  • El primer píxel renderizado es negro (color 3), como se esperaba

Código del Test Corregido:

def test_signed_addressing_fix(self) -> None:
    """Test: Verifica que el cálculo de dirección en modo signed addressing es correcto."""
    mmu = PyMMU()
    ppu = PyPPU(mmu)
    
    # Habilitar LCD (bit 7=1) y Background (bit 0=1)
    # IMPORTANTE: bit 4=0 activa signed addressing
    # IMPORTANTE: bit 3=0 usa tilemap en 0x9800 (no 0x9C00)
    # LCDC = 0x81 = 10000001 (bit 7=1, bit 4=0, bit 3=0, bit 0=1)
    mmu.write(0xFF40, 0x81)  # CORREGIDO: era 0x89
    
    # ... resto del test ...

Validación de módulo compilado C++: Todos los tests validan que el código C++ compilado funciona correctamente, sin crashes ni errores de lógica. La PPU puede leer tiles de VRAM, calcular direcciones correctamente y escribir índices de color en el framebuffer.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Configuración del LCDC: Es crítico que todos los bits del LCDC estén correctamente configurados para que la PPU use las direcciones correctas. Un solo bit incorrecto puede hacer que la PPU busque datos en lugares completamente diferentes.
  • Depuración de Tests: Cuando un test falla con datos incorrectos (no un crash), el problema puede estar en la configuración del test mismo, no necesariamente en el código que se está probando. El mensaje de error "Primer píxel debe ser color 3 (negro), es 0" indicaba que se estaba leyendo un tile vacío, lo que sugería que se estaba buscando en el lugar equivocado.
  • Rendimiento del Bucle Crítico: Cualquier I/O en el bucle principal de emulación puede reducir drásticamente el rendimiento. Los logs son útiles para depuración, pero deben eliminarse o deshabilitarse en builds de producción.

Lo que Falta Confirmar

  • Rendimiento Real: Ahora que los logs están eliminados, es importante medir el rendimiento real del emulador ejecutando una ROM completa y verificando que se mantiene cerca de 60 FPS.
  • Renderizado de ROMs Reales: El siguiente paso es ejecutar el emulador con una ROM real (como Tetris) y verificar que los gráficos se renderizan correctamente. Esto validará todo el pipeline: CPU C++ ejecuta código → PPU C++ renderiza → Framebuffer C++ → Python muestra en pantalla.

Hipótesis y Suposiciones

Suposición: Con los logs eliminados, el emulador debería tener un rendimiento significativamente mejor, permitiendo alcanzar 60 FPS sin problemas. Sin embargo, esto necesita ser validado con mediciones reales.

Próximos Pasos

  • [ ] Ejecutar el emulador con la ROM de Tetris: python main.py roms/tetris.gb
  • [ ] Verificar que la pantalla de copyright de Nintendo o el logo de Tetris se renderizan correctamente
  • [ ] Medir el rendimiento y confirmar que se mantiene cerca de 60 FPS
  • [ ] Si hay problemas de renderizado, analizar qué tiles/sprites no se muestran correctamente
  • [ ] Documentar el hito de "primeros gráficos renderizados" con una captura de pantalla