⚠️ 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 de Paleta: ¿Por qué el Negro es Blanco?

Fecha: 2025-12-22 Step ID: 0215 Estado: DRAFT

Resumen

El Step 0213 confirmó que Python recibe correctamente el valor 3 (negro) en el framebuffer, pero la pantalla sigue blanca. Esto indica que el sistema de renderizado en Python está mapeando el índice 3 al color blanco, probablemente debido a que el registro BGP (0xFF47) es 0x00 o la lógica de decodificación de paleta es incorrecta.

Hallazgo crítico: Si el registro BGP es 0x00, todos los pares de bits son 00, lo que significa que todos los índices de color (0, 1, 2, 3) se mapean al color 0 de la paleta (blanco). Esto explica por qué incluso píxeles negros (índice 3) se renderizan como blancos.

Solución: Se añadió una sonda de diagnóstico para verificar el valor de BGP en Python, y se implementó una corrección que fuerza un valor por defecto estándar (0xE4) cuando BGP es 0x00, asegurando que los índices de color se mapeen correctamente a los colores de la paleta.

Concepto de Hardware: El Registro BGP (Background Palette)

El registro BGP (Background Palette, dirección 0xFF47) es un byte de 8 bits que define cómo se mapean los índices de color (0-3) del framebuffer a los colores reales de la paleta de grises de la Game Boy.

Formato del registro BGP:

  • Bits 0-1: Color para índice 0 (píxeles con valor 0)
  • Bits 2-3: Color para índice 1 (píxeles con valor 1)
  • Bits 4-5: Color para índice 2 (píxeles con valor 2)
  • Bits 6-7: Color para índice 3 (píxeles con valor 3)

Cada par de bits puede tener un valor de 0-3, que se mapea a los 4 tonos de gris disponibles:

  • 0: Blanco (255, 255, 255)
  • 1: Gris Claro (170, 170, 170)
  • 2: Gris Oscuro (85, 85, 85)
  • 3: Negro (0, 0, 0)

El problema del BGP = 0x00:

Si el registro BGP es 0x00 (todos los bits en 0), entonces:

  • Índice 0 → Color 0 (Blanco)
  • Índice 1 → Color 0 (Blanco)
  • Índice 2 → Color 0 (Blanco)
  • Índice 3 → Color 0 (Blanco)

Esto significa que todos los píxeles, independientemente de su índice de color, se renderizan como blanco. Incluso si el framebuffer contiene correctamente el valor 3 (negro), el renderizador lo mapea al color 0 (blanco) porque el registro BGP está en 0x00.

Valor por defecto estándar:

El valor 0xE4 (11100100 en binario) es un valor común usado por muchos juegos de Game Boy. Este valor mapea:

  • Índice 0 → Color 0 (Blanco) - bits 0-1 = 00
  • Índice 1 → Color 1 (Gris Claro) - bits 2-3 = 01
  • Índice 2 → Color 2 (Gris Oscuro) - bits 4-5 = 10
  • Índice 3 → Color 3 (Negro) - bits 6-7 = 11

Fuente: Pan Docs - Background Palette Register (BGP), LCD Control Register.

Implementación

Se implementaron dos modificaciones principales:

  1. Sonda de diagnóstico de BGP: Se añadió código en src/viboy.py para leer y mostrar el valor del registro BGP cuando se captura el framebuffer, permitiendo diagnosticar si el problema es un BGP en 0x00.
  2. Corrección de paleta en el renderer: Se modificó src/gpu/renderer.py para detectar cuando BGP es 0x00 y forzar un valor por defecto estándar (0xE4) que asegura un mapeo correcto de colores.

Modificación en src/viboy.py

Se añadió una sonda de diagnóstico que lee el registro BGP justo cuando se captura el framebuffer:

# --- Step 0215: SONDA DE PALETA ---
bgp_value = self._mmu.read(0xFF47)
print(f"BGP Register (0xFF47): 0x{bgp_value:02X}")
# ----------------------------------

Esta sonda permite verificar si el problema es un BGP en 0x00 o si hay otro problema en la lógica de decodificación de paleta.

Modificación en src/gpu/renderer.py

Se añadió una corrección en dos lugares del renderer (tanto en el método que usa el framebuffer C++ como en el método Python original):

# --- Step 0215: CORRECCIÓN DE PALETA ---
# Si BGP es 0x00, todos los índices se mapean al color 0 (blanco).
# Esto causa que incluso píxeles negros (índice 3) se rendericen como blancos.
# Forzamos un valor por defecto estándar (0xE4 = 11100100) que mapea:
# Índice 0 -> Color 0 (Blanco)
# Índice 1 -> Color 1 (Gris Claro)
# Índice 2 -> Color 2 (Gris Oscuro)
# Índice 3 -> Color 3 (Negro)
if bgp == 0x00:
    logger.warning(f"[Renderer] BGP es 0x00 (paleta inválida). Forzando 0xE4 (paleta estándar)")
    bgp = 0xE4
