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

Operation Ghost in the Machine: Post-Delay Flow Tracing and PPU Pattern Debugging

Date:2025-12-25 StepID:0278 State: draft

Summary

This Step implements "Operation Ghost in the Machine" to trace the execution flow after the delay loop identified in Step 0277 ends. Previous analysis confirmed that the delay loop works correctly (DE decreases to 0), but the game does not activate the intro (the Nidorino vs Gengar fight) after the delay. Additionally, the screen shows a pattern of wrong vertical stripes, suggesting a problem in the PPU rendering.

Added instrumentation at two critical points: (1) post-delay execution trail that captures the next 200 instructions after the PC exits 0x6155 (where the delay loop ends), and (2) inspection of the PPU in the center of the screen (LY=72, X=80) to see what Tile ID is actually reading when rendering the background. The goal is to identify if the game is trying enable interrupts after delay, and understand why the PPU is rendering an erroneous vertical stripe pattern.

Hardware Concept

In the original Game Boy games developed by companies such as Game Freak (Pokémon), the initialization routines follow a specific pattern to manage the hardware before handing over control to the main game engine. These routines are critical for the correct functioning of the game.

1. Game Initialization Sequence

A typical initialization routine in a Game Boy game follows this pattern:

  1. Hardware Reset:Clean hardware registers (VRAM, OAM, PPU registers)
  2. Registry Configuration:Set control registers (LCDC, STAT, paddles, etc.)
  3. Interrupt Deactivation:RunD.I.(Disable Interrupts) to avoid interruptions during initialization
  4. Delay Loops:Use software delay loops to sync with hardware or create temporary pauses
  5. Activation of Interrupts:RunEI(Enable Interrupts) to allow the hardware (PPU, Timer) to interrupt the CPU
  6. Game Engine Start:Gives control to the main game engine, which usually runs in the main loop or on V-Blank interrupts

2. The "Post-Delay Silence" Problem

If a game runs a delay loop but never enables interrupts afterwards, the game becomes "mute": the CPU can execute instructions, but hardware interrupts (V-Blank, Timer, etc.) are never processed. This causes several problems:

  • Intro does not start:Many game intros run on the V-Blank ISR. If IE=0, the ISR is never executed and the intro is not activated
  • PPU not synchronizing:Although the PPU can continue rendering, without V-Blank interruptions, the game does not know when a frame is complete
  • Timer not working:Timer interrupts are never processed, causing time-dependent functions (animations, music) to not work

3. Wrong Rendering Patterns in PPU

If the PPU is rendering the wrong vertical stripe pattern, this may indicate several problems:

  • Incorrect addressing:The PPU could be reading Tile IDs from the tilemap from incorrect addresses, causing random or garbage tiles to be rendered
  • Tilemap not initialized:If the tilemap contains garbage values ​​(such as 0x7F), the PPU will try to render tiles that do not exist or are empty
  • Incorrect Tile Data Base:If the LCDC register has an incorrect Tile Data Base configured (signed vs unsigned), the Tile IDs are interpreted incorrectly
  • Incorrect scroll:If the SCX/SCY registers have incorrect values, the tilemap is read from the wrong positions

Analysis of the "Barcode":If the VRAM was cleared to 0x00 (Step 0272) and the Tilemap has 0x7F (Step 0271), the PPU is trying to render Tile 0x7F on the entire screen. If Tile 0x7F is empty (0x00), the screen should be single solid color. The vertical stripes suggest an addressing error or that we are reading "junk" that is interprets as patterns.

4. Hardware Initialization and Management Routines

Original game initialization routines (such as those developed by Game Freak) are especially careful with hardware management because:

  • Compatibility:Should work on all hardware variants (original Game Boy, Game Boy Pocket, Game Boy Color)
  • Critical timing:Initialization must be synchronized with the hardware (PPU, Timer) to avoid visual glitches
  • Sturdiness:They must handle unexpected hardware states (e.g. if the game restarts in the middle of a frame)

Fountain:Bread Docs -Game Boy Programming Manual- Interrupts, PPU, LCD Control

