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

Verificación y Tests de Interrupciones STAT

Fecha: 2025-12-18 Step ID: 0072 Estado: Verified

Resumen

Se creó una suite completa de tests unitarios para verificar que la implementación de interrupciones STAT y registro LYC funciona correctamente. Los tests validan la comparación LY==LYC, actualización del bit 2 de STAT, solicitud de interrupciones STAT en diferentes condiciones (LYC coincidence, H-Blank, V-Blank, OAM Search), y la detección de rising edge. Todos los tests pasan, confirmando que la implementación es correcta y funcional.

Concepto de Hardware

Las interrupciones STAT son críticas para muchos juegos de Game Boy que necesitan sincronizarse con eventos específicos del renderizado. Los tests validan que el emulador implementa correctamente el comportamiento del hardware real:

  • Comparación LY==LYC: El bit 2 de STAT se actualiza dinámicamente cuando LY coincide con LYC.
  • Interrupciones por modo: Se pueden habilitar interrupciones cuando la PPU entra en H-Blank, V-Blank u OAM Search.
  • Rising edge detection: Las interrupciones solo se disparan cuando la condición pasa de False a True, no mientras permanece True.
  • Verificación inmediata: Cuando se escribe en LYC, se verifica inmediatamente si LY coincide con el nuevo valor.

Fuente: Pan Docs - LCD Status Register (STAT), LYC Register, STAT Interrupt

Implementación

Se creó el archivo tests/test_ppu_stat.py con 7 tests unitarios que cubren todos los aspectos críticos de las interrupciones STAT:

Tests implementados

  • test_lyc_coincidence_flag: Verifica que el bit 2 de STAT se activa cuando LY == LYC y se desactiva cuando LY != LYC.
  • test_stat_interrupt_lyc_coincidence: Verifica que se solicita interrupción STAT cuando LY == LYC y el bit 6 de STAT está activo.
  • test_stat_interrupt_rising_edge: Verifica que la interrupción solo se dispara en rising edge, no múltiples veces en la misma línea.
  • test_stat_interrupt_mode_hblank: Verifica que se solicita interrupción STAT cuando entra en H-Blank y el bit 3 está activo.
  • test_stat_interrupt_mode_vblank: Verifica que se solicita interrupción STAT cuando entra en V-Blank y el bit 4 está activo.
  • test_stat_interrupt_mode_oam_search: Verifica que se solicita interrupción STAT cuando entra en OAM Search y el bit 5 está activo.
  • test_lyc_write_triggers_check: Verifica que escribir en LYC verifica inmediatamente si LY coincide con el nuevo valor.

Decisiones de diseño

  • Cobertura completa: Los tests cubren todos los casos de uso principales de las interrupciones STAT, asegurando que la implementación funciona correctamente en todos los escenarios.
  • Tests deterministas: Todos los tests son deterministas y no dependen de timing externo o estado aleatorio.
  • Validación de hardware: Cada test valida un comportamiento específico del hardware real, no solo que el código no crashea.

Archivos Afectados

  • tests/test_ppu_stat.py (nuevo) - Suite completa de tests para interrupciones STAT y registro LYC:
    • 7 tests unitarios que validan todos los aspectos críticos.
    • Tests deterministas que no dependen de timing externo.
    • Cobertura completa de casos de uso principales.

Tests y Verificación

Ejecución de Tests: pytest tests/test_ppu_stat.py -v

  • Entorno: Windows, Python 3.13.5
  • Resultado: ✅ 7 tests PASSED en 0.26s
  • Qué valida:
    • El bit 2 de STAT se actualiza correctamente cuando LY == LYC.
    • Las interrupciones STAT se solicitan correctamente cuando se cumplen las condiciones configuradas.
    • La detección de rising edge funciona correctamente (no hay múltiples interrupciones en la misma línea).
    • La verificación inmediata al escribir en LYC funciona correctamente.

Código del test (ejemplo: test_lyc_coincidence_flag):

def test_lyc_coincidence_flag(self) -> None:
    """Test: El bit 2 de STAT se activa cuando LY == LYC."""
    mmu = MMU(None)
    ppu = PPU(mmu)
    mmu.set_ppu(ppu)
    
    # Encender LCD (LCDC bit 7 = 1)
    mmu.write_byte(IO_LCDC, 0x80)
    
    # Configurar LYC = 10
    mmu.write_byte(IO_LYC, 10)
    
    # Avanzar PPU hasta LY = 10
    ppu.step(4560)
    assert ppu.get_ly() == 10
    
    # Verificar que el bit 2 de STAT está activo
    stat = mmu.read_byte(IO_STAT)
    assert (stat & 0x04) != 0, "Bit 2 de STAT debe estar activo cuando LY == LYC"

Por qué este test demuestra algo del hardware: Este test verifica que el bit 2 de STAT (LYC=LY Coincidence Flag) se actualiza dinámicamente por el hardware cuando LY coincide con LYC. En hardware real, este bit es de solo lectura y se actualiza automáticamente por la PPU. El test confirma que nuestro emulador implementa este comportamiento correctamente.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Tests unitarios son esenciales: Los tests unitarios permiten verificar que la implementación funciona correctamente sin necesidad de ejecutar juegos completos. Esto es especialmente útil para validar comportamientos específicos del hardware que pueden ser difíciles de observar en tiempo de ejecución.
  • Rising edge detection es crítico: La detección de rising edge evita que se disparen múltiples interrupciones en la misma línea, lo cual es el comportamiento real del hardware. Los tests confirman que esta implementación funciona correctamente.
  • Verificación inmediata al cambiar LYC: Cuando el juego escribe en LYC, el hardware verifica inmediatamente si LY coincide con el nuevo valor. Esto es crítico para juegos que cambian LYC dinámicamente durante el renderizado.

Lo que Falta Confirmar

  • Comportamiento en juegos reales: Aunque los tests pasan, es importante verificar que las interrupciones STAT funcionan correctamente en juegos reales que las usan (como Pokémon Red). Si un juego sigue congelado, puede haber un problema de timing o de sincronización que los tests unitarios no detectan.
  • Timing exacto de interrupciones: Los tests verifican que las interrupciones se solicitan cuando se cumplen las condiciones, pero no verifican el timing exacto (cuántos ciclos después del evento se solicita la interrupción). En la mayoría de los casos, esto no es crítico, pero puede ser importante para algunos juegos.

Hipótesis y Suposiciones

Se asume que si todos los tests unitarios pasan, la implementación es correcta. Sin embargo, si un juego real sigue congelado, puede haber un problema de integración o de timing que los tests no detectan. En ese caso, sería necesario investigar más a fondo con logs o debugging en tiempo de ejecución.

Próximos Pasos

  • [ ] Verificar que las interrupciones STAT funcionan correctamente en juegos reales (p.ej. Pokémon Red)
  • [ ] Si un juego sigue congelado, investigar con logs o debugging en tiempo de ejecución
  • [ ] Documentar timing exacto de interrupciones STAT si se encuentra información más detallada
  • [ ] Continuar con otras funcionalidades pendientes del emulador (APU, mejoras de renderizado, etc.)