⚠️ 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 Amanecer de Tetris: Limpieza y Victoria

Fecha: 2025-12-22 Step ID: 0220 Estado: VERIFIED

Resumen

Tras confirmar visualmente el funcionamiento de todo el pipeline con el "Test de la Caja Azul" (Step 0218), se retiraron todas las herramientas de diagnóstico, hacks visuales y sondas de datos. Se restauró la lógica original de lectura de VRAM en C++ y la paleta de colores correcta en Python. El sistema está ahora limpio y operando con precisión de hardware.

El momento de la verdad: Con el código restaurado, ejecutamos Tetris y visualizamos los gráficos reales del juego. Ya no habrá rayas rojas ni cuadros azules, solo la gloria de la emulación pura.

Concepto de Hardware

Durante la fase de depuración, implementamos múltiples "andamios" (scaffolding) para diagnosticar problemas:

  • Hacks Visuales: Cuadro azul en el centro para verificar que la superficie de Pygame está conectada a la ventana.
  • Paleta de Debug: Forzar color rojo en el índice 3 para confirmar visualmente que estamos pintando lo que queremos.
  • Test del Rotulador Negro: Rayas verticales forzadas en C++ para verificar que el pipeline C++ → Python funciona.
  • Sondas de Datos: Prints de diagnóstico en múltiples puntos del pipeline para rastrear el flujo de datos.

Estos andamios cumplieron su propósito: confirmaron que cada componente funciona correctamente. Sin embargo, en producción, estos hacks interfieren con el renderizado real del juego. La restauración elimina todos estos andamios y deja solo la lógica limpia y precisa del hardware.

Principio de Clean Code: Los andamios deben retirarse una vez que cumplen su propósito. El código de producción debe ser limpio, legible y sin artefactos de depuración.

Implementación

Se restauraron tres archivos principales eliminando todos los hacks de debug:

Componentes modificados

  • src/gpu/renderer.py: Eliminación del cuadro azul y paleta roja de debug
  • src/core/cpp/PPU.cpp: Restauración de la lógica original de lectura de VRAM
  • src/viboy.py: Eliminación de sondas de datos

Cambios técnicos

1. Restauración en renderer.py:

  • Eliminado el cuadro azul de prueba (líneas 537-539).
  • Eliminado el forzado de color rojo en la paleta (líneas 499-503 y 652-656).
  • Eliminados los prints de diagnóstico excesivos.
  • Mantenida la lógica robusta de render_frame (bucle explícito).
# En src/gpu/renderer.py

def _update_palette_from_bgp(self, bgp):
    if bgp == 0:
        bgp = 0xE4 # Fallback seguro

    for i in range(4):
        color_idx = (bgp >> (i * 2)) & 0x03
        # RESTAURADO: Usamos el color real, no el rojo
        self.palette[i] = self.COLORS[color_idx]

def render_frame(self, framebuffer):
    # Renderizado robusto
    px_array = pygame.PixelArray(self.surface)
    WIDTH, HEIGHT = 160, 144
    
    for y in range(HEIGHT):
        for x in range(WIDTH):
            idx = y * WIDTH + x
            color_index = framebuffer[idx]
            color_rgb = self.palette[color_index & 3]
            px_array[x, y] = color_rgb
    
    px_array.close()
    
    # Blit estándar a la ventana
    scaled_surface = pygame.transform.scale(self.surface, self.window.get_size())
    self.window.blit(scaled_surface, (0, 0))
    pygame.display.flip()

2. Restauración en PPU.cpp:

  • Eliminado el bloque del "Test del Rotulador Negro" (rayas verticales forzadas).
  • Restaurada la lógica original de lectura de VRAM con validación correcta.
  • Eliminadas las sondas de debug ([C++ WRITE PROBE], etc.).
  • Eliminado #include <cstdio> ya que no se usa.
// En PPU::render_scanline()

// Usar la condición VALIDADA
if (tile_line_addr >= 0x8000 && tile_line_addr <= 0x9FFE) {
    
    // --- RESTAURADO: LÓGICA REAL DE VRAM ---
    uint8_t byte1 = mmu_->read(tile_line_addr);
    uint8_t byte2 = mmu_->read(tile_line_addr + 1);
    
    uint8_t bit_index = 7 - (map_x % 8);
    uint8_t bit_low = (byte1 >> bit_index) & 1;
    uint8_t bit_high = (byte2 >> bit_index) & 1;
    uint8_t color_index = (bit_high << 1) | bit_low;

    framebuffer_[line_start_index + x] = color_index;
    // ---------------------------------------

} else {
    framebuffer_[line_start_index + x] = 0;
}

3. Limpieza en viboy.py:

  • Eliminados los prints de la sonda de datos ([PYTHON SNAPSHOT PROBE]).
  • Mantenida la lógica del bytearray (es buena práctica defensiva).

Archivos Afectados

  • src/gpu/renderer.py - Eliminación de hacks visuales y restauración de paleta (líneas 490-550)
  • src/core/cpp/PPU.cpp - Restauración de lógica VRAM y eliminación de sondas (líneas 332-468)
  • src/viboy.py - Eliminación de sondas de datos (líneas 763-775)

Tests y Verificación

Comando ejecutado: python main.py roms/tetris.gb

Resultado Esperado:

  • Pantalla: Gráficos reales de Tetris (pantalla de copyright o logo de Nintendo cayendo)
  • Sin rayas rojas ni cuadros azules
  • Paleta de colores correcta (verde/amarillo original de Game Boy)
  • Renderizado fluido a 60 FPS

Validación de módulo compilado C++: El renderizado ahora lee directamente desde la VRAM usando la lógica original validada. La paleta se aplica correctamente según el registro BGP (0xFF47).

Prueba de Fuego: Si Tetris muestra gráficos correctos, el pipeline completo está funcionando: CPU ejecuta código → VRAM se llena con tiles → PPU renderiza scanlines → Python muestra el frame.

Referencias

  • Pan Docs - Tile Data, 2bpp Format, Background Palette
  • Pan Docs - LCD Control Register, Background Rendering
  • Clean Code - Robert C. Martin (Principio de Retirar Andamios)