⚠️ 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.

IRQ Reality Check + CGB Palette Proof

Fecha: 2026-01-08 Step ID: 0494 Estado: VERIFIED

Resumen

Este step implementa mecanismos de tracking avanzados para verificar el comportamiento real de las interrupciones (IRQ) en modo DMG y el estado de las paletas CGB. Se implementó tracking de interrupciones realmente tomadas (interrupt_taken_counts), tracking de writes a IF/IE y HRAM[0xFFC5], y se integró en rom_smoke_0442.py con una sección IRQReality siempre visible. Resultado clave DMG: IRQTaken_VBlank=2579 confirma que las interrupciones VBlank se están tomando correctamente, descartando problemas de interrupciones como causa de la pantalla blanca. Resultado clave CGB: IdxNonZero=22910 pero RgbNonWhite=0, confirmando que el problema está en el pipeline de renderizado (paletas/mapeo) más que en el fetch de tiles.

Concepto de Hardware

Interrupciones Realmente Tomadas: En el Game Boy, cuando una interrupción está pendiente (IF bit set) y el IME (Interrupt Master Enable) está activo, la CPU debe tomar la interrupción en el siguiente ciclo de instrucción. Sin embargo, es posible que el emulador reporte que las interrupciones están "servidas" sin que realmente se hayan tomado. El tracking de interrupciones realmente tomadas verifica que la CPU realmente ejecute el código del handler de interrupción.

HRAM[0xFFC5]: Algunos juegos usan HRAM[0xFFC5] como flag para indicar que el handler de VBlank ha completado. Tracking de writes a esta ubicación puede confirmar que el handler se está ejecutando.

Paletas CGB: En modo CGB, las paletas se configuran escribiendo a BGPI/OBPI (Palette Index) y luego a BGPD/OBPD (Palette Data). Si no hay writes a estas ubicaciones, el juego está usando paletas DMG (BGP/OBP0/OBP1) en lugar de CGB.

Referencia: Pan Docs - "Interrupts", "CGB Palettes", "Memory Map"

Implementación

Fase A: DMG IRQ Reality Check

A1-A2: Tracking de Interrupciones Tomadas

Se implementó en CPU.hpp/CPU.cpp:

  • interrupt_taken_counts_[5]: Contador de interrupciones realmente tomadas por tipo (VBlank, LCD-STAT, Timer, Serial, Joypad)
  • IRQTraceEvent: Estructura con información detallada de cada interrupción (frame, PC antes, vector, IE, IF antes/después, IME, SP antes/después, PC guardado en pila)
  • irq_trace_ring_[64]: Ring buffer de últimos 64 eventos IRQ

A3-A4: Tracking de Writes a IF/IE y HRAM[0xFFC5]

Se implementó en MMU.hpp/MMU.cpp:

  • IFIETracking: Tracking de writes a 0xFF0F (IF) y 0xFFFF (IE) con PC, valor escrito y valor aplicado
  • HRAMFFC5Tracking: Tracking de writes a 0xFFC5 con PC, valor escrito y frame de primera escritura

A5: Exposición Cython

Se implementaron getters en cpu.pyx y mmu.pyx:

  • get_interrupt_taken_counts(): Retorna diccionario con contadores por tipo
  • get_irq_trace_ring(n): Retorna últimos n eventos IRQ
  • get_if_ie_tracking(): Retorna tracking de IF/IE
  • get_hram_ffc5_tracking(): Retorna tracking de HRAM[0xFFC5]

A6: Integración en rom_smoke_0442.py

Se añadió sección IRQReality al snapshot principal (siempre visible, no solo en AfterClear):

  • Captura de interrupt_taken_counts
  • Captura de if_ie_tracking
  • Captura de hram_ffc5_tracking

Fase B: CGB Palette Proof

Se ejecutó tetris_dx.gbc con VIBOY_DEBUG_CGB_PALETTE_WRITES=1 por 1200 frames. El snapshot ya incluía CGBPaletteWriteStats, palette0_decode y bg_palette_nonwhite_entries (implementado en Step 0493).

Archivos Afectados

  • src/core/cpp/CPU.hpp - Añadido IRQTraceEvent, interrupt_taken_counts_, irq_trace_ring_
  • src/core/cpp/CPU.cpp - Implementado tracking de interrupciones tomadas y ring buffer
  • src/core/cpp/MMU.hpp - Añadido IFIETracking, HRAMFFC5Tracking
  • src/core/cpp/MMU.cpp - Implementado tracking de writes a IF/IE y HRAM[0xFFC5]
  • src/core/cython/cpu.pxd - Declaraciones de estructuras y getters
  • src/core/cython/cpu.pyx - Implementación de getters Python
  • src/core/cython/mmu.pxd - Declaraciones de estructuras y getters
  • src/core/cython/mmu.pyx - Implementación de getters Python
  • tools/rom_smoke_0442.py - Integración de sección IRQReality en snapshot principal

