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.
La Sonda en el Píxel Cero
Resumen
La "Inundación de VRAM" (Step 0208) y el "Forzado de Negro" (Step 0209) han fallado, lo que indica que la lógica de validación de direcciones en render_scanline está rechazando sistemáticamente los accesos a VRAM, desviando el flujo al bloque else (blanco). Matemáticamente esto no debería ocurrir, así que debemos ver los valores en tiempo real.
Objetivo: Instrumentar PPU::render_scanline() con printf para mostrar las variables de cálculo (LCDC, direcciones, Tile ID) exclusivamente para el píxel (0,0) del fotograma. Esto nos dará una radiografía exacta de por qué la dirección se considera inválida sin inundar la consola con miles de líneas de log.
Concepto de Hardware: Diagnóstico Quirúrgico
Cuando un sistema falla de manera sistemática, necesitamos datos exactos, no suposiciones. El problema que enfrentamos es que la condición de validación if (tile_line_addr >= 0x8000 && tile_line_addr <= 0x9FFE) está fallando sistemáticamente, llevando la ejecución al bloque else que escribe color 0 (blanco) en el framebuffer.
El problema matemático: Cualquier tile_id válido (0-255) debería generar una dirección válida dentro de la VRAM (0x8000-0x9FFF). Si esto no está ocurriendo, hay un error en:
- Cálculo de direcciones: El
tile_map_addrpuede estar fuera de rango, leyendo basura del mapa de tiles. - Direccionamiento de tiles: El modo signed/unsigned puede estar calculando direcciones incorrectas.
- Desbordamiento de tipos: Un
uint16_tpuede estar desbordándose o unint8_tpuede estar interpretándose incorrectamente. - Validación incorrecta: Aunque corregimos la condición en el Step 0210, puede haber otro problema que no vimos.
La solución quirúrgica: En lugar de imprimir miles de líneas de log para cada píxel, instrumentamos el código para imprimir los valores de cálculo solo una vez por fotograma, específicamente cuando ly_ == 0 y x == 0 (el primer píxel del primer fotograma). Esto nos dará una instantánea exacta del estado interno de la PPU en el momento crítico del renderizado.
Fuente: Pan Docs - "PPU Rendering", "Tile Addressing", "VRAM Access"
Implementación
Se añadió un bloque de diagnóstico en PPU::render_scanline() dentro del archivo src/core/cpp/PPU.cpp, justo antes de la condición de validación de VRAM.
Inclusión de Header
Se añadió #include <cstdio> al inicio del archivo para habilitar printf.
#include "PPU.hpp"
#include "MMU.hpp"
#include <algorithm>
#include <cstdio> // Step 0211: Para printf de diagnóstico
Bloque de Diagnóstico
Se añadió el siguiente bloque de código justo después del cálculo de tile_line_addr y antes de la condición de validación:
// --- Step 0211: SONDA DE DIAGNÓSTICO (Píxel 0,0) ---
// Imprimir el estado interno solo una vez por frame (al inicio)
// Esto nos permite ver los valores exactos que la PPU está calculando
// sin inundar la consola con miles de líneas de log
if (ly_ == 0 && x == 0) {
printf("--- [PPU DIAGNOSTIC FRAME START] ---\n");
printf("LCDC: 0x%02X | SCX: 0x%02X | SCY: 0x%02X\n", lcdc, scx, scy);
printf("MapBase: 0x%04X | MapAddr: 0x%04X | TileID: 0x%02X\n", tile_map_base, tile_map_addr, tile_id);
printf("DataBase: 0x%04X | Signed: %d\n", tile_data_base, signed_addressing ? 1 : 0);
printf("CalcTileAddr: 0x%04X | LineAddr: 0x%04X\n", tile_addr, tile_line_addr);
bool valid = (tile_line_addr >= 0x8000 && tile_line_addr <= 0x9FFE);
printf("VALID CHECK: %s\n", valid ? "PASS" : "FAIL");
printf("------------------------------------\n");
}
// -------------------------------------------------
Variables Diagnosticadas
El bloque de diagnóstico imprime las siguientes variables críticas:
- LCDC: Registro de control del LCD (0xFF40), incluye bits de habilitación y modo de direccionamiento.
- SCX/SCY: Registros de scroll horizontal y vertical (0xFF43/0xFF42).
- MapBase: Dirección base del mapa de tiles (0x9800 o 0x9C00 según LCDC bit 3).
- MapAddr: Dirección calculada en el mapa de tiles para el tile actual.
- TileID: ID del tile leído desde el mapa (0-255).
- DataBase: Dirección base de los datos de tiles (0x8000 o 0x9000 según LCDC bit 4). Signed: Indica si se usa direccionamiento signed (1) o unsigned (0).
- CalcTileAddr: Dirección base del tile calculada (antes de añadir el offset de línea).
- LineAddr: Dirección final de la línea del tile (tile_addr + line_in_tile * 2).
- VALID CHECK: Resultado de la validación (PASS si está en rango, FAIL si no).
Archivos Afectados
src/core/cpp/PPU.cpp- Añadido#include <cstdio>y bloque de diagnóstico enrender_scanline()(líneas 347-361)
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: Al ejecutar el emulador con python main.py roms/tetris.gb, deberíamos ver en la consola un bloque de diagnóstico que muestra los valores exactos calculados para el píxel (0,0) del primer fotograma. Este bloque aparecerá una vez por fotograma (60 veces por segundo), pero solo mostrará los valores del primer píxel, evitando inundar la consola.
Análisis de resultados esperados: Con estos datos, podremos identificar exactamente dónde está el error:
- Si TileID es extraño: Quizás leemos basura del mapa de tiles (MapAddr fuera de rango).
- Si MapAddr está fuera de rango: Error en el cálculo de posición en el mapa de tiles.
- Si LineAddr es 0 o enorme: Error de desbordamiento o tipos de datos incorrectos.
- Si VALID CHECK dice FAIL: Veremos por qué el número exacto falla la condición, permitiéndonos corregir el problema en el siguiente paso.
Fuentes Consultadas
- Pan Docs: "PPU Rendering" - Proceso de renderizado de líneas de escaneo
- Pan Docs: "Tile Addressing" - Cálculo de direcciones de tiles (signed vs unsigned)
- Pan Docs: "VRAM Access" - Validación de rangos de memoria VRAM
Integridad Educativa
Lo que Entiendo Ahora
- Diagnóstico quirúrgico: Cuando un problema es sistemático, es mejor instrumentar el código para ver los valores exactos en tiempo de ejecución que adivinar basándose en suposiciones.
- Control de salida de logs: Imprimir logs para cada píxel (160×144 = 23,040 píxeles por fotograma) saturaría la consola. Es mejor limitar la salida a casos específicos (como el primer píxel del primer fotograma).
- Validación de hipótesis: Aunque matemáticamente cualquier tile_id debería generar una dirección válida, necesitamos verificar que los cálculos intermedios sean correctos.
Lo que Falta Confirmar
- Valores reales: Ver los valores exactos que la PPU está calculando en tiempo de ejecución para identificar dónde está el error.
- Origen del problema: Determinar si el problema está en el cálculo de direcciones, la lectura del mapa de tiles, o la validación de rangos.
Hipótesis y Suposiciones
Hipótesis principal: La condición de validación está fallando porque alguna de las variables intermedias (MapAddr, TileID, CalcTileAddr, LineAddr) tiene un valor incorrecto que no podemos ver sin instrumentar el código.
Suposición: Una vez que veamos los valores exactos, la corrección será obvia. Puede ser:
- Un error en el cálculo de MapAddr (fuera del rango 0x9800-0x9FFF)
- Un TileID inválido leído desde el mapa (basura de memoria no inicializada)
- Un error en el cálculo de direcciones signed/unsigned
- Un desbordamiento de tipos de datos (uint16_t, int8_t)
Próximos Pasos
- [ ] Recompilar el módulo C++ con
.\rebuild_cpp.ps1opython setup.py build_ext --inplace - [ ] Ejecutar el emulador:
python main.py roms/tetris.gb - [ ] Analizar la consola: Buscar el bloque
[PPU DIAGNOSTIC FRAME START] - [ ] Identificar el error: Comparar los valores impresos con los valores esperados según la documentación
- [ ] Aplicar corrección: Una vez identificado el problema, corregirlo en el siguiente step