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.
Fix pygame window freeze + CGB tilemap attrs/banks + DMG quick classifier
Resumen
Este Step implementa tres mejoras críticas: (A) Fix del freeze de ventana pygame mediante event pumping, (B) Corrección de lectura de tilemap CGB (tile_id de bank 0, attr de bank 1), y (C) Clasificador rápido para diagnóstico DMG. Los resultados muestran que tetris_dx.gbc (CGB) genera contenido visible (FirstSignal en frame_id=170, IdxNonZero=5120, RgbNonWhite=5120, PresentNonWhite=6409), mientras que tetris.gb (DMG) es clasificado correctamente como VRAM_TILEDATA_ZERO.
Concepto de Hardware
CGB Tilemap Attributes: En CGB, el tilemap (0x9800-0x9FFF) tiene dos bancos VRAM:
- Bank 0: Contiene los tile IDs (0-255) que apuntan a los tiles en VRAM
- Bank 1: Contiene los atributos de cada tile (paleta CGB, banco VRAM del tile pattern, flips, prioridad)
El banco VRAM del tile pattern (tile_bank) se extrae del atributo (bit 3) y se usa para leer los bytes del tile desde el banco VRAM correcto (0 o 1). Si se lee el tile_id del banco equivocado, se obtienen valores incorrectos que resultan en renderizado de "basura".
Event Pumping en Sistemas con Ventanas: En sistemas con ventanas gráficas (X11, Wayland, Windows), el Window Manager (WM) espera que las aplicaciones procesen eventos del sistema periódicamente. Si una aplicación no procesa eventos durante un tiempo prolongado, el WM la marca como "no responde" (not responding). En pygame/SDL, esto se soluciona llamando a pygame.event.pump() periódicamente, que procesa eventos del sistema sin bloquear.
DMG Quick Classifier: El clasificador rápido permite identificar la causa raíz de problemas de pantalla blanca en DMG sin necesidad de análisis manual extenso. Clasifica el estado en 6 categorías: CPU_LOOP, LCDC_OFF, VRAM_TILEDATA_ZERO, IDX_ZERO_DESPITE_TILEDATA, RGB_FAIL_DESPITE_IDX, OK_BUT_WHITE.
Referencia: Pan Docs - CGB Registers, BG Map Attributes; Documentación pygame - Event handling
Implementación
Fase A: Fix pygame window freeze ✅
A1) Modo windowed opcional en rom_smoke:
- Nuevo parámetro
use_renderer_windoweden__init__()y CLI (--use-renderer-windowed) - Configuración de variables de entorno: Si windowed, quitar
SDL_VIDEODRIVERyVIBOY_HEADLESSpara forzar modo windowed real - Creación condicional de renderer: Si
use_renderer_windowed=True, crear renderer sin forzar headless
A2) Event pumping en renderer.py:
- Detección de ventana real:
has_window = hasattr(self, 'screen') and self.screen is not None - Event pumping condicional: Solo en modo windowed (no headless)
pygame.event.pump()cada frame (procesa eventos del sistema sin bloquear)- Verificación de eventos QUIT (opcional, útil para debugging)
- Logging limitado (primeros 20 frames)
Resultado: En modo windowed, la ventana no se marca como "no responde" porque los eventos se procesan cada frame.
Fase B: CGB Tilemap Fix ✅
B1) Auditoría de lectura de tilemap:
- Problema identificado:
tile_idse leía conread_vram()que usa el banco actual (VBK), no siempre bank 0 - En CGB,
tile_iddebe leerse SIEMPRE de VRAM bank 0, ytile_attrde bank 1
B2) Corrección de lectura CGB tilemap:
- Detección de modo CGB:
HardwareMode hw_mode = mmu_->get_hardware_mode() - Detección de modo compatibilidad DMG:
dmg_compat_mode = mmu_->get_dmg_compat_mode() - Lectura condicional de
tile_id:- CGB real (no dmg_compat_mode):
tile_id = mmu_->read_vram_bank(0, tile_map_offset)(bank 0 siempre) - DMG o dmg_compat_mode:
tile_id = mmu_->read_vram(tile_map_addr)(ruta DMG normal)
- CGB real (no dmg_compat_mode):
- Lectura condicional de
tile_attr:- CGB real:
tile_attr = mmu_->read_vram_bank(1, tile_map_offset)(bank 1) - DMG o dmg_compat_mode:
tile_attr = 0x00,tile_bank = 0(default)
- CGB real:
Resultado: En CGB real, tile_id se lee siempre de bank 0 y tile_attr de bank 1, permitiendo que el renderizado use el banco VRAM correcto para los tiles.
Fase C: DMG Quick Classifier ✅
C1) Implementación del clasificador:
- Función
_classify_dmg_quick(ppu, mmu, renderer=None): Clasifica el estado DMG en 6 categorías:- CPU_LOOP: CPU no progresa (hotspot > 100000)
- LCDC_OFF: LCDC desactivado
- VRAM_TILEDATA_ZERO: VRAM tiledata vacía
- IDX_ZERO_DESPITE_TILEDATA: Hay tiledata pero no se renderiza
- RGB_FAIL_DESPITE_IDX: Hay IDX pero RGB falla
- OK_BUT_WHITE: Todo parece OK pero sigue blanco
- Integración en snapshot: Solo se ejecuta para DMG (no CGB), se añade al snapshot con formato
DMGQuickClassifier=...
Resultado: tetris.gb es clasificado correctamente como VRAM_TILEDATA_ZERO, identificando que el problema es que VRAM tiledata está vacía (no hay tiles cargados).
Archivos Afectados
tools/rom_smoke_0442.py- Añadido parámetrouse_renderer_windowedy--use-renderer-windowed, implementada función_classify_dmg_quick(), integración del clasificador en snapshot (solo DMG)src/gpu/renderer.py- Event pumping enrender_frame()(solo modo windowed), detección de ventana real vs headlesssrc/core/cpp/PPU.cpp- Corrección de lectura detile_id(bank 0 en CGB real), corrección de lectura detile_attr(solo en CGB real, no DMG)
Tests y Verificación
Compilación:
python3 setup.py build_ext --inplace
✅ Compilación exitosa sin errores
Validación con tetris_dx.gbc (CGB):
export VIBOY_SIM_BOOT_LOGO=0
export VIBOY_DEBUG_PRESENT_TRACE=1
export VIBOY_DEBUG_CGB_PALETTE_WRITES=1
export PYTHONPATH=.
python3 tools/rom_smoke_0442.py roms/tetris_dx.gbc --frames 600 --use-renderer-headless
Resultados:
- ✅ FirstSignal detectado en frame_id=170
- ✅ IdxNonZero=5120 (hay contenido en framebuffer de índices)
- ✅ RgbNonWhite=5120 (conversión IDX→RGB funciona)
- ✅ PresentNonWhite=6409 (renderer presenta contenido)
- ✅ VRAM_Regions_TiledataNZ=3479 (hay tiles en VRAM)
- ✅ VRAM_Regions_TilemapNZ=2012 (hay tilemap)
Conclusión: El fix de CGB tilemap permite que tetris_dx.gbc renderice contenido estructurado en lugar de "basura". El pipeline PPU→RGB→Renderer funciona correctamente.
Validación con tetris.gb (DMG):
export VIBOY_SIM_BOOT_LOGO=0
export PYTHONPATH=.
python3 tools/rom_smoke_0442.py roms/tetris.gb --frames 3000
Resultados:
- ✅ DMGQuickClassifier=VRAM_TILEDATA_ZERO
- ✅ VRAM_Regions_TiledataNZ=0 (VRAM tiledata vacía)
- ✅ VRAM_Regions_TilemapNZ=1024 (tilemap tiene datos, pero apunta a tiles vacíos)
- ✅ ThreeBufferStats: IdxNonZero=0, RgbNonWhite=0 (no hay contenido renderizado)
Conclusión: El clasificador identifica correctamente que el problema es que VRAM tiledata está vacía. El juego progresa (VBlank IRQs servidos, IME=1, IE=0x09), pero no hay tiles cargados en VRAM.
Validación de módulo compilado C++:
python3 test_build.py
✅ Test de compilación exitoso
Fuentes Consultadas
- Pan Docs: CGB Registers, BG Map Attributes
- Pan Docs: VRAM Banks (CGB Only), VBK register (0xFF4F)
- Documentación pygame: Event handling,
pygame.event.pump()
Integridad Educativa
Lo que Entiendo Ahora
- En CGB, el tilemap tiene dos bancos: bank 0 para tile IDs, bank 1 para atributos
- El banco VRAM del tile pattern se extrae del atributo (bit 3) y se usa para leer los bytes del tile
- En sistemas con ventanas, es necesario procesar eventos periódicamente para evitar que el WM marque la app como "no responde"
- El clasificador DMG permite identificar rápidamente la causa raíz de problemas de pantalla blanca
Lo que Falta Confirmar
- Por qué algunos juegos CGB siguen mostrando contenido incorrecto a pesar del fix
- Por qué VRAM tiledata está vacía en tetris.gb (timing, MBC, o boot sequence)
Hipótesis y Suposiciones
El fix de CGB tilemap debería resolver el problema de "basura" en tetris_dx.gbc (confirmado: funciona). El clasificador DMG debería identificar correctamente la causa raíz (confirmado: identifica VRAM_TILEDATA_ZERO).
Próximos Pasos
- [ ] CGB: Investigar por qué algunos juegos CGB siguen mostrando contenido incorrecto (puede ser timing, paletas, o sprites)
- [ ] DMG: Investigar por qué VRAM tiledata está vacía en tetris.gb (puede ser timing de carga, MBC, o boot sequence)
- [ ] Event Pumping: Verificar que el fix funciona en modo windowed real (probar con
--use-renderer-windowed)