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

Corrección Crítica: Error de Validación de VRAM en PPU

Fecha: 2025-12-21 Step ID: 0210 Estado: ✅ VERIFIED

Resumen

Tras una auditoría completa del código de PPU::render_scanline(), se identificó un error lógico crítico en la validación de direcciones VRAM. La condición tile_line_addr < 0xA000 - 1 era incorrecta y causaba que muchos tiles válidos fueran rechazados, escribiendo color 0 (blanco) en el framebuffer en lugar del color real del tile. Este error explicaba por qué la pantalla permanecía blanca incluso cuando se forzaban los bytes de tile a 0xFF (negro) en el Step 0209.

Corrección aplicada: Cambiar la validación a tile_line_addr >= 0x8000 && tile_line_addr <= 0x9FFE, garantizando que tanto tile_line_addr como tile_line_addr + 1 estén dentro del rango válido de VRAM (0x8000-0x9FFF).

Concepto de Hardware: Validación de Acceso a VRAM

La VRAM (Video RAM) de la Game Boy ocupa 8KB de memoria, desde la dirección 0x8000 hasta 0x9FFF (inclusive). Cada tile ocupa 16 bytes (8 líneas × 2 bytes por línea), y cada línea de un tile se representa con 2 bytes consecutivos:

  • Byte 1: Bits bajos de cada píxel (bit 7 = píxel 0, bit 6 = píxel 1, ...)
  • Byte 2: Bits altos de cada píxel (bit 7 = píxel 0, bit 6 = píxel 1, ...)

Cuando la PPU renderiza una línea de escaneo, necesita leer dos bytes consecutivos para decodificar cada línea de tile. Por lo tanto, la validación de direcciones debe garantizar que:

  1. tile_line_addr >= 0x8000 (dentro del inicio de VRAM)
  2. tile_line_addr + 1 <= 0x9FFF (el segundo byte también está dentro de VRAM)

Esto implica que tile_line_addr <= 0x9FFE es la condición correcta para el límite superior.

Error encontrado: La condición original tile_line_addr < 0xA000 - 1 (equivalente a tile_line_addr < 0x9FFF) rechazaba direcciones válidas como 0x9FFE, que debería ser aceptada porque 0x9FFE + 1 = 0x9FFF está dentro de VRAM. Además, si tile_line_addr = 0x9FFF, entonces tile_line_addr + 1 = 0xA000 estaría fuera de VRAM, por lo que debe ser rechazada correctamente.

Impacto del error: Muchos tiles válidos caían en el bloque else y se escribía color_index = 0 (blanco) en el framebuffer, independientemente del contenido real de VRAM. Esto explicaba por qué la pantalla permanecía blanca incluso cuando se forzaban los bytes a 0xFF.

Fuente: Pan Docs - "VRAM", "Tile Data Format", "PPU Rendering"

Implementación

Se corrigió la condición de validación en PPU::render_scanline() dentro del archivo src/core/cpp/PPU.cpp.

Corrección Aplicada

Antes (incorrecto):

if (tile_line_addr >= 0x8000 && tile_line_addr < 0xA000 - 1) {
    // Leer y decodificar tile
} else {
    framebuffer_[line_start_index + x] = 0; // Color por defecto
}

Después (correcto):

if (tile_line_addr >= 0x8000 && tile_line_addr <= 0x9FFE) {
    uint8_t byte1 = mmu_->read(tile_line_addr);
    uint8_t byte2 = mmu_->read(tile_line_addr + 1);
    // ... decodificación ...
} else {
    framebuffer_[line_start_index + x] = 0; // Dirección inválida
}

Análisis del Error

La condición original tile_line_addr < 0xA000 - 1 es equivalente a tile_line_addr < 0x9FFF, lo que significa:

  • tile_line_addr = 0x9FFE: ❌ Rechazado (incorrecto, debería ser aceptado)
  • tile_line_addr = 0x9FFF: ❌ Rechazado (correcto, porque 0x9FFF + 1 = 0xA000 está fuera de VRAM)

