⚠️ Clean-Room / Educational

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

Date:2025-12-22 StepID:0237 State: draft

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 ofTOfrom the stack.
  • RH A, d8(0x3E): Load an 8-bit immediate value intoTO.

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 onlyTOandH.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:

  1. Recompilation: Execute.\rebuild_cpp.ps1to recompile the C++ extension.
  2. Execution: Executepython main.py roms/tetris.gband observe the logs[SNIPER].
  3. Analysis: Search the logs for instructions that loadTObefore 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

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: YeahH.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 loadTObefore 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 loadsTObeforeCP 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.).