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

Operación "Interrupt Awakening" - Depuración de Activación de Interrupciones

Fecha: 2025-12-25 Step ID: 0280 Estado: Draft

Resumen

Este Step implementa la Operación "Interrupt Awakening" para investigar por qué Pokémon Red está atrapado en un bucle infinito esperando que el flag 0xD732 cambie. El análisis del Step 0279 confirmó que el problema NO es un Reset Loop, sino un "coma inducido": el juego está atascado en el bucle de polling (PC: 0x614D-0x6153) esperando que una ISR de V-Blank modifique el flag, pero las interrupciones están deshabilitadas (IE=0x00). Aunque se detectó un EI en PC:0x60A6, las interrupciones no parecen estar activas durante el polling. Este Step añade instrumentación ultra-precisa para rastrear el estado de IE e IME cuando se ejecuta EI, y monitorea el bucle de polling para detectar si alguien está escribiendo en IE durante la espera.

Concepto de Hardware

El Retraso de un Ciclo de la Instrucción EI

La instrucción EI (Enable Interrupts, opcode 0xFB) tiene un comportamiento especial en el hardware real del Game Boy: el Interrupt Master Enable (IME) se activa DESPUÉS de ejecutar la siguiente instrucción, no inmediatamente. Este retraso de un ciclo es crítico porque permite que la instrucción siguiente a EI se ejecute sin interrupciones, lo cual es necesario para configuraciones atómicas o para evitar condiciones de carrera.

Flujo de activación de interrupciones:

  1. Ejecución de EI: El opcode 0xFB se ejecuta, pero IME no se activa inmediatamente. En su lugar, se marca una bandera interna (ime_scheduled_) que indica que IME debe activarse después de la siguiente instrucción.
  2. Ejecución de la siguiente instrucción: La instrucción que sigue a EI se ejecuta con IME=false, garantizando que no se interrumpa.
  3. Activación de IME: Al inicio del siguiente ciclo de instrucción, antes del fetch, se verifica si ime_scheduled_ es true. Si lo es, se activa IME y se limpia la bandera.
  4. Procesamiento de interrupciones: Una vez que IME está activo, el sistema puede procesar interrupciones pendientes si IE & IF != 0.

Fuente: Pan Docs - "EI Instruction": "Interrupts are enabled after the instruction following EI."

Registros de Interrupciones

El Game Boy tiene dos registros críticos para el manejo de interrupciones:

  • IE (0xFFFF) - Interrupt Enable: Registro de habilitación de fuentes de interrupciones. Cada bit habilita una fuente específica:
    • Bit 0: V-Blank
    • Bit 1: LCD STAT
    • Bit 2: Timer
    • Bit 3: Serial
    • Bit 4: Joypad
  • IF (0xFF0F) - Interrupt Flag: Registro de flags de interrupciones pendientes. Cada bit indica si una interrupción está pendiente.
  • IME (Interrupt Master Enable): Flag interno de la CPU que controla si las interrupciones pueden ser procesadas. Solo se puede activar mediante EI (con retraso) o desactivar mediante DI (inmediato).

Condición para procesar una interrupción: IME == true && (IE & IF) != 0

Fuente: Pan Docs - "Interrupts": "An interrupt is processed if IME is set and the corresponding bit in both IE and IF is set."

Implementación

Este Step añade dos instrumentaciones críticas para depurar el problema de activación de interrupciones:

1. Rastreo Ultra-Preciso de EI e IME

Se modificó el caso 0xFB (EI) en CPU.cpp para capturar el estado exacto de IE e IME cuando se intenta habilitar las interrupciones. Esto nos permite identificar si el problema es que IE está en 0x00 cuando se ejecuta EI, o si IME no se activa correctamente.

case 0xFB:  // EI (Enable Interrupts)
{
    // --- Step 0280: Rastreo Ultra-Preciso de EI e IME ---
    uint8_t ie_val = mmu_->read(0xFFFF);
    printf("[CPU-EI] Instrucción EI en PC:0x%04X | IE Actual:0x%02X | IME previo:%d | IME programado:%d\n",
           original_pc, ie_val, ime_ ? 1 : 0, ime_scheduled_ ? 1 : 0);
    ime_scheduled_ = true;
    cycles_ += 1;
    return 1;
}

Información capturada:

  • PC original: Dirección donde se ejecutó EI
  • IE Actual: Valor del registro IE en el momento de ejecutar EI
  • IME previo: Estado de IME antes de ejecutar EI
  • IME programado: Si ya había un EI pendiente (debería ser 0 normalmente)

2. Sniper de Polling con Estado de IE

Se añadió un monitor del bucle de polling (PC: 0x614D-0x6153) que captura el estado de IE, IF, IME y el flag 0xD732 durante la espera. Esto nos permite ver si alguien está escribiendo en IE durante la espera, o si IE cambia mágicamente a cero.

// --- Step 0280: Sniper de Polling con Estado de IE ---
if (original_pc >= 0x614D && original_pc <= 0x6153) {
    static int polling_watch_count = 0;
    if (polling_watch_count < 20) {
        printf("[POLLING-WATCH] PC:%04X | IE:0x%02X | IF:0x%02X | IME:%d | D732:0x%02X\n",
               original_pc, mmu_->read(0xFFFF), mmu_->read(0xFF0F), 
               ime_ ? 1 : 0, mmu_->read(0xD732));
        polling_watch_count++;
    }
}