Implementation

Two instrumentation points were implemented: one inCPU.cppto track post-delay flow, and another inPPU.cppto inspect which Tile ID the PPU is reading when rendering the center of the screen.

1. Post-Delay Execution Trail (PC:0x6155)

At the end of the methodstep()inCPU.cpp, added a trail that captures the next 200 instructions after the PC exits 0x6155 (where the delay loop ends). The code usesoriginal_pc(captured at the beginning ofstep()before the fetch) to detect when the instruction is executed at 0x6155, activating the trace.

// --- Step 0278: Post-Delay Execution Trail (0x6155) ---
static bool post_delay_trace = false;
static int post_delay_count = 0;

if (original_pc == 0x6155 && !post_delay_trace) {
    printf("[SNIPER-AWAKE] Exiting delay loop! Starting stream trace...\n");
    post_delay_trace = true;
    post_delay_count = 0;
}

if (post_delay_trace && post_delay_count< 200) {
    uint8_t current_op = mmu_->read(original_pc);
    printf("[POST-DELAY] PC:%04X OP:%02X | A:%02X HL:%04X | IE:%02X IME:%d\n",
           original_pc, current_op, regs_->a, regs_->get_hl(),
           mmu_->read(0xFFFF), ime_ ? 1 : 0);
    post_delay_count++;
}
// -----------------------------------------

Information captured:Each line of the trail shows the original PC, the executed opcode, the A register, HL, the IE register (0xFFFF), and the IME (Interrupt Master Enable) status. This allows you to identify if the game is trying to runEI(opcode 0xFB) or write to 0xFFFF to enable interrupts.

2. PPU Inspection in the Center of the Screen (LY=72)

In the methodrender_scanline()ofPPU.cpp, a timely log was added that runs only once when the center of the screen is rendered (LY=72 of 144 lines, X=80 of 160 pixels). The log shows the Tile Map Address, the Tile ID read, and the Tile Data Base configured.

// --- Step 0278: PPU inspection in the center of the screen ---
static int ppu_debug_count = 0;
if (ly_ == 72 && ppu_debug_count< 1) {
    // Solo una vez en el medio de la pantalla (LY=72 de 144 líneas)
    if (x == 80) {  // Centro horizontal (80 de 160 píxeles)
        printf("[PPU-DEBUG] LY:72 X:80 | TileMapAddr:%04X | TileID:%02X | TileDataBase:%04X\n",
               tile_map_addr, tile_id, tile_data_base);
        ppu_debug_count++;
    }
}
// -----------------------------------------

Information captured:The log shows the address of the tilemap where the Tile ID is read, the Tile ID read, and the configured tile database. This allows you to check if the PPU is reading correct Tile IDs from the tilemap or if He is reading garbage.

Components created/modified

  • CPU.cpp:The method was modifiedstep()at the end to add the post-delay execution trail
  • PPU.cpp:The method was modifiedrender_scanline()to add Tile ID inspection in the center of the screen

Design decisions

Limit of 200 instructions:The post-delay trail is limited to 200 instructions to avoid saturating the log. If the game doesn't enable interrupts in the first 200 instructions after the delay, it probably never will, or there is a deeper problem in the execution flow.

Single inspection in the center:PPU inspection runs only once when the center is rendered. the screen (LY=72, X=80) to avoid saturating the log. The center of the screen is representative of the general state of the tilemap.

Usage of original_pc:It is usedoriginal_pc(captured at the beginning ofstep()before fetch) to detect when the instruction at 0x6155 is executed. This is critical because the PC advances during instruction execution, but we want to detect the instruction that is at 0x6155, not the next one.

Affected Files

  • src/core/cpp/CPU.cpp- Modified methodstep()at the end to add post-delay execution trail (0x6155)
  • src/core/cpp/PPU.cpp- Modified methodrender_scanline()to add Tile ID inspection in the center of the screen (LY=72, X=80)

Tests and Verification

