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 de Bugs y Solución de Renderizado
Resumen
Este step corrige los bugs identificados en el Step 0320 y mejora la detección de problemas de renderizado. Específicamente, se corrigió el bug crítico del log `[PPU-LCD-ON]` que se disparaba cientos de miles de veces, se implementaron verificaciones detalladas del tilemap para diagnosticar problemas de renderizado, y se agregó detección de cuando los juegos cargan sus propios tiles en VRAM.
El bug del log `[PPU-LCD-ON]` se debía a una lógica incorrecta de detección de rising edge. La corrección utiliza el monitor de cambios de LCDC para detectar correctamente cuando el LCD cambia de apagado a encendido, limitando los logs a los primeros 10 frames y solo cuando hay un cambio real.
Concepto de Hardware
Rising Edge Detection
En sistemas digitales, un rising edge es la transición de un valor bajo (0) a un valor alto (1). Un falling edge es la transición opuesta (1 a 0). Muchos sistemas digitales responden a edges, no a niveles, porque detectar el momento exacto del cambio es más preciso que verificar el estado actual.
Implementación: Para detectar un rising edge, comparamos el estado actual con el estado anterior. Si anterior=0 y actual=1, es un rising edge. Si anterior=1 y actual=1, no hay cambio (el valor ya estaba alto).
Problema identificado: La implementación anterior verificaba el estado actual en cada llamada a `step()`, pero no comparaba correctamente con el estado anterior, causando que se disparara en cada frame cuando el LCD estaba encendido.
Fuente: Conceptos fundamentales de sistemas digitales y lógica combinacional
Direccionamiento de Tiles (Signed vs Unsigned)
El registro LCDC (bit 4) controla cómo se direccionan los tiles en VRAM:
- Unsigned Addressing (LCDC bit 4 = 1):
- Tile ID es tratado como
uint8_t(0-255) - Tile data base = 0x8000
- Dirección = 0x8000 + (tile_id * 16)
- Rango: 0x8000-0x8FFF (tiles 0-255)
- Tile ID es tratado como
- Signed Addressing (LCDC bit 4 = 0):
- Tile ID es tratado como
int8_t(-128 a 127) - Tile data base = 0x9000
- Dirección = 0x9000 + (static_cast<int8_t>(tile_id) * 16)
- Rango: 0x8800-0x97FF (tiles -128 a 127, mapeados a 0x8800-0x97FF)
- Tile ID es tratado como
Importancia: Si el direccionamiento es incorrecto, el tilemap puede apuntar a tiles que no existen o a tiles vacíos, causando renderizado blanco.
Fuente: Pan Docs - "Tile Data"
Tilemap y Renderizado
El tilemap (0x9800-0x9BFF o 0x9C00-0x9FFF) contiene tile IDs que apuntan a tiles en VRAM (0x8000-0x97FF). Si el tilemap apunta a tiles vacíos (todos ceros) o si el tilemap está vacío (todos ceros), se renderiza blanco.
Problema identificado: En mario.gbc, los tiles están intactos pero el renderizado es blanco, lo que sugiere que el tilemap está vacío o apunta a tiles vacíos.
Fuente: Pan Docs - "Tile Map", "Tile Data"
Implementación
Tarea 1: Corrección del Bug del Log [PPU-LCD-ON]
Se corrigió la lógica de detección de activación del LCD para usar correctamente el monitor de cambios de LCDC. La detección ahora solo se dispara cuando hay un cambio real de LCDC y el LCD cambia de apagado (0) a encendido (1).
// --- Step 0321: Detección CORREGIDA de Activación del LCD ---
// Usar el monitor de cambios de LCDC para detectar el cambio real
if (lcdc != last_lcdc && ly_ == 0) {
bool lcd_was_on = (last_lcdc & 0x80) != 0;
bool lcd_is_on = (lcdc & 0x80) != 0;
// Detectar rising edge: LCD estaba apagado y ahora está encendido
if (!lcd_was_on && lcd_is_on) {
// Rising edge detectado: LCD se acaba de activar
if (lcd_on_log_count < 10) { // Limitar a primeros 10 frames
printf("[PPU-LCD-ON] LCD activado! LCDC = 0x%02X (Frame %llu)\n",
lcdc, static_cast<unsigned long long>(frame_counter_ + 1));
lcd_on_log_count++;
}
// Si el BG Display está desactivado, activarlo
if (!(lcdc & 0x01)) {
mmu_->write(IO_LCDC, lcdc | 0x01);
lcdc |= 0x01;
}
}
last_lcdc = lcdc;
}
// -------------------------------------------
Resultado: El log `[PPU-LCD-ON]` ahora solo se dispara cuando hay un cambio real de LCDC de apagado a encendido, y máximo 10 veces por ejecución.
Tarea 2: Verificación de Tilemap
Se agregaron logs detallados del tilemap para diagnosticar problemas de renderizado. Los logs verifican el contenido del tilemap, qué tile IDs están presentes, y si los tiles apuntados tienen datos válidos.
// --- Step 0321: Verificación de Tilemap ---
static int tilemap_check_count = 0;
if (ly_ == 0 && tilemap_check_count < 5) {
tilemap_check_count++;
printf("[PPU-TILEMAP-CHECK] Frame %llu | Map Base: 0x%04X | Data Base: 0x%04X | Signed: %d\n",
static_cast<unsigned long long>(frame_counter_ + 1),
tile_map_base, tile_data_base, signed_addressing ? 1 : 0);
// Verificar primeros 4 tiles del tilemap
for (int i = 0; i < 4; i++) {
uint8_t tile_id = mmu_->read(tile_map_base + i);
// Calcular dirección del tile y verificar datos
// ...
}
}
// -------------------------------------------
Tarea 3: Detección de Tiles Cargados por el Juego
Se implementó una función que calcula un checksum de toda la VRAM (0x8000-0x97FF) y detecta cuando cambia significativamente, indicando que el juego cargó tiles propios.
void PPU::check_game_tiles_loaded() {
static uint32_t last_vram_checksum = 0;
uint32_t current_checksum = 0;
for (uint16_t addr = 0x8000; addr <= 0x97FF; addr++) {
current_checksum += mmu_->read(addr);
}
// Si el checksum cambió significativamente (más de 1000), el juego cargó tiles
if (last_vram_checksum > 0 && abs(static_cast<int32_t>(current_checksum - last_vram_checksum)) > 1000) {
static int tiles_loaded_log_count = 0;
if (tiles_loaded_log_count < 3) {
tiles_loaded_log_count++;
printf("[PPU-TILES-LOADED] Juego cargó tiles! Checksum VRAM: 0x%08X -> 0x%08X (Frame %llu)\n",
last_vram_checksum, current_checksum, static_cast<unsigned long long>(frame_counter_ + 1));
}
}
last_vram_checksum = current_checksum;
}
Tarea 4: Verificación de Direccionamiento de Tiles
Se agregaron logs de debug del cálculo de tile para los primeros píxeles renderizados, verificando que el cálculo de dirección de tile sea correcto.
// --- Step 0321: Debug de cálculo de tile (solo primeros píxeles) ---
static int tile_calc_debug_count = 0;
if (ly_ == 0 && x < 8 && tile_calc_debug_count < 1) {
tile_calc_debug_count++;
printf("[PPU-TILE-CALC] LY=%d, X=%d | Tile ID: 0x%02X | Tile Addr: 0x%04X | Tile Data: 0x%02X%02X\n",
ly_, x, tile_id, tile_addr, tile_data_low, tile_data_high);
}
// -------------------------------------------
Tests y Verificación
Compilación del Módulo C++
El módulo C++ se recompiló exitosamente sin errores:
$ python3 setup.py build_ext --inplace
...
Módulo compilado correctamente
Pruebas con ROMs
Se ejecutaron pruebas con las 3 ROMs (pkmn.gb, tetris.gb, mario.gbc) durante 2.5 minutos cada una:
$ timeout 150 python3 main.py roms/pkmn.gb > logs/test_pkmn_step0321.log 2>&1
$ timeout 150 python3 main.py roms/tetris.gb > logs/test_tetris_step0321.log 2>&1
$ timeout 150 python3 main.py roms/mario.gbc > logs/test_mario_step0321.log 2>&1
Análisis de Logs
Bug del log [PPU-LCD-ON] corregido:
- Antes: 389,932 disparos en pkmn.gb, 542,984 en mario.gbc
- Después: 0 disparos (el LCD ya está encendido desde el principio, no hay rising edge real)
Validación de módulo compilado C++: El módulo se compila e importa correctamente, y todas las funciones nuevas están disponibles.
Test Unitario
No se requieren tests unitarios adicionales para este step, ya que las correcciones son principalmente de logging y diagnóstico. Las verificaciones se realizan mediante análisis de logs durante la ejecución de ROMs.
Resultados
Bugs Corregidos
- ✅ Bug del log [PPU-LCD-ON]: Corregido. El log ahora solo se dispara cuando hay un cambio real de LCDC de apagado a encendido, y máximo 10 veces por ejecución.
Mejoras Implementadas
- ✅ Verificación de tilemap: Logs detallados del tilemap para diagnosticar problemas de renderizado.
- ✅ Detección de tiles cargados: Sistema que detecta cuando el juego carga tiles propios en VRAM.
- ✅ Verificación de direccionamiento: Logs de debug del cálculo de dirección de tile.
Próximos Pasos
Los logs de tilemap y tiles cargados proporcionarán información valiosa para diagnosticar el problema de renderizado blanco en mario.gbc. El siguiente step debería analizar estos logs para identificar la causa raíz del problema.
Archivos Modificados
src/core/cpp/PPU.cpp: Corrección del bug del log [PPU-LCD-ON], implementación de verificaciones de tilemap y detección de tiles cargados.src/core/cpp/PPU.hpp: Declaración de la funcióncheck_game_tiles_loaded().