This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Build Cleanup and Render Verification
Summary
The user reported that they were still seeing the gray screen with a red dot from step 116, despite having applied the changes from step 119. The diagnostic indicated that the C++ module had not been recompiled correctly and Python was loading an outdated version of the `viboy_core` binary. Implemented a full build cleanup (removal of compiled files and cache) and added LY diagnostics in the main loop to verify that the C++ PPU is working correctly.
Hardware Concept
Build Cache Issue in Cython/C++:When modifying C++ (`.cpp`/`.hpp`) or Cython (`.pyx`) code, Python may continue to load the old binary (`.pyd` on Windows, `.so` on Linux/Mac) if a full rebuild is not forced. This occurs because:
- Python cache:Python can keep the compiled module in memory even after closing the process
- Locked files:On Windows, `.pyd` files can get stuck if there are Python processes running
- Build dependencies:Cython only recompiles if it detects changes to the source files, but may fail if there are corrupt intermediate files
LY Registration (Current Line):The PPU's LY (Line Y) register indicates which line of the screen is currently being rendered. Values range from 0 to 153:
- 0-143:Visible lines (rendered in the framebuffer)
- 144-153:V-Blank (vertical delay period, does not render)
If LY moves (changes from 0 to 153 and back to 0), it means that the PPU is working correctly and advancing the lines. If LY remains at 0, it may indicate that the PPU is not receiving cycles or that the LCD is disabled.
Fountain:Pan Docs - LCD Timing, LY Register
Implementation
Three main actions were implemented:
1. Source Code Verification
Before recompiling, it was verified that the source code had actually changed:
- PPU Builder:Verified that `std::fill` initializes to white (`0xFFFFFFFF`), not gray (`0xFF808080`)
- `step()` method:Verified that there is NO line `framebuffer[0] = ...` (the red dot)
- `step()` method:Verified that `render_scanline()` is called when `mode_ == MODE_0_HBLANK && ly_< VISIBLE_LINES`
2. Complete Cleanup and Recompile
The following commands were run in order to remove any trace of the old binary:
#1. Clean build with setuptools
python setup.py clean --all
#2. Delete build folder manually (if it exists)
Remove-Item -Recurse -Force build
#3. Delete old .pyd files (if they exist)
Remove-Item -Force viboy_core*.pyd
#4. Recompile from scratch
python setup.py build_ext --inplace
Note on locked files:On Windows, if the `.pyd` file is blocked by a running Python process, all Python processes will need to be closed before it can be killed. The new binary will be generated in `build/lib.win-amd64-cpython-313/` and it will be copied to the root directory when the old file is freed.
3. Diagnosis of LY in the Main Loop
Added diagnostic logging to `src/viboy.py` to verify that the C++ PPU is working:
#6. Heartbeat: LY diagnosis every second (60 frames ≈ 1 second at 60 FPS)
if frame_count % 60 == 0 and self._ppu is not None:
# Get LY from the PPU (supports Python and C++)
ly_value = 0
try:
if self._use_cpp:
# PPU C++: use .ly property (defined in ppu.pyx)
ly_value = self._ppu.ly
else:
# PPU Python: use get_ly() method
ly_value = self._ppu.get_ly()
exceptAttributeError:
# Fallback if the method/property does not exist
ly_value = 0
# Diagnostic logging: If LY moves (0-153), PPU is alive
logger.info(f"💓 Heartbeat ... LY_C++={ly_value} (PPU {'alive' if ly_value > 0 or frame_count > 60 else 'initializing'})")
This log is displayed every second (every 60 frames) when run with `--verbose`:
python main.py roms/tetris.gb --verbose
Affected Files
src/viboy.py- Added LY diagnostic logging in the main loop (`run()` method)setup.py- Not modified, but used for cleanup and recompilationsrc/core/cpp/PPU.cpp- Verified (not modified, it was already correct from step 119)
Tests and Verification
Verification command:
python main.py roms/tetris.gb --verbose
Expected result:
- If the rebuild was successful:The screen must be white (or black) without a red dot. If you can't see the game, it's because `render_scanline` doesn't look good, but at least we're on the new version.
- If you see the game:Victory! It was just a build cache issue.
- LY Logging:Every second, a message should appear like `💓 Heartbeat ... LY_C++=XX` where XX changes from 0 to 153. If LY moves, the PPU is alive.
Compiled C++ module validation:The new binary is generated in `build/lib.win-amd64-cpython-313/viboy_core.cp313-win_amd64.pyd` and is copied to the root directory when the old file is released.
Sources consulted
- Bread Docs:LCD Timing
- Bread Docs:LCDC Register (LY)
- Cython Documentation:Compilation
Educational Integrity
What I Understand Now
- Build cache:Cython and Python can keep old binaries in memory or on disk, causing changes to C++ code not to be reflected until a full rebuild is forced.
- Diagnosis of PPU:The LY log is an excellent indicator of whether the PPU is working. If LY moves (0-153), the PPU is alive and processing lines.
- Files locked in Windows:`.pyd` files can become locked if there are Python processes running, preventing them from being deleted until all processes are closed.
What remains to be confirmed
- Actual rendering:If after the rebuild the screen is white but the game is not visible, it may indicate that `render_scanline()` is not correctly painting the tiles from VRAM.
- Register initialization:Verify that the PPU registers (LCDC, BGP, SCX, SCY) are initialized correctly when a ROM is loaded.
Hypotheses and Assumptions
Assumption:The reported problem (gray screen with red dot) was caused by an old binary loaded in memory or on disk. Full rebuild should resolve the issue if the source code is correct.
Next Steps
- [ ] Verify that the full rebuild resolves the gray screen issue
- [ ] If the screen is white but the game is not visible, investigate why `render_scanline()` does not paint correctly
- [ ] Verify that the LY log shows changing values (0-153) when run with `--verbose`
- [ ] If everything works, proceed with the Audio (APU) implementation in the next phase