La condición corregida tile_line_addr <= 0x9FFE garantiza:

  • tile_line_addr = 0x9FFE: ✅ Aceptado (correcto, porque 0x9FFE + 1 = 0x9FFF está dentro de VRAM)
  • tile_line_addr = 0x9FFF: ❌ Rechazado (correcto, porque 0x9FFF + 1 = 0xA000 está fuera de VRAM)

Comentarios Educativos Añadidos

Se añadieron comentarios extensos explicando el problema, la solución y el impacto, siguiendo el principio de documentación educativa del proyecto.

Archivos Afectados

  • src/core/cpp/PPU.cpp - Corrección de validación de VRAM en render_scanline() (líneas 349-371)

Tests y Verificación

Compilación: El código se compiló exitosamente con python setup.py build_ext --inplace. No se introdujeron errores de compilación.

Validación de módulo compilado C++: La extensión Cython se generó correctamente y está lista para pruebas en tiempo de ejecución.

Prueba esperada: Con esta corrección, los tiles válidos deberían ser aceptados correctamente y sus colores deberían escribirse en el framebuffer. Si el diagnóstico del Step 0209 (forzar bytes a 0xFF) ahora produce una pantalla negra, confirmaremos que el problema era la validación de direcciones, no el framebuffer o la paleta.

Próximo paso de verificación: Ejecutar el emulador con una ROM de test y verificar que los tiles se renderizan correctamente. Si la pantalla sigue blanca, el problema puede estar en otro lugar (por ejemplo, la ROM borra la VRAM antes del renderizado, o hay un problema de direccionamiento de tiles).

Fuentes Consultadas

  • Pan Docs: "VRAM" - Rango de direcciones 0x8000-0x9FFF (8KB)
  • Pan Docs: "Tile Data Format" - 16 bytes por tile, 2 bytes por línea
  • Pan Docs: "PPU Rendering" - Lectura de datos de tile desde VRAM

Integridad Educativa

Lo que Entiendo Ahora

  • Validación de rangos de memoria: Cuando se necesita leer múltiples bytes consecutivos, la validación debe garantizar que todos los bytes estén dentro del rango válido, no solo el primero.
  • Errores de off-by-one: Los errores de validación de rangos son comunes y pueden causar comportamientos inesperados que son difíciles de diagnosticar sin una auditoría cuidadosa del código.
  • Importancia de la auditoría de código: Asumir que el código funciona como se espera sin verificar la implementación real puede llevar a diagnósticos incorrectos y pérdida de tiempo.

Lo que Falta Confirmar

  • Renderizado real: Verificar que con esta corrección, los tiles se renderizan correctamente cuando la VRAM contiene datos válidos.
  • Comportamiento de la ROM: Si la pantalla sigue blanca después de esta corrección, puede ser que la ROM borre la VRAM antes del primer renderizado, o que haya un problema de direccionamiento de tiles (signed vs unsigned).

Hipótesis y Suposiciones

Hipótesis principal: El error de validación era la causa principal de la pantalla blanca. Con esta corrección, los tiles válidos deberían renderizarse correctamente.

Suposición: Si la pantalla sigue blanca después de esta corrección (incluso con bytes forzados a 0xFF), el problema puede estar en:

  • La ROM borra la VRAM antes del primer renderizado
  • Error en el cálculo de direcciones de tile (signed vs unsigned addressing)
  • Problema en la aplicación de la paleta BGP en Python

Próximos Pasos

  • [ ] Ejecutar el emulador y verificar si la pantalla ahora muestra color negro (con bytes forzados a 0xFF del Step 0209)
  • [ ] Si la pantalla es negra, remover el código de diagnóstico del Step 0209 y verificar el renderizado normal
  • [ ] Si la pantalla sigue blanca, investigar otros posibles problemas (direccionamiento de tiles, paleta BGP, etc.)
  • [ ] Una vez confirmado el renderizado, implementar la carga correcta de tiles desde la ROM