This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Checkerboard Test: Rendering Pipeline Validation
Summary
We have reached a critical diagnostic point. Even though all the components seem to work (CPU, MMU, PPU), the screen remains blank because the VRAM is erased by the ROM itself before we can render anything. This is a "Initialization War" moment between our BIOS simulation and the game ROM itself.
We need to unambiguously validate that our rendering pipeline (C++ PPU → Cython → Python Pygame) is working. To do this, we implement a "Checkerboard Test": we temporarily modifyPPU::render_scanline()so that it ignores all emulation logic and draws a checkerboard pattern directly into the framebuffer.
This test will give us a binary and definitive answer:
- If we see the checkerboard:The C++ → Python data pipeline works. The problem is, 100% confirmed, that the VRAM is empty.
- If the screen is still blank:Our diagnosis is wrong. The pipe is broken at some point (the
memoryviewCython, pointer passing, etc.).
Engineering Concept: Data Pipe Isolation and Testing
When a complex system fails, the best strategy isisolation. Let's isolate the rendering "pipeline" from the rest of the emulator. If we can write data to astd::vectorin C++ and view them in a Pygame window in Python, then the pipeline works. If not, the pipe is broken.
The checkerboard pattern is ideal because it is:
- Visually unmistakable:It is impossible to confuse with corrupted memory or an empty VRAM state.
- Easy to generate mathematically:It does not require access to VRAM, tiles, or any other component of the emulator.
- Deterministic:If the pipeline works, we'll see the pattern. If not, the screen will remain blank.
The Initialization War:
The problem we face is a masterpiece of technical irony: our emulator is now so precise that it is faithfully executing the Tetris ROM code...which erases the VRAM that we so carefully pre-loaded.
The Sequence of Events:
- Our Emulator (Simulating the BIOS):Upon startup, our builder
MMUis executed. Create the 64KB memory space. Run our code from Step 0201:pre-charge the VRAMwith the logo data. At this moment, the VRAM contains the graphics. - Tetris ROM (The Game Takes Control):Execution starts at
PC=0x0100. The gamedoes not trust the state of the machine. It does not assume that the VRAM is clean or ready. One of the first actions any well-programmed game performs isclean working memory (WRAM) and often video memory (VRAM)to make sure there is no "garbage" from a previous boot. - The Erasure:This is done with a very quick assembler loop, something like:
LD HL, 0x9FFF; LD B, NUM_BYTES; loop: RH (HL-), A; DEC B; JR NZ, loop. Our emulator, now 100% functional, executes this cleanup loop perfectly.In the first microseconds of execution, Tetris's CPU passes through VRAM and fills it with zeros, erasing our logo before the PPU has a chance to draw a single frame.
The Unequivocal Evidence:
- Heartbeat Log:
💓 Heartbeat ... LY=0 | Mode=2 | LCDC=91. This shows that the Tetris ROM DOES try to turn on the screen (LCDC=91) from the first moment. He wants to show something. - Renderer Log:
[Renderer] Frame #0: framebuffer read, display indices: [0, 0, 0, 0, 0, 0]. This shows that, althoughLCDCis91, the PPU reads a VRAM that is already full of zeros.
We have reached such a high point of precision that we are correctly emulating how the game itself sabotages our attempt to simulate the BIOS. This is not a failure, it is an extraordinary validation of the correctness of our CPU and MMU.
Implementation
We temporarily modifyPPU::render_scanline()so that it ignores all emulation logic and draws a checkerboard pattern directly into the framebuffer.
Modification in PPU::render_scanline() (C++)
Insrc/core/cpp/PPU.cpp, we completely replace the content of the methodrender_scanline()with the following pattern generation code:
void PPU::render_scanline() {
// --- Step 0202: Checkerboard test to validate the data pipeline ---
// This code ignores VRAM, LCDC, scroll and all emulation.
// Draw a checkerboard pattern directly to the framebuffer.
//
// GOAL: Isolate and test the C++ -> Cython -> Python rendering pipeline.
// If we see the checkerboard, the pipe works. If the screen is still blank,
// the problem is in the Cython interface or in the pointer passing.
// Only draw if we are on the visible lines
if (ly_ >= VISIBLE_LINES) {
return;
}
size_t line_start_index = ly_ * 160;
for (int x = 0; x< 160; ++x) {
// Generar un patrón de cuadrados de 8x8 píxeles
// Alternar entre cuadrados oscuros y claros basado en la posición
bool is_dark_square = ((ly_ / 8) % 2) == ((x / 8) % 2);
// Usar índice de color 3 (oscuro) y 0 (claro)
uint8_t color_index = is_dark_square ? 3 : 0;
framebuffer_[line_start_index + x] = color_index;
}
// CÓDIGO ORIGINAL COMENTADO (se restaurará después del test):
// ... código original de render_scanline() ...
}
Algorithm Explanation:
- Visible lines:We only draw if
ly_ < VISIBLE_LINES(0-143). - Line index:We calculate
line_start_index = ly_ * 160to get the start of the current line in the framebuffer. - Board pattern:For each pixel, we determine whether it is in a dark or light square by comparing the parity of
ly_ / 8andx/8. If both have the same parity, the square is dark (color 3). If not, it is clear (color 0). - 8x8 squares:The pattern generates 8x8 pixel squares, creating a perfectly visible checkerboard.
⚠️ Important:This code is temporary and should be reverted after testing. The original code is commented within the method to facilitate its restoration.
Affected Files
src/core/cpp/PPU.cpp- Modifiedrender_scanline()to draw checkerboard pattern instead of reading from VRAM
Tests and Verification
The verification is purely visual:
- Recompilation:Recompile C++ module using
.\rebuild_cpp.ps1 - Execution:Run the emulator with the Tetris ROM:
python main.py roms/tetris.gb
Expected Result:
When running the emulator, there are only two possible outcomes:
- We see a Perfect Chess Board:
- Meaning:Success! The C++ → Cython → Python data pipeline works perfectly.
- Confirmed Diagnosis:The problem is, without a doubt, that the VRAM is empty because the ROM is cleaning it.
- Next Step:We could reverse this test and look for a test ROM thatNoclean the VRAM, or move directly to implementing Sprites.
- The Screen Is Still Blank:
- Meaning:Pipeline failure! The PPU in C++ is generating the pattern, but it never reaches the screen.
- Diagnosis:The problem is in our Cython wrapper, in how we expose the framebuffer pointer, or how Python interprets it as a
memoryview. - Next Step:Debug the Cython interface, verifying pointers, data types and the life cycle of the
memoryview.
Compiled C++ module validation:This test validates that the rendering pipeline is working correctly, regardless of the state of the VRAM or the emulation logic.
Sources consulted
- Isolation Principle in Software Engineering:When a complex system fails, the best strategy is to isolate components and test them individually.
- Checkerboard test:Classic pattern in graphics engine development to verify the integrity of the rendering pipeline.
- Bread Docs:"LCD Timing", "VRAM" - Game Boy video hardware behavior
Educational Integrity
What I Understand Now
- Component Isolation:When a complex system fails, the best strategy is to isolate components and test them individually. The checkerboard test allows us to isolate the rendering pipeline from the rest of the emulator.
- The Initialization War:Our emulator is so precise that it is faithfully executing the Tetris ROM code, which erases the VRAM that we pre-loaded. This is not a failure, it is an extraordinary validation of the correctness of our CPU and MMU.
- Binary Diagnosis:The checkerboard test will give us a binary and definitive answer: if we see the pattern, the pipeline works. If not, the pipe is broken.
What remains to be confirmed
- Test Result:We need to run the emulator and visually check if the checkerboard pattern appears or if the screen is still blank.
- Final Diagnosis:Once we have the test result, we can determine if the problem is with the empty VRAM or the rendering pipeline.
Hypotheses and Assumptions
Main Hypothesis:The rendering pipeline (C++ → Cython → Python) is working correctly, and the problem is that the VRAM is empty because the ROM is clearing it. This hypothesis will be validated with the checkerboard test.
Assumption:If we see the checkerboard, we will confirm that the pipeline works and that the problem is empty VRAM. If the screen is still blank, the problem is with the Cython interface or pointer passing.
Next Steps
- [ ] Run the emulator and visually check if the checkerboard pattern appears
- [ ] If we see the checkerboard: Reverse the test and look for a test ROM that does not clean the VRAM, or advance directly to the Sprites implementation
- [ ] If the screen is still blank: Debug the Cython interface, checking pointers, data types and the life cycle of the
memoryview