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

V-Blank Interrupt Tracing

Date:2025-12-18 StepID:0050 State: draft

Summary

were addedcritical diagnostic logsto monitor the dispatch of interrupts in the CPU, specifically to detect whether the V-Blank interrupt (0x0040) is executing correctly after the game turns on the LCD. The diagnostic includes an informative log with the ⚡ symbol when an interrupt is dispatched, showing the vector, the previous PC and the interrupt type. Additionally, a log was added to the renderer to detect when LCDC is 0x80 (LCD ON, BG OFF), a critical state indicating that the game is waiting for the V-Blank interrupt to configure the rest of the graphics.

Hardware Concept

On the Game Boy, when a game turns on the LCD by typing0x80in the LCDC register (0xFF40), is activating bit 7 (LCD Enable) but leaving bit 0 (BG Display) at 0. This means that the LCD is turned on but the background is not drawn yet.

The typical game initialization flow is:

  1. Turn on LCD: Write0x80in LCDC (LCD ON, BG OFF)
  2. Wait V-Blank: The game waits for the PPU to generate the V-Blank interrupt (vector 0x0040)
  3. Set up graphics: Inside the V-Blank handler, the game activates the background and sprites (type0x93or similar in LCDC)

If the V-Blank interrupt is not dispatched correctly after step 1, the game freezes waiting an interruption that never comes. This can occur if:

  • IME is disabled: The CPU has interrupts disabled (IME=False)
  • IE is not configured: Register IE (0xFFFF) does not have bit 0 (V-Blank) set
  • IF is not being generated: The PPU is not correctly generating the interrupt flag in IF (0xFF0F)

Fountain:Pan Docs - Interrupts, V-Blank Interrupt, LCD Control Register

Implementation

Two strategic diagnostic points were added:

1. Interrupt Dispatch Log (CPU)

Insrc/cpu/core.py, methodhandle_interrupts(), an informative log was added right after the CPU accepts an interrupt and jumps to the vector. The log shows:

  • Interrupt vector (0x0040 for V-Blank, 0x0048 for STAT, etc.)
  • Previous PC (address where the CPU was before jumping)
  • Interrupt Type (V-Blank, LCD STAT, Timer, Serial, Joypad)

This log is executedonly when the interrupt is actually dispatched, that is, when: IME=True, IE has the bit set, IF has the bit set, and the CPU accepts the interrupt.

2. LCDC Status Log 0x80 (Renderer)

Insrc/gpu/renderer.py, methodrender_frame(), a debug log was added which is activated when LCDC is exactly0x80. This status indicates that the LCD is on but the background is off, which is a transient state the game uses while waiting for V-Blank.

Design decisions

  • INFO log level for interrupts: Usedlogger.info()so that the log be visible even without debug mode, since it is critical for diagnosis.
  • DEBUG log level for LCDC 0x80: Usedlogger.debug()because it is less critical and can generate many messages if the game remains in that state.
  • ⚡ symbol for interruptions: A distinctive visual symbol was added to facilitate the search in the logs.

Affected Files

  • src/cpu/core.py- Added informative log inhandle_interrupts()when an interrupt is dispatched
  • src/gpu/renderer.py- Added debug log when LCDC is 0x80

Tests and Verification

Execution mode:Manual with commercial ROM (Pokémon Red, contributed by the user, not distributed)

Command executed: python main.py pkmn.gb

Around:Windows 10, Python 3.13.5

Success Criterion:

  • See the messageCRITICAL: [TRAP LCDC] LCDC CHANGE ATTEMPT: 00 -> 80(confirmed in previous step)
  • See the message⚡ INTERRUPT DISPATCHED! Vector: 0040 | Previous PC: XXXX | Type: V-Blankafter LCDC change
  • If the interruption message does NOT appear, identify why (IME=False, IE not configured, IF not generated)

Expected observation:

  • If it appears⚡ INTERRUPT DISPATCHED! Vector: 0040: The CPU works correctly and the problem is graphical (renderer does not draw what the game expects after V-Blank)
  • If it does NOT appear: The bug is in the interruption system (IME, IE, IF, or V-Blank generation by the PPU)

Result: draft- Pending manual execution to check if the interruption log appears

Legal notes:The Pokémon Red ROM is user-contributed for local testing, it is not distributed or linked in the repository.

Sources consulted

Educational Integrity

What I Understand Now

  • Game initialization flow:Games turn on the LCD with BG off (0x80) and wait for V-Blank to configure the rest of the graphics. If V-Blank is not dispatched, the game freezes.
  • Conditions for interruptions:An interrupt is dispatched only if IME=True, IE has the bit set, and IF has the bit set. If any of these conditions fail, the interrupt is not processed.
  • Interrupt diagnosis:The interrupt dispatch log allows you to identify if the problem is in the CPU (does not accept interrupts) or in the PPU (does not generate V-Blank).

What remains to be confirmed

  • IME status in Pokémon:Check if IME is enabled when the game writes 0x80 to LCDC. If IME=False, interrupts will not be processed even if they are pending.
  • IE Settings:Check if the IE register (0xFFFF) has bit 0 (V-Blank) activated. If not enabled, the interrupt will not be processed even if IF is enabled.
  • V-Blank generation by PPU:Verify if the PPU is correctly generating the interrupt flag in IF (0xFF0F) when entering V-Blank mode.

Hypotheses and Assumptions

Main hypothesis:The game turns on the LCD (0x80), but the V-Blank interrupt is not dispatched because: (a) IME is disabled, (b) IE does not have bit 0 set, or (c) the PPU is not correctly generating the flag in IF.

Next verification step:Run the emulator and look for the log⚡ INTERRUPT DISPATCHED!. If it appears, the problem is graphic. If it does not appear, the problem is with the interrupt system.

Next Steps

  • [ ] Run the emulator with Pokémon Red and check if the log appears⚡ INTERRUPT DISPATCHED! Vector: 0040
  • [ ] If NOT displayed: Add additional logs to check the status of IME, IE, and IF when 0x80 is written to LCDC
  • [ ] If it appears: Investigate why the renderer does not draw correctly after V-Blank (check palette settings, VRAM, tilemap)
  • [ ] Verify that the PPU is correctly generating the V-Blank flag in IF when it enters V-Blank mode