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.
Francotirador Expandido: El Origen de A
Resumen
La traza del Step 0236 reveló un bucle infinito en 0x2B2A donde el juego compara
el acumulador A con 0xFD mediante CP 0xFD. El valor de
A es constantemente 0x00, causando que la comparación falle y el salto
condicional JR NZ se ejecute, creando un bucle infinito.
Para identificar el origen del valor incorrecto en A, expandimos el rango de trazado
del Francotirador hacia atrás desde 0x2B2A hasta 0x2B20, permitiendo
observar las instrucciones que preceden a la comparación y determinar qué operación carga el
acumulador antes de la verificación.
Concepto de Hardware
En el Game Boy, el acumulador A es el registro principal para operaciones aritméticas
y lógicas. Las instrucciones que cargan valores en A incluyen:
- LD A, (HL) (0x7E): Lee un byte de memoria desde la dirección apuntada por HL.
- LD A, (DE) (0x1A): Lee un byte de memoria desde la dirección apuntada por DE.
- POP AF (0xF1): Recupera el valor de
Adesde la pila. - LD A, d8 (0x3E): Carga un valor inmediato de 8 bits en
A.
Cuando un programa entra en un bucle infinito debido a una comparación que siempre falla, es crítico identificar qué instrucción carga el valor que se está comparando. Si el valor proviene de memoria (WRAM, VRAM, HRAM), puede indicar que:
- La memoria no se ha inicializado correctamente.
- Una rutina de inicialización no se ejecutó o falló.
- El valor esperado se escribió en una dirección diferente.
En el caso de Tetris, la traza mostró que HL apunta a 0xE7F9 (WRAM - Work RAM)
y que DE se incrementa de 2 en 2, sugiriendo un bucle de copia o verificación de memoria.
Si el juego lee de (HL) y obtiene 0x00 en lugar de 0xFD, significa
que la memoria en esa dirección no contiene el valor esperado.
Implementación
Expandimos el rango de trazado del Francotirador desde 0x2B2A-0x2B35 a
0x2B20-0x2B30, moviendo el límite inferior hacia atrás para capturar las
instrucciones que preceden a la comparación.
Modificación en CPU.cpp
Actualizamos el bloque de debug quirúrgico para incluir el nuevo rango y simplificamos
la salida para mostrar solo A y HL (los registros más relevantes
para identificar cargas desde memoria):
// --- Step 0237: FRANCOTIRADOR EXPANDIDO (RETROCESO) ---
// Ampliamos el rango hacia atrás (0x2B20) para identificar la instrucción
// que carga A antes de la comparación CP 0xFD en 0x2B2A.
if (regs_->pc >= 0x2B20 && regs_->pc <= 0x2B30) {
uint8_t opcode = mmu_->read(regs_->pc);
uint8_t next_byte = mmu_->read(regs_->pc + 1);
printf("[SNIPER] PC:%04X | OP:%02X %02X | A:%02X | HL:%04X\n",
regs_->pc, opcode, next_byte, regs_->a, regs_->get_hl());
}
Decisiones de diseño
- Rango 0x2B20-0x2B30: Incluye 16 bytes antes de la comparación, suficiente para capturar varias instrucciones previas (la mayoría de instrucciones son de 1-3 bytes).
- Salida simplificada: Mostramos solo
AyHLpara reducir el ruido en los logs y facilitar la identificación de cargas desde memoria. - Límite superior 0x2B30: Mantenemos el límite cerca de la comparación para evitar capturar instrucciones irrelevantes más adelante en el flujo.
Archivos Afectados
src/core/cpp/CPU.cpp- Expansión del rango de trazado del Francotirador (Step 0237)
Tests y Verificación
Para validar la expansión del rango de trazado:
- Recompilación: Ejecutar
.\rebuild_cpp.ps1para recompilar la extensión C++. - Ejecución: Ejecutar
python main.py roms/tetris.gby observar los logs[SNIPER]. - Análisis: Buscar en los logs instrucciones que carguen
Aantes de llegar a0x2B2A:LD A, (HL)(0x7E): Si aparece, verificar qué valor hay en la dirección apuntada porHL.LD A, (DE)(0x1A): Si aparece, verificar qué valor hay en la dirección apuntada porDE.POP AF(0xF1): Si aparece, verificar qué valor se recuperó de la pila.
Resultado esperado: Los logs deben mostrar la secuencia de instrucciones desde
0x2B20 hasta 0x2B2A, revelando qué operación carga A antes
de la comparación CP 0xFD.
Fuentes Consultadas
- Pan Docs: CPU Instruction Set - Instrucciones de carga (LD)
- Pan Docs: CPU Registers - Acumulador A y registros HL/DE
Integridad Educativa
Lo que Entiendo Ahora
- Búsqueda de origen de datos: Cuando un valor en un registro es incorrecto, es necesario rastrear hacia atrás en el flujo de ejecución para encontrar la instrucción que lo cargó.
- Rango de trazado: Expandir el rango de debug hacia atrás permite capturar instrucciones previas que pueden ser la causa raíz del problema.
- Patrones de memoria: Si
HLyDEse incrementan sistemáticamente, sugiere un bucle de copia o verificación de memoria que puede fallar si la memoria no está inicializada correctamente.
Lo que Falta Confirmar
- Instrucción que carga A: Necesitamos ver en los logs qué instrucción
específica carga
Aantes de la comparación. - Valor en memoria: Si la carga es desde memoria (
LD A, (HL)), necesitamos verificar qué valor hay realmente en esa dirección y por qué no es0xFD. - Rutina de inicialización: Determinar si una rutina de inicialización no se ejecutó o si escribió el valor en una dirección diferente.
Hipótesis y Suposiciones
Hipótesis principal: El juego está leyendo de WRAM (0xE7F9 y siguientes)
esperando encontrar 0xFD, pero la memoria contiene 0x00 porque:
- Una rutina de inicialización no se ejecutó (quizás bloqueada por una condición previa).
- El valor se escribió en una dirección diferente (error de offset o cálculo de dirección).
- La memoria se sobrescribió después de la inicialización (corrupción de memoria).
La traza expandida nos permitirá confirmar o refutar esta hipótesis identificando la instrucción
exacta que carga A y desde dónde lee.
Próximos Pasos
- [ ] Ejecutar el emulador y analizar los logs del Francotirador expandido.
- [ ] Identificar la instrucción que carga
Aantes deCP 0xFD. - [ ] Si es una carga desde memoria, verificar qué valor hay realmente en esa dirección.
- [ ] Determinar por qué el valor esperado (
0xFD) no está presente. - [ ] Implementar el fix necesario (inicialización de memoria, corrección de offset, etc.).