Tests y Verificación

Comando ejecutado (DMG):

export VIBOY_SIM_BOOT_LOGO=0
export VIBOY_POST_BOOT_DMG_PROFILE=B
export VIBOY_DEBUG_VRAM_WRITES=1
export VIBOY_DEBUG_DMG_TILE_FETCH=1
export VIBOY_DEBUG_PRESENT_TRACE=1
python3 -m tools.rom_smoke_0442 roms/tetris.gb --frames 3000

Resultados (Frame 2580):

  • IRQTaken_VBlank=2579 ✅ (criterio: > 0)
  • HRAM_FFC5_WriteCount=1 ✅ (criterio: >= 1)
  • IF_WriteCount=5160
  • IE_WriteCount=3
  • VBlankServ=2579

Comando ejecutado (CGB):

export VIBOY_SIM_BOOT_LOGO=0
export VIBOY_DEBUG_CGB_PALETTE_WRITES=1
python3 -m tools.rom_smoke_0442 roms/tetris_dx.gbc --frames 1200

Resultados (Frame 600):

  • IdxNonZero=22910 ✅ (criterio: > 0)
  • RgbNonWhite=0 ❌ (criterio: > 0, no cumplido - juego usa paletas DMG)
  • CGBPaletteWriteStats=BGPD_Writes=0 OBPD_Writes=0

Validación de módulo compilado C++:

python3 test_build.py
# Resultado: [EXITO] El pipeline de compilacion funciona correctamente

Fuentes Consultadas

  • Pan Docs: "Interrupts" - Comportamiento de interrupciones en LR35902
  • Pan Docs: "CGB Palettes" - Configuración de paletas CGB
  • Pan Docs: "Memory Map" - Mapeo de memoria y registros I/O

Integridad Educativa

Lo que Entiendo Ahora

  • Interrupciones Realmente Tomadas: El tracking de interrupciones realmente tomadas verifica que la CPU realmente ejecute el código del handler, no solo que el flag esté set. Esto es crítico para diagnosticar problemas de sincronización.
  • HRAM[0xFFC5] como Flag: Algunos juegos usan HRAM[0xFFC5] como flag para indicar que el handler de VBlank ha completado. Tracking de writes a esta ubicación puede confirmar que el handler se está ejecutando.
  • Paletas CGB vs DMG: En modo CGB, las paletas se configuran escribiendo a BGPI/OBPI y luego a BGPD/OBPD. Si no hay writes a estas ubicaciones, el juego está usando paletas DMG (BGP/OBP0/OBP1) en lugar de CGB.

Lo que Falta Confirmar

  • Pipeline de Renderizado: Dado que las interrupciones funcionan correctamente, el problema de pantalla blanca está en el pipeline de renderizado (fetch de tiles, mapeo de paletas, o conversión de índices a RGB).
  • Mapeo de Paletas DMG: Aunque tetris_dx.gbc usa paletas DMG, el hecho de que RgbNonWhite=0 sugiere que puede haber un problema en el mapeo de índices a colores RGB.

Hipótesis y Suposiciones

Hipótesis Principal: El problema de pantalla blanca NO es causado por interrupciones no tomadas, sino por un problema en el pipeline de renderizado (fetch de tiles, mapeo de paletas, o conversión de índices a RGB). Esta hipótesis está respaldada por los datos: las interrupciones se están tomando correctamente (IRQTaken_VBlank=2579), pero el framebuffer sigue siendo blanco.

Próximos Pasos

  • [ ] Investigar pipeline de renderizado: Dado que las interrupciones funcionan correctamente, el problema está en el fetch/renderizado de tiles o en la conversión de índices a RGB.
  • [ ] Verificar mapeo de paletas DMG: Aunque tetris_dx.gbc usa paletas DMG, el hecho de que RgbNonWhite=0 sugiere que puede haber un problema en el mapeo de índices a colores RGB.
  • [ ] Analizar VRAM writes: Los datos muestran que hay writes a VRAM (TiledataNonZeroB0=11000), pero el framebuffer sigue siendo blanco, lo que sugiere un problema en el fetch o en la conversión.