This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Hardware Fix: LCD Off and LY Reset
Summary
The Step 0225 autopsy revealed incorrect critical behavior: the PPU kept incrementing `LY` (value 97) even though the LCD was turned off (`LCDC Bit 7 = 0`). According to the hardware specification (Pan Docs), when the LCD is disabled, the PPU should stop immediately and the `LY` register should be reset and kept at 0. This fix corrects the implementation to ensure that the internal counters (`ly_`, `clock_`, `mode_`) are reset correctly when the LCD is off, allowing games to correctly time the screen reset.
Hardware Concept
The recordLCDC (LCD Control)in the direction0xFF40controls the state of the Game Boy screen. Hebit 7of this record is especially critical: when it is in0, the LCD is completely disabled; when it is in1, the LCD is on and the PPU operates normally.
According to the official documentation (Pan Docs), when the LCD is disabled (LCDC bit 7 = 0), the following happens:
- The PPU stops immediately:No scan lines are processed, no pixels are rendered.
- The LY register is reset to 0:The value of LY (0xFF44) is set to 0 and remains fixed at that value while the LCD is disabled.
- The internal clock stops:The PPU cycle counters are reset.
This behavior is critical because games use this feature to time the screen reset. A typical pattern is:
- The game turns off the LCD (writes 0x00 or similar to LCDC, with bit 7 = 0)
- The game copies graphic data to VRAM (tiles, maps, etc.)
- The game turns the LCD back on (writes to LCDC with bit 7 = 1)
- The game assumes LY is 0 when the LCD turns on
If LY is not at 0 when the LCD turns back on, the game may get confused about which line it is on and fail to render correctly. In our autopsy, we detected `LY=97` with `LCDC=0x08` (bit 7 off), which indicated that the PPU was still "ghost running" even though it should be completely stopped.
Fountain:Pan Docs - "LCD Control Register (LCDC)" and "LCD Y-Coordinate (LY)"
Implementation
We modified the `PPU::step()` method so that when it detects that the LCD is off, it not only returns raw, but also explicitly resets the PPU's internal counters.
Modified components
- PPU.cpp:Updated LCD off check block in `step()` to reset `ly_ = 0`, `clock_ = 0` and `mode_ = MODE_0_HBLANK` before returning.
Change implemented
Instead of just returning when the LCD is off:
if (!lcd_enabled) {
// LCD off: PPU stopped, LY stays at 0
// We do not accumulate cycles or advance lines
return;
}
Now we explicitly reset the counters:
// --- Step 0227: FIX LCD DISABLE BEHAVIOR ---
// Pan Docs: When LCD is disabled (LCDC bit 7 = 0), the PPU stops immediately
// and the LY register is reset to 0 and remains fixed at 0. The internal clock
// is also reset. This is critical for proper synchronization when the game
// turns the LCD back on, as it expects LY to be 0.
if (!lcd_enabled) {
// Reset counters and status when LCD is off
ly_ = 0;
clock_ = 0;
mode_ = MODE_0_HBLANK; // H-Blank is the safest mode when turned off
// We do not accumulate cycles or advance lines
// PPU is completely stopped until LCD turns on again
return;
}
// -------------------------------------------
Design decisions
We chose to reset `mode_` to `MODE_0_HBLANK` (H-Blank) because it is the most "neutral" mode when the PPU is off. In real hardware, when the LCD turns off, the mode can technically be any, but H-Blank is the safest and most common. The important thing is that `ly_` and `clock_` are reset to 0.
Affected Files
src/core/cpp/PPU.cpp- Modified `step()` to reset counters when LCD is off
Tests and Verification
To verify that the fix works correctly:
- Recompile:
.\rebuild_cpp.ps1eitherpython setup.py build_ext --inplace - Execute:
python main.py roms/tetris.gb - Verify in the autopsy:When the LCD is off (LCDC bit 7 = 0), the LY value must be 0, not 97 or any other value.
Expected result:The autopsy should display `LY: 0` when `LCDC: 0x08` (or any value with bit 7 = 0), indicating that the PPU correctly honors the LCD off state.
Compiled C++ module validation:This fix directly affects the behavior of the PPU C++ module. Verification is performed by system autopsy, which directly reads the internal state of the compiled PPU.
Sources consulted
- Bread Docs:LCD Control Register (LCDC)
- Bread Docs:LCD Status Register (STAT)
- Bread Docs:LCD Y-Coordinate (LY)
Educational Integrity
What I Understand Now
- LCD Behavior Off:When bit 7 of LCDC is 0, the PPU stops completely and LY is reset to 0. This is not just an optimization, but a critical behavior of the hardware that games use for synchronization.
- Importance of LY=0:Games assume that when they turn on the LCD, LY starts at 0. If LY has a different value, it can cause rendering or timing issues.
- Counter Reset:It is not enough to not process cycles when the LCD is off; we must actively reset the counters to reflect the real state of the hardware.
What remains to be confirmed
- STAT register behavior:When the LCD is off, what value should the STAT register have? The documentation is not completely clear on this, but in our case, when resetting the mode to H-Blank, the STAT should reflect that mode.
- Effect on other records:Are there other registers or states of the PPU that need to be reset when the LCD turns off? For now, we only reset LY, clock and mode, but there could be more.
Hypotheses and Assumptions
We assume that resetting `mode_` to `MODE_0_HBLANK` is the correct behavior when the LCD is off. The documentation does not explicitly specify which mode should be reported when the LCD is off, but H-Blank is the safest and most neutral mode. If future tests reveal that this causes problems, we may need to adjust this decision.
Next Steps
- [ ] Run the emulator with the fix applied and verify that LY=0 when LCD is off
- [ ] Check if the game can now restart the screen correctly
- [ ] If there are still problems, investigate the behavior of the STAT register when LCD is off