The verification will be carried out by running Pokémon Red and analyzing the generated logs:

  • Command executed: python main.py roms/pkmn.gb
  • Expected result:You should see two types of messages in the log:
    • [SNIPER-AWAKE]: Indicates that the delay loop has ended and scanning has started
    • [POST DELAY]: Displays the next 200 instructions after the delay, including PC, opcode, A/HL registers, IE, and IME
    • [PPU-DEBUG]: Shows the Tile ID read in the center of the screen

Log Analysis

Search for EI (0xFB):In the logs[POST DELAY], you should search if the opcode appears0xFB(EI - Enable Interrupts). If it appears, the game tries to enable interrupts after the delay. If it does not appear in the first 200 instructions, the game probably doesn't enable interrupts, causing "post-delay silence".

Search for writes to 0xFFFF:You should also look for if the game writes to 0xFFFF (IE - Interrupt Enable) after the delay. If the game writes to 0xFFFF but does not run EI, interrupts will not be enabled until EI is run.

Tile ID Analysis:The log[PPU-DEBUG]will show which Tile ID the PPU in the center of the tile is reading. screen. If the Tile ID is 0x7F (as noted in Step 0271), the PPU is attempting to render Tile 0x7F, which is probably is empty (0x00 in VRAM), causing the screen to appear a solid color. If there are vertical stripes, there could be a printing error. addressing in the PPU.

Compiled C++ module validation:✅ Successful build without linter errors. Instrumentation is ready to be tested in execution.

Sources consulted

Note: Implementation based on general knowledge of LR35902 architecture and Pan Docs specifications.

Educational Integrity

What I Understand Now

  • Initialization routines:Original games follow a specific pattern to manage hardware before handing over control to the game engine. This includes disabling interrupts during initialization, running delay loops, and then trigger interrupts to allow the hardware to function properly.
  • The "post-delay silence":If a game runs a delay loop but never enables interrupts Afterwards, the game goes "mute": the CPU can execute instructions, but hardware interrupts are never they process. This causes the intro to not boot, the PPU to not sync, and the Timer to not work.
  • Bad rendering patterns:If the PPU is rendering an incorrect vertical stripe pattern, this may indicate routing problems, uninitialized tilemap, incorrect Tile Data Base, or incorrect scrolling.
  • Execution trail:Trace instructions that are executed after a critical point (such as the end of a delay loop) allows you to identify if the game is trying to enable interruptions or if there is a problem in the flow of execution.

What remains to be confirmed

  • Does the game enable interrupts after delay?If we seeEI(0xFB) or a write to 0xFFFF in the [POST-DELAY] logs, the game tries to enable interrupts. If not, the game never enables interruptions, causing "post-delay silence".
  • What Tile ID is the PPU reading?The [PPU-DEBUG] log will show which Tile ID the PPU in the center is reading of the screen. If it is 0x7F, the PPU is trying to render an empty tile, which would explain why the screen looks of a solid color.
  • Why are there vertical stripes?If there are vertical stripes instead of a solid color, there could be a addressing error in the PPU or we are reading "garbage" that is interpreted as patterns.

Hypotheses and Assumptions

Main hypothesis:The game does not enable interrupts after the delay loop, causing the "silence post-delay". The intro doesn't start because it depends on V-Blank interrupts that are never processed.

Secondary hypothesis:The vertical stripes on the screen are caused by an addressing error in the PPU or by reading incorrect Tile IDs from the tilemap. If the tilemap has 0x7F and the VRAM is empty (0x00), the screen should be a solid color, not show stripes.

Next Steps

  • [ ] Run Pokémon Red and analyze the [POST-DELAY] logs to see what instructions are executed after the delay
  • [ ] Search if it appearsEI(0xFB) or writes to 0xFFFF in the logs [POST-DELAY]
  • [ ] Analyze the [PPU-DEBUG] log to see what Tile ID the PPU in the center of the screen is reading
  • [ ] If the game does not enable interruptions, investigate why (is there a bug in the game code? Are we accidentally skipping code?)
  • [ ] If there are vertical stripes, investigate the addressing error in the PPU or tilemap
  • [ ] If the game enables interrupts but the intro does not start, investigate why V-Blank interrupts are not being processed correctly