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.
Step 0456: Fijar Tests Clean-Room y Añadir Test No Plano
Resumen
STOP: Antes de tocar el core, validamos formalmente que los tests de paleta estaban bien diseñados. El Step 0454 creó tests de paleta que fallaban con "framebuffer plano", pero antes de asumir que el problema estaba en el core, verificamos que el patrón de tile usado en los tests generaba realmente los índices esperados.
Hallazgo crítico: El patrón 0x00/0xFF usado en los tests originales genera un índice constante (=2), no los 4 índices (0/1/2/3) necesarios para validar paletas. Esto explicaba los falsos positivos.
Acción: Corregimos los tests para usar el patrón 0x55/0x33 que garantiza índices 0/1/2/3, añadimos un test anti-regresión "no plano", y creamos un test sanity 2bpp para validar formalmente la decodificación.
Resultado: Los tests corregidos ahora fallan porque el core tiene un bug real (framebuffer RGB plano), confirmando que el problema NO era el diseño de los tests, sino la conversión índice→RGB en el core.
Concepto de Hardware
Decodificación 2bpp (2 bits per pixel)
Fuente: Pan Docs - "Tile Data" / GBEDG
Los tiles del Game Boy se almacenan en formato 2bpp: cada píxel requiere 2 bits, y cada fila de 8 píxeles requiere 2 bytes (16 bits total). El byte bajo contiene el bit menos significativo de cada píxel, y el byte alto contiene el bit más significativo.
Fórmula de Decodificación:
Para cada píxel i (0-7, MSB primero):
bit_low = (byte_low >> (7-i)) & 0x01
bit_high = (byte_high >> (7-i)) & 0x01
color_idx = bit_low | (bit_high << 1)
// Resultado: color_idx ∈ {0, 1, 2, 3}
Ejemplos de Patrones:
- 0x00/0x00: Todos los bits = 0 → color_idx = 0 para todos los píxeles
- 0xFF/0xFF: Todos los bits = 1 → color_idx = 3 para todos los píxeles
- 0x00/0xFF: byte_low=0, byte_high=0xFF → color_idx = 2 para todos los píxeles (constante)
- 0x55/0x33: Patrón variado → color_idx = [0, 1, 2, 3, 0, 1, 2, 3] (contiene los 4 índices)
Implicación para tests: Para validar que una paleta mapea correctamente los 4 índices, el tile usado debe generar los 4 índices (0/1/2/3). Si el tile genera solo 1 índice constante, el test no puede distinguir si la paleta funciona o no.
Implementación
Fase A: Test Sanity 2bpp
Creamos test_tile_2bpp_decode_sanity_0456.py para validar formalmente qué patrones generan
qué índices. Este test es puro (no usa el core) y demuestra matemáticamente el comportamiento del decode 2bpp.
Resultados del Test Sanity:
Patrón 0x00/0xFF genera índices: [2, 2, 2, 2, 2, 2, 2, 2]
Índices únicos: {2}
⚠️ ADVERTENCIA: Patrón 0x00/0xFF genera índice constante = 2
Este patrón NO es adecuado para tests de paleta que requieren 0/1/2/3
Patrón 0x55/0x33 genera índices: [0, 1, 2, 3, 0, 1, 2, 3]
Índices únicos: {0, 1, 2, 3}
✅ Patrón 0x55/0x33 genera los 4 índices (adecuado para tests de paleta)
Fase B: Corrección de Tests de Paleta
Modificamos test_palette_dmg_bgp_0454.py y test_palette_dmg_obj_0454.py para usar
el patrón 0x55/0x33 en lugar de 0x00/0xFF. También ajustamos los asserts para ser más robustos
(no valores RGB exactos, solo distinción y cambio).
Cambios Clave:
# ANTES (patrón incorrecto):
tile_data = [
0x00, 0xFF, # Fila 0: índices 0,1,2,3 (INCORRECTO: genera constante 2)
...
]
# DESPUÉS (patrón correcto):
tile_data = [
0x55, 0x33, # Fila 0: índices 0,1,2,3,0,1,2,3 (CORRECTO: genera 0/1/2/3)
...
]
Fase C: Test Anti-Regresión "No Plano"
Creamos test_framebuffer_not_flat_0456.py que valida que el framebuffer RGB tiene al menos
3 colores únicos. Este test evita que en el futuro volvamos a pasar "por casualidad" con todo un color.
Decisiones de Diseño
- NO tocar el core: El plan especifica explícitamente que NO debemos modificar el core, solo los tests. El objetivo es validar que los tests están bien diseñados antes de asumir bugs en el core.
- Evidencia reproducible: El test sanity 2bpp es puro (no depende del core) y demuestra matemáticamente qué patrones generan qué índices.
- Asserts robustos: Los tests de paleta no verifican valores RGB exactos, solo que hay múltiples colores distintos y que cambiar la paleta reordena los colores.
Archivos Afectados
tests/test_tile_2bpp_decode_sanity_0456.py(creado) - Test sanity de decodificación 2bpptests/test_palette_dmg_bgp_0454.py(modificado) - Corregido patrón de tile a 0x55/0x33tests/test_palette_dmg_obj_0454.py(modificado) - Corregido patrón de tile a 0x55/0x33tests/test_framebuffer_not_flat_0456.py(creado) - Test anti-regresión "no plano"
Tests y Verificación
Test Sanity 2bpp
Comando: pytest -v tests/test_tile_2bpp_decode_sanity_0456.py -s
Resultado: ✅ 3/3 tests pasan
Evidencia:
def decode_2bpp_line(byte_low: int, byte_high: int) -> list[int]:
"""Decodifica una línea de tile 2bpp a índices de color (0..3)."""
color_indices = []
for i in range(8):
bit_pos = 7 - i # Bit position (MSB first)
bit_low = (byte_low >> bit_pos) & 0x01
bit_high = (byte_high >> bit_pos) & 0x01
color_idx = bit_low | (bit_high << 1)
color_indices.append(color_idx)
return color_indices
Tests de Paleta Corregidos
Comando: pytest -v tests/test_palette_dmg_bgp_0454.py tests/test_palette_dmg_obj_0454.py
Resultado: ❌ 0/2 tests pasan (esperado: el core tiene un bug real)
Evidencia del fallo:
FAILED tests/test_palette_dmg_bgp_0454.py::test_dmg_bgp_palette_mapping
AssertionError: Frame plano: solo 2 colores únicos (esperado ≥3 con patrón 0x55/0x33)
assert 2 >= 3
+ where 2 = len({(85, 85, 85), (255, 255, 255)})
FAILED tests/test_palette_dmg_obj_0454.py::test_dmg_obj_palette_mapping
AssertionError: Sprite plano: solo 1 colores únicos (esperado ≥2 con patrón 0x55/0x33)
assert 1 >= 2
+ where 1 = len({(0, 0, 0)})
Interpretación: Los tests ahora fallan porque el core tiene un bug real (framebuffer RGB plano). El test sanity confirma que el patrón 0x55/0x33 genera índices 0/1/2/3 correctamente, así que el problema NO es el diseño de los tests, sino la conversión índice→RGB en el core.
Test Anti-Regresión "No Plano"
Comando: pytest -v tests/test_framebuffer_not_flat_0456.py
Resultado: ❌ 0/1 tests pasa (esperado: el core tiene un bug real)
Validación de módulo compilado C++: Los tests llaman a ppu.get_framebuffer_rgb()
que retorna el framebuffer RGB convertido desde el framebuffer de índices. El test valida que hay al menos
3 colores únicos en el framebuffer RGB.
Fuentes Consultadas
- Pan Docs: "Tile Data" - Formato 2bpp de tiles
- GBEDG: "Tile Format" - Decodificación de tiles
Integridad Educativa
Lo que Entiendo Ahora
- Decodificación 2bpp: El formato 2bpp requiere 2 bytes por fila de 8 píxeles.
El byte bajo contiene el bit menos significativo y el byte alto el bit más significativo.
La fórmula
color_idx = bit_low | (bit_high << 1)genera índices 0-3. - Patrones de Tile: No todos los patrones generan los 4 índices. El patrón 0x00/0xFF genera índice constante (=2), mientras que 0x55/0x33 genera los 4 índices (0/1/2/3).
- Diseño de Tests: Para validar paletas, el tile usado debe generar los 4 índices. Si el tile genera solo 1 índice constante, el test no puede distinguir si la paleta funciona.
- Guardrails: Antes de tocar el core, validamos formalmente que los tests están bien diseñados. El test sanity 2bpp es puro (no usa el core) y demuestra matemáticamente el comportamiento.
Lo que Falta Confirmar
- Bug en el core: Los tests corregidos fallan porque el framebuffer RGB está plano. El siguiente paso será investigar la conversión índice→RGB en el core (PPU o renderer).
- Conversión RGB: Necesitamos verificar que la función
convert_framebuffer_to_rgb()aplica correctamente las paletas BGP/OBP0/OBP1 a los índices de color.
Hipótesis y Suposiciones
Hipótesis confirmada: El patrón 0x00/0xFF usado en los tests originales generaba índice constante, causando falsos positivos. Los tests corregidos ahora detectan un bug real en el core.
Próximos Pasos
- [ ] Investigar la conversión índice→RGB en el core (PPU o renderer)
- [ ] Verificar que
convert_framebuffer_to_rgb()aplica correctamente las paletas BGP/OBP0/OBP1 - [ ] Corregir el bug del framebuffer RGB plano en el core
- [ ] Re-ejecutar los tests de paleta corregidos para validar la corrección