Información capturada:

  • PC: Dirección actual dentro del bucle de polling
  • IE: Valor del registro IE durante la espera
  • IF: Valor del registro IF (interrupciones pendientes)
  • IME: Estado del Interrupt Master Enable
  • D732: Valor del flag que el juego está esperando que cambie

3. Verificación de la Lógica de handle_interrupts

Se verificó que la función handle_interrupts() no modifica el registro IE (solo lo lee). La función solo lee IE para calcular las interrupciones pendientes (pending = IE & IF), pero nunca escribe en IE. Esto confirma que el problema no está en el procesamiento de interrupciones, sino en la activación de IE o IME.

Componentes modificados

  • src/core/cpp/CPU.cpp:
    • Modificado case 0xFB (EI) para agregar rastreo detallado de IE e IME
    • Agregado sniper de polling al final de step() para monitorear el bucle de espera

Decisiones de diseño

  • Límite de 20 logs en polling: Se limita el número de logs del sniper de polling a 20 para evitar saturar el log, pero es suficiente para ver el patrón del bucle.
  • Captura de IE en tiempo real: Se lee IE directamente de la MMU en cada log para capturar cambios en tiempo real, no se usa un valor cacheado.
  • Uso de original_pc: Se usa original_pc (PC antes del fetch) para asegurar que capturamos la dirección correcta, incluso si el PC avanza durante la ejecución de la instrucción.

Archivos Afectados

  • src/core/cpp/CPU.cpp - Modificado caso 0xFB (EI) y agregado sniper de polling

Tests y Verificación

La verificación se realizará mediante análisis de logs durante la ejecución de Pokémon Red:

  • Logs [CPU-EI]: Deben aparecer cuando el juego ejecuta EI. Si IE Actual es 0x00 en el momento del EI, confirmaremos que el problema es que el juego está habilitando el maestro (IME) pero no ha habilitado ninguna fuente individual (IE).
  • Logs [POLLING-WATCH]: Deben aparecer cuando el juego entra al bucle de polling. Si IE cambia mágicamente a cero durante la espera, sabremos que algo está escribiendo en IE incorrectamente.
  • Validación de módulo compilado C++: El código se compila sin errores y el módulo se genera correctamente.

Comando de prueba:

python setup.py build_ext --inplace
python main.py roms/pkmn.gb > debug_step_0280.log 2>&1

Análisis esperado:

  • Buscar [CPU-EI] en el log para ver el estado de IE cuando se ejecuta EI
  • Buscar [POLLING-WATCH] para ver el estado de IE durante el bucle de espera
  • Verificar si IE cambia entre el momento del EI y el bucle de polling

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Retraso de un ciclo de EI: La instrucción EI no activa IME inmediatamente, sino después de ejecutar la siguiente instrucción. Esto es un comportamiento del hardware real, no una limitación de nuestra implementación.
  • Diferencia entre IE e IME: IE (0xFFFF) habilita fuentes específicas de interrupciones, mientras que IME es un flag interno que controla si las interrupciones pueden ser procesadas. Ambos deben estar activos para que una interrupción se procese.
  • El problema real: El juego está atascado en un bucle de espera porque IE=0x00, lo que impide que las interrupciones se procesen, incluso si IME está activo y hay interrupciones pendientes en IF.

Lo que Falta Confirmar

  • ¿Por qué IE se queda en 0x00?: Necesitamos verificar si el juego nunca escribe en IE después del EI, o si algo está escribiendo 0x00 en IE incorrectamente.
  • ¿El EI se ejecuta correctamente?: Necesitamos verificar si el EI en PC:0x60A6 realmente programa IME correctamente, y si IME se activa después de la siguiente instrucción.
  • ¿Hay escrituras en IE durante el polling?: El sniper de polling nos dirá si alguien está escribiendo en IE durante la espera, lo cual podría explicar por qué IE se queda en 0x00.

Hipótesis y Suposiciones

Hipótesis principal: El juego ejecuta EI en PC:0x60A6, pero IE está en 0x00 en ese momento. El juego espera que algo más (probablemente una ISR o código de inicialización) habilite las fuentes de interrupciones en IE, pero eso nunca ocurre porque las interrupciones están deshabilitadas.

Suposición: El juego debería escribir en IE (por ejemplo, LD A, 0x0D; LDH (0xFF), A para habilitar V-Blank y Timer) antes o después del EI, pero por alguna razón no lo hace o se ejecuta incorrectamente.

Próximos Pasos

  • [ ] Ejecutar el emulador con la nueva instrumentación y analizar los logs [CPU-EI] y [POLLING-WATCH]
  • [ ] Verificar si IE está en 0x00 cuando se ejecuta EI en PC:0x60A6
  • [ ] Verificar si IE cambia durante el bucle de polling
  • [ ] Si IE está en 0x00, buscar en el código del juego dónde debería habilitarse
  • [ ] Si IE cambia a 0x00 durante el polling, identificar qué código está escribiendo en IE
  • [ ] Implementar corrección basada en los hallazgos