This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Expanded Sniper: The Rise of A
Summary
The trace from Step 0236 revealed an infinite loop in0x2B2Awhere the game compares
the accumulatorTOwith0xFDthroughCP 0xFD. The value ofTOis constantly0x00, causing the comparison to fail and the jump
conditionalJR NZruns, creating an infinite loop.
To identify the source of the incorrect value inTO, we expand the tracing range
of the Sniper backwards from0x2B2Auntil0x2B20, allowing
Observe the instructions that precede the comparison and determine which operation loads the
accumulator before verification.
Hardware Concept
On the Game Boy, the accumulatorTOis the main register for arithmetic operations
and logical. Instructions that load values intoTOinclude:
- LD A, (HL)(0x7E): Read a byte of memory from the address pointed to by HL.
- LD A, (DE)(0x1A): Reads a byte of memory from the address pointed to by DE.
- POP AF(0xF1): Retrieves the value of
TOfrom the stack. - RH A, d8(0x3E): Load an 8-bit immediate value into
TO.
When a program enters an infinite loop due to a comparison that always fails, it is critical identifywhich instruction loads the valuethat is being compared. If the value comes from memory (WRAM, VRAM, HRAM), it may indicate that:
- The memory has not been initialized correctly.
- An initialization routine did not run or failed.
- The expected value was written to a different address.
In the case of Tetris, the trace showed thatH.L.points to0xE7F9(WRAM - Work RAM)
and thatOFincreases by 2 at a time, suggesting a memory copy or check loop.
If the game reads from(H.L.)and gets0x00rather0xFD, means
that the memory at that address does not contain the expected value.
Implementation
Sniper's tracing range expanded from0x2B2A-0x2B35to0x2B20-0x2B30, moving the lower limit back to capture the
instructions preceding the comparison.
Modification in CPU.cpp
We updated the surgical debug block to include the new range and simplified
output to show onlyTOandH.L.(the most relevant records
to identify loads from memory):
// --- Step 0237: EXPANDED SNIPER (BACKBACK) ---
// We extend the range backwards (0x2B20) to identify the instruction
// which loads A before the CP 0xFD comparison at 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());
}
Design decisions
- Range 0x2B20-0x2B30: Includes 16 bytes before comparison, enough for capture several previous instructions (most instructions are 1-3 bytes).
- Simplified exit: We show only
TOandH.L.to reduce noise in the logs and facilitate the identification of loads from memory. - Upper limit 0x2B30: We keep the limit close to the comparison to avoid capture irrelevant instructions later in the flow.
Affected Files
src/core/cpp/CPU.cpp- Sniper tracing range expansion (Step 0237)
Tests and Verification
To validate the tracing range expansion:
- Recompilation: Execute
.\rebuild_cpp.ps1to recompile the C++ extension. - Execution: Execute
python main.py roms/tetris.gband observe the logs[SNIPER]. - Analysis: Search the logs for instructions that load
TObefore reaching0x2B2A:LD A, (HL)(0x7E): If it appears, check what value is in the address pointed to byH.L..LD A, (DE)(0x1A): If it appears, check what value is in the address pointed to byOF.POP AF(0xF1): If it appears, check what value was retrieved from the stack.
Expected result:The logs should show the sequence of instructions from0x2B20until0x2B2A, revealing which operation loadsTObefore
of the comparisonCP 0xFD.
Sources consulted
- Bread Docs:CPU Instruction Set- Loading instructions (LD)
- Bread Docs:CPU Registers- Accumulator A and HL/DE registers
Educational Integrity
What I Understand Now
- Data source search: When a value in a record is incorrect, it is necessary to trace back in the execution flow to find the instruction that He loaded it.
- Plot range: Expanding the debug range backwards allows capturing previous instructions that may be the root cause of the problem.
- Memory patterns: Yeah
H.L.andOFthey increase systematically, it suggests a memory copy or check loop that may fail if memory is not initialized correctly.
What remains to be confirmed
- Instruction loading A: We need to see in the logs what instruction
specific load
TObefore comparison. - Memory value: If the load is from memory (
LD A, (HL)), we need to check what value is really at that address and why it is not0xFD. - Initialization routine: Determine if an initialization routine is not executed or if you wrote the value to a different address.
Hypotheses and Assumptions
Main hypothesis:The game is reading from WRAM (0xE7F9and following)
waiting to find0xFD, but the memory contains0x00because:
- An initialization routine was not executed (perhaps blocked by a precondition).
- The value was written to a different address (offset or address calculation error).
- Memory was overwritten after initialization (memory corruption).
The expanded trace will allow us to confirm or refute this hypothesis by identifying the instruction
exactly what loadTOand where he reads from.
Next Steps
- [ ] Run the emulator and analyze the expanded Sniper logs.
- [ ] Identify the instruction that loads
TObeforeCP 0xFD. - [ ] If it is a load from memory, check what value is really at that address.
- [ ] Determine why the expected value (
0xFD) is not present. - [ ] Implement the necessary fix (memory initialization, offset correction, etc.).