This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
The Probe at Pixel Zero
Summary
"VRAM Flood" (Step 0208) and "Forcing Black" (Step 0209) have failed, indicating that the address validation logic inrender_scanlineis systematically rejecting VRAM accesses, diverting the flow to the blockelse(white). Mathematically this should not happen, so we should see the values in real time.
Aim:InstrumentPPU::render_scanline()withprintfto display the calculation variables (LCDC, addresses, Tile ID) exclusively for the pixel (0,0) of the frame. This will give us an exact x-ray of why the address is considered invalid without flooding the console with thousands of log lines.
Hardware Concept: Surgical Diagnosis
When a system fails systematically, we need accurate data, not assumptions. The problem we face is that the validation conditionif (tile_line_addr >= 0x8000 && tile_line_addr<= 0x9FFE)is systematically failing, leading execution to blockelsewhich writes color 0 (white) to the framebuffer.
The mathematical problem:Anytile_idvalid (0-255) should generate a valid address within VRAM (0x8000-0x9FFF). If this is not happening, there is an error in:
- Address calculation:He
tile_map_addrmay be out of range, reading garbage from the tilemap. - Tiles addressing:The signed/unsigned mode may be calculating incorrect addresses.
- Type Overflow:A
uint16_tmay be overflowing or aint8_tmay be being interpreted incorrectly. - Incorrect validation:Although we corrected the condition in Step 0210, there may be another problem that we missed.
The surgical solution:Instead of printing thousands of log lines for each pixel, we instrument the code to print the calculation valuesonly once per frame, specifically whenly_ == 0andx == 0(the first pixel of the first frame). This will give us an exact snapshot of the internal state of the PPU at the critical moment of rendering.
Fountain:Pan Docs - "PPU Rendering", "Tile Addressing", "VRAM Access"
Implementation
Added a diagnostic block inPPU::render_scanline()inside the filesrc/core/cpp/PPU.cpp, just before the VRAM validation condition.
Header Inclusion
was added#include <cstdio>at the beginning of the file to enableprintf.
#include "PPU.hpp"
#include "MMU.hpp"
#include <algorithm>
#include <cstdio> // Step 0211: For diagnostic printf
Diagnostic Block
The following code block was added right after the calculation oftile_line_addrand before the validation condition:
// --- Step 0211: DIAGNOSTIC PROBE (Pixel 0.0) ---
// Print the internal state only once per frame (at startup)
// This allows us to see the exact values that the PPU is calculating
// without flooding the console with thousands of log lines
if (ly_ == 0 && x == 0) {
printf("--- [PPU DIAGNOSTIC FRAME START] ---\n");
printf("LCDC: 0x%02X | SCX: 0x%02X | SCY: 0x%02X\n", lcdc, scx, scy);
printf("MapBase: 0x%04X | MapAddr: 0x%04X | TileID: 0x%02X\n", tile_map_base, tile_map_addr, tile_id);
printf("DataBase: 0x%04X | Signed: %d\n", tile_data_base, signed_addressing ? 1 : 0);
printf("CalcTileAddr: 0x%04X | LineAddr: 0x%04X\n", tile_addr, tile_line_addr);
bool valid = (tile_line_addr >= 0x8000 && tile_line_addr<= 0x9FFE);
printf("VALID CHECK: %s\n", valid ? "PASS" : "FAIL");
printf("------------------------------------\n");
}
// -------------------------------------------------
Diagnosed Variables
The diagnostic block prints the following critical variables:
- LCDC:LCD control register (0xFF40), includes enable and addressing mode bits.
- SCX/SCY:Horizontal and vertical scroll registers (0xFF43/0xFF42).
- MapBase:Base address of the tile map (0x9800 or 0x9C00 according to LCDC bit 3).
- MapAddr:Calculated address in the tile map for the current tile.
- TileID:ID of the tile read from the map (0-255).
- Database:Base address of tile data (0x8000 or 0x9000 according to LCDC bit 4). Signed:Indicates whether signed (1) or unsigned (0) addressing is used.
- CalcTileAddr:Base address of the tile calculated (before adding the line offset).
- LineAddr:End address of the tile line (tile_addr + line_in_tile * 2).
- VALID CHECK:Validation result (PASS if in range, FAIL if not).
Affected Files
src/core/cpp/PPU.cpp- Added#include <cstdio>and diagnostic block inrender_scanline()(lines 347-361)
Tests and Verification
Compilation:The code was compiled successfully withpython setup.py build_ext --inplace. No compilation errors were introduced.
Compiled C++ module validation:The Cython extension has been successfully generated and is ready for runtime testing.
Expected test:When running the emulator withpython main.py roms/tetris.gb, we should see in the console a diagnostic block showing the exact values calculated for the pixel (0,0) of the first frame. This block will appear once per frame (60 times per second), but will only display the values of the first pixel, avoiding flooding the console.
Analysis of expected results:With this data, we will be able to identify exactly where the error is:
- If TileID is strange:Maybe we read garbage from the tile map (MapAddr out of range).
- If MapAddr is out of range:Error in calculating position in the tile map.
- If LineAddr is 0 or huge:Overflow error or incorrect data types.
- If VALID CHECK says FAIL:We will see why the exact number fails the condition, allowing us to correct the problem in the next step.
Sources consulted
- Pan Docs: "PPU Rendering" - Scan Line Rendering Process
- Pan Docs: "Tile Addressing" - Calculation of tile addresses (signed vs unsigned)
- Pan Docs: "VRAM Access" - VRAM Memory Range Validation
Educational Integrity
What I Understand Now
- Surgical diagnosis:When a problem is systematic, it is better to instrument your code to see exact values at runtime than to guess based on assumptions.
- Log output control:Printing logs for each pixel (160x144 = 23,040 pixels per frame) would overwhelm the console. It is best to limit the output to specific cases (such as the first pixel of the first frame).
- Hypothesis validation:Although mathematically any tile_id should generate a valid address, we need to verify that the intermediate calculations are correct.
What remains to be confirmed
- Actual values:View the exact values that the PPU is calculating at runtime to identify where the error is.
- Origin of the problem:Determine if the problem is in calculating addresses, reading the tilemap, or validating ranges.
Hypotheses and Assumptions
Main hypothesis:The validation condition is failing because some of the intermediate variables (MapAddr, TileID, CalcTileAddr, LineAddr) have an incorrect value that we cannot see without instrumenting the code.
Assumption:Once we see the exact values, the correction will be obvious. Could be:
- An error in MapAddr calculation (outside the range 0x9800-0x9FFF)
- An invalid TileID read from the map (uninitialized memory garbage)
- An error in the calculation of signed/unsigned addresses
- A data type overflow (uint16_t, int8_t)
Next Steps
- [ ] Recompile the C++ module with
.\rebuild_cpp.ps1eitherpython setup.py build_ext --inplace - [ ] Run the emulator:
python main.py roms/tetris.gb - [ ] Analyze the console: Find the block
[PPU DIAGNOSTIC FRAME START] - [ ] Identify the error: Compare the printed values with the expected values according to the documentation
- [ ] Apply correction: Once the problem is identified, correct it in the next step