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.
Investigación de Bucle de Reinicio y MBC1
Resumen
Este Step implementa instrumentación avanzada para detectar si Pokémon Red está atrapado en un Bucle de Reinicio (Reset Loop). El análisis del Step 0278 reveló que se detectaron más de 300,000 salidas del bucle de retardo en solo 12 segundos, lo que sugiere fuertemente que el juego está reiniciándose continuamente. Es probable que, tras salir del retardo, el juego encuentre una condición de error (como una pila corrupta o un banco de ROM mal mapeado) y salte de nuevo a 0x0000 o ejecute un RST 00.
Se añadieron tres monitores críticos: (1) detector de paso por los vectores de reinicio (0x0000 y 0x0100) para confirmar la teoría del Reset Loop, (2) seguimiento del handler de V-Blank (0x0040) para verificar si las interrupciones se procesan correctamente, y (3) monitor de cambio de modo MBC1 para detectar si el mapeo de memoria se corrompe y desplaza el Banco 0 fuera de 0x0000-0x3FFF, rompiendo los vectores de interrupción.
Concepto de Hardware
Los bucles de reinicio son un problema común en emuladores cuando hay errores en la emulación de hardware crítico. Estos bucles ocurren cuando el código del juego intenta ejecutar una instrucción o acceder a memoria que no está disponible o está corrupta, causando que el juego salte al vector de reinicio (0x0000 o 0x0100) y reinicie la ejecución desde el principio.
1. Vectores de Reinicio en Game Boy
El Game Boy tiene dos vectores de reinicio principales:
- 0x0000: Vector de Boot ROM. Si el sistema tiene una Boot ROM activa, este vector apunta al inicio de la Boot ROM. Si no hay Boot ROM, este vector apunta directamente al código del cartucho.
- 0x0100: Vector de entrada del cartucho. Este es el punto de entrada estándar para el código del juego después de que la Boot ROM termina (o si no hay Boot ROM).
Cuando el PC alcanza 0x0000 o 0x0100, el juego está reiniciando. Si esto ocurre repetidamente, el juego está atrapado en un bucle de reinicio.
2. MBC1 y Mapeo de Memoria
El MBC1 (Memory Bank Controller 1) es el controlador de memoria más común en cartuchos de Game Boy. Tiene dos modos de operación:
- Modo 0 (ROM Banking): El Banco 0 siempre está mapeado en 0x0000-0x3FFF. Los bancos superiores se mapean en 0x4000-0x7FFF. Este es el modo estándar y el más común.
- Modo 1 (RAM Banking): El Banco 0 puede ser reemplazado por un banco de RAM en 0x0000-0x3FFF. Este modo se usa raramente y solo en cartuchos con RAM grande.
Problema crítico: Si el MBC1 se cambia accidentalmente al Modo 1, el Banco 0 de ROM podría desaparecer de 0x0000-0x3FFF. Esto rompería los vectores de interrupción (0x0000, 0x0040, 0x0048, 0x0050, 0x0058, 0x0060) porque estos vectores están en el Banco 0. Si el juego intenta saltar a uno de estos vectores y el Banco 0 no está mapeado, leerá datos incorrectos o basura, causando un crash o reinicio.
Fuente: Pan Docs - "MBC1": El modo 0/1 controla si 0x0000-0x3FFF es ROM o RAM. En modo 1, el Banco 0 de ROM puede no estar disponible en 0x0000-0x3FFF, rompiendo los vectores de interrupción.
3. Vectores de Interrupción
El Game Boy tiene 5 vectores de interrupción, todos ubicados en el Banco 0 de ROM (0x0000-0x3FFF):
- 0x0040: V-Blank Interrupt (Vertical Blanking)
- 0x0048: LCD STAT Interrupt
- 0x0050: Timer Interrupt
- 0x0058: Serial Interrupt
- 0x0060: Joypad Interrupt
Si el Banco 0 no está mapeado correctamente en 0x0000-0x3FFF, estos vectores apuntarán a datos incorrectos, causando que las interrupciones ejecuten código corrupto o basura, lo que puede llevar a un reinicio del sistema.
Implementación
Se implementaron tres monitores de instrumentación para detectar y diagnosticar el bucle de reinicio:
1. Monitor de Reinicio en CPU.cpp
Se añadió un detector que monitorea cuando el PC pasa por los vectores de reinicio (0x0000 o 0x0100). Este monitor captura:
- El PC original (antes del fetch de la instrucción)
- Un contador de reinicios (para ver cuántas veces ocurre)
- El Stack Pointer (SP) para detectar corrupción de pila
- El banco ROM actual (para verificar si el mapeo es correcto)
- El estado de IME (Interrupt Master Enable)
- Los registros IE (Interrupt Enable) e IF (Interrupt Flag)
// --- Step 0279: Monitor de Reinicio (Reset Loop Detection) ---
if (original_pc == 0x0000 || original_pc == 0x0100) {
static uint32_t reset_count = 0;
printf("[RESET-WATCH] Pasando por PC:0x%04X (Contador: %u) | SP:0x%04X Bank:%d | IME:%d IE:%02X IF:%02X\n",
original_pc, ++reset_count, regs_->sp, mmu_->get_current_rom_bank(),
ime_ ? 1 : 0, mmu_->read(0xFFFF), mmu_->read(0xFF0F));
}
2. Seguimiento del Handler de V-Blank
Se añadió un monitor que detecta cuando el código entra al handler de V-Blank (0x0040). Este monitor captura:
- El Stack Pointer (para verificar si la pila es válida cuando se entra al handler)
- El registro HL (para ver qué datos está manipulando el handler)
- El registro A (para ver el estado de los datos)
- El banco ROM actual (para verificar que el vector apunta al código correcto)
// --- Step 0279: Seguimiento del Handler de V-Blank ---
if (original_pc == 0x0040) {
printf("[VBLANK-ENTRY] Vector 0x0040 alcanzado. SP:0x%04X | HL:0x%04X | A:0x%02X | Bank:%d\n",
regs_->sp, regs_->get_hl(), regs_->a, mmu_->get_current_rom_bank());
}
3. Monitor de Cambio de Modo MBC1 en MMU.cpp
Se añadió un monitor que detecta cuando el MBC1 cambia de modo (0x6000-0x7FFF). Este monitor captura:
- El modo anterior y el nuevo modo (0 = ROM Banking, 1 = RAM Banking)
- El PC donde ocurre el cambio (para identificar qué código lo causa)
- El banco 0 y banco N actuales (para verificar el mapeo)
// --- Step 0279: Monitor de Cambio de Modo MBC1 ---
uint8_t new_mode = value & 0x01;
if (mbc1_mode_ != new_mode) {
printf("[MBC1-MODE] Cambio de modo detectado: %d -> %d en PC:0x%04X | Bank0:%d BankN:%d\n",
mbc1_mode_, new_mode, debug_current_pc, bank0_rom_, bankN_rom_);
}
mbc1_mode_ = new_mode;
update_bank_mapping();
Decisiones de Diseño
Uso de original_pc: Se usa el PC original (capturado al inicio de step() antes del fetch)
en lugar del PC actual, porque el PC puede cambiar durante la ejecución de la instrucción. Esto asegura que detectamos el paso por los
vectores incluso si la instrucción modifica el PC.
Contadores estáticos: Se usan variables estáticas para los contadores (como reset_count) para mantener
el estado entre llamadas a step(). Esto permite rastrear cuántas veces ocurre un evento sin necesidad de almacenamiento externo.
Logging detallado: Se captura información completa del estado del sistema (SP, bancos, interrupciones) para facilitar el diagnóstico. Esto permite identificar patrones en los reinicios y correlacionarlos con cambios en el estado del sistema.
Archivos Afectados
src/core/cpp/CPU.cpp- Añadidos monitores de reinicio (0x0000/0x0100) y seguimiento de V-Blank (0x0040)src/core/cpp/MMU.cpp- Añadido monitor de cambio de modo MBC1 en el rango 0x6000-0x7FFF
Tests y Verificación
Esta implementación es puramente de instrumentación y diagnóstico. No se añadieron tests unitarios porque los monitores son herramientas de depuración que se activan durante la ejecución del emulador con ROMs reales.
Validación: Los monitores se validarán ejecutando Pokémon Red y observando los logs en la consola. Si el juego está en un
bucle de reinicio, deberíamos ver múltiples mensajes [RESET-WATCH] con el contador incrementándose. Si el MBC1 cambia de modo
incorrectamente, deberíamos ver mensajes [MBC1-MODE] indicando el cambio.
Comando de ejecución: Los monitores se activan automáticamente durante la ejecución normal del emulador. Para ver los logs, ejecutar el emulador con Pokémon Red y observar la salida de la consola.
Validación de módulo compilado C++: Los cambios requieren recompilación de la extensión Cython. Ejecutar:
python setup.py build_ext --inplace
Fuentes Consultadas
- Pan Docs: https://gbdev.io/pandocs/ - Sección "MBC1" (modos de operación y mapeo de memoria)
- Pan Docs: https://gbdev.io/pandocs/ - Sección "Interrupts" (vectores de interrupción en 0x0040-0x0060)
- Pan Docs: https://gbdev.io/pandocs/ - Sección "Reset Vectors" (vectores de reinicio en 0x0000 y 0x0100)
Integridad Educativa
Lo que Entiendo Ahora
- Bucles de Reinicio: Ocurren cuando el juego salta repetidamente a los vectores de reinicio (0x0000 o 0x0100), generalmente debido a errores en la emulación de hardware crítico (pila corrupta, mapeo de memoria incorrecto, vectores de interrupción rotos).
- MBC1 Modo 1: Si el MBC1 se cambia al Modo 1 (RAM Banking), el Banco 0 de ROM puede desaparecer de 0x0000-0x3FFF, rompiendo los vectores de interrupción. Esto puede causar que las interrupciones ejecuten código corrupto o basura, llevando a un reinicio del sistema.
- Vectores de Interrupción: Todos los vectores de interrupción (0x0040-0x0060) están en el Banco 0 de ROM. Si el Banco 0 no está mapeado correctamente, estos vectores apuntarán a datos incorrectos, causando crashes o reinicios.
Lo que Falta Confirmar
- Frecuencia de reinicios: ¿Con qué frecuencia ocurren los reinicios? ¿Es constante o intermitente? Los logs de
[RESET-WATCH]nos dirán esto. - Causa del reinicio: ¿Es el MBC1 cambiando de modo, una pila corrupta, o algo más? Los logs de
[MBC1-MODE]y el estado de SP en[RESET-WATCH]nos ayudarán a identificar la causa. - Estado del handler de V-Blank: ¿Se ejecuta el handler de V-Blank correctamente, o nunca se alcanza? Los logs de
[VBLANK-ENTRY]nos dirán si el vector 0x0040 es accesible.
Hipótesis y Suposiciones
Hipótesis principal: El juego está en un bucle de reinicio causado por uno de estos problemas:
- El MBC1 cambia accidentalmente al Modo 1, desplazando el Banco 0 fuera de 0x0000-0x3FFF y rompiendo los vectores de interrupción.
- La pila se corrompe, causando que un RET o POP ejecute código corrupto que salta a 0x0000.
- Un opcode no implementado o mal emulado causa un comportamiento inesperado que lleva a un salto a 0x0000.
Los monitores implementados nos permitirán confirmar o refutar estas hipótesis observando los logs durante la ejecución.
Próximos Pasos
- [ ] Ejecutar Pokémon Red con los monitores activos y analizar los logs de
[RESET-WATCH]para confirmar si hay un bucle de reinicio - [ ] Verificar si hay mensajes de
[MBC1-MODE]que indiquen cambios de modo incorrectos - [ ] Analizar los logs de
[VBLANK-ENTRY]para verificar si el handler de V-Blank se ejecuta correctamente - [ ] Si se confirma un bucle de reinicio, identificar la causa raíz (MBC1, pila, opcode) y corregirla
- [ ] Si el MBC1 está cambiando de modo incorrectamente, investigar qué código del juego lo causa y por qué