# ----------------------------------------

Esta corrección asegura que, incluso si el registro BGP no ha sido inicializado por el juego o la MMU, el renderizador use una paleta válida que permita visualizar correctamente los gráficos.

Decisiones de diseño

¿Por qué forzar 0xE4 en lugar de inicializar BGP en la MMU?

La decisión de forzar el valor en el renderer en lugar de inicializar BGP en la MMU se tomó por las siguientes razones:

  • Separación de responsabilidades: El renderer es responsable de la visualización, y es el lugar más apropiado para manejar casos edge de paleta inválida.
  • Compatibilidad: Algunos juegos pueden escribir 0x00 temporalmente durante la inicialización, y forzar el valor en el renderer permite que el juego inicialice BGP correctamente más tarde sin interferencias.
  • Debugging: Mantener BGP en 0x00 en la MMU permite que la sonda de diagnóstico detecte el problema, mientras que el renderer corrige la visualización.

Archivos Afectados

  • src/viboy.py - Añadida sonda de diagnóstico de BGP en el bloque de captura de framebuffer (líneas ~777-780)
  • src/gpu/renderer.py - Añadida corrección de paleta en dos lugares:
    • Método render_frame() cuando usa framebuffer C++ (líneas ~446-456)
    • Método render_frame() en el método Python original (líneas ~502-512)

Tests y Verificación

La verificación se realizó mediante:

  • Sonda de diagnóstico: Al ejecutar el emulador, la sonda mostrará el valor de BGP en la consola, permitiendo verificar si el problema es un BGP en 0x00.
  • Validación visual: Si el problema era un BGP en 0x00, la corrección debería permitir que los píxeles negros (índice 3) se rendericen correctamente como negro en lugar de blanco.
  • Logs: El renderer emitirá un warning cuando detecte un BGP en 0x00 y fuerce el valor por defecto, permitiendo diagnosticar el problema en tiempo de ejecución.

Comando de prueba:

python main.py roms/tetris.gb

Resultado esperado:

  • Si BGP es 0x00, la consola mostrará: BGP Register (0xFF47): 0x00
  • El renderer emitirá un warning: [Renderer] BGP es 0x00 (paleta inválida). Forzando 0xE4 (paleta estándar)
  • Los píxeles negros (índice 3) deberían renderizarse correctamente como negro en lugar de blanco.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Registro BGP: El registro BGP es un byte que mapea índices de color (0-3) a colores reales de la paleta. Si BGP es 0x00, todos los índices se mapean al color 0 (blanco), causando que incluso píxeles negros se rendericen como blancos.
  • Pipeline de renderizado: El flujo de datos va desde el framebuffer C++ (índices 0-3) → Python (lectura del framebuffer) → Renderer (mapeo de índices a colores RGB usando BGP) → Pygame (dibujo en pantalla). El problema puede estar en cualquier punto de este pipeline.
  • Debugging de sistemas híbridos: Cuando se trabaja con sistemas híbridos Python/C++, es crucial añadir sondas de diagnóstico en múltiples puntos del pipeline para identificar dónde se pierden o corrompen los datos.

Lo que Falta Confirmar

  • Inicialización de BGP: ¿Cuándo y cómo debería inicializarse el registro BGP en la MMU? ¿Algunos juegos dependen de que BGP esté en 0x00 inicialmente?
  • Valor por defecto: ¿Es 0xE4 el valor correcto por defecto, o debería ser otro valor? ¿Varía según el modelo de Game Boy (DMG vs CGB)?
  • Comportamiento del hardware real: ¿Qué hace el hardware real cuando BGP es 0x00? ¿Se renderiza todo en blanco, o hay algún comportamiento especial?

Hipótesis y Suposiciones

Suposición: Asumimos que forzar 0xE4 cuando BGP es 0x00 es un comportamiento aceptable, ya que es un valor común usado por muchos juegos. Sin embargo, esto podría no ser el comportamiento exacto del hardware real, y debería verificarse con documentación técnica o tests en hardware real.

Próximos Pasos

  • [ ] Ejecutar el emulador y verificar que la sonda de BGP muestre el valor correcto
  • [ ] Confirmar que la corrección de paleta permite visualizar correctamente los píxeles negros
  • [ ] Si el problema persiste, investigar si hay otros problemas en la lógica de decodificación de paleta
  • [ ] Considerar inicializar BGP con un valor por defecto en la MMU en lugar de forzarlo en el renderer
  • [ ] Verificar el comportamiento del hardware real cuando BGP es 0x00 para asegurar compatibilidad