This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Tiles Detection and Rendering Fix
Summary
This step fixes the real tile check to check the ENTIRE VRAM range (0x8000-0x97FF) instead of just the first 2048 bytes, investigates why the tilemap doesn't point to the actual tiles being loaded, and improves the rendering logic to use real tiles when they are available.
Additional monitors were implemented to track changes to the tilemap, correspondence analysis between tilemap and real tiles, verification of tile address calculation, and improved empty tile detection to verify the entire tile (16 bytes) before considering it empty.
Hardware Concept
VRAM Full (0x8000-0x97FF)
VRAM has a total range of 6144 bytes (384 tiles × 16 bytes). The addressing can be:
- Unsigned addressing (base 0x8000): Tiles 0-255 in 0x8000-0x8FFF
- Signed addressing (base 0x9000): Tiles -128 to 127 in 0x8800-0x97FF
In signed addressing:
- Tile 0 is at 0x9000
- Tile -1 (0xFF) is at 0x8FF0
- Tile -128 (0x80) is at 0x8800
- Tile 127 (0x7F) is at 0x9FF0
Fountain: Pan Docs - "Tile Data"
Tilemap and Correspondence with Tiles
The tilemap contains tile IDs that point to tiles in VRAM. If the tilemap is not updated after loading tiles, it points to empty tiles. Games should update the tilemap to point to the actual loaded tiles. If the tilemap points to empty tiles, a blank or test pattern is rendered.
Fountain: Pan Docs - "Tile Map"
Detection of Empty Tiles
A tile is completely empty if all 8 lines (16 bytes) are 0x0000. Some legitimate tiles may have lines with 0x0000 (transparent). Test pattern should only be used if the ENTIRE tile is empty.
Fountain: Pan Docs - "Tile Data"
Implementation
1. Real Tiles Verification Fix
Verification was changed inPPU::render_scanline()to check ALL VRAM (0x8000-0x97FF = 6144 bytes) instead of just the first 2048 bytes. This covers both signed and unsigned addressing.
The threshold was adjusted from 100 non-zero bytes to 500 non-zero bytes (approx. 31 full tiles) because we now check 3x more bytes.
// Check ALL VRAM (0x8000-0x97FF = 6144 bytes = 384 tiles)
for (uint16_t i = 0; i< 6144; i++) {
uint8_t byte = mmu_->read(0x8000 + i);
if (byte != 0x00) {
non_zero_bytes++;
}
}
bool has_tiles_now = (non_zero_bytes > 500);
2. Monitor Changes in Tilemap
The monitor was improved inMMU::write()to detect when the game updates the tilemap. The monitor logs the first 100 changes and when there are significant changes (from 0x00 to a non-zero value).
if ((addr >= 0x9800 && addr<= 0x9BFF) || (addr >= 0x9C00 && addr<= 0x9FFF)) {
static int tilemap_update_count = 0;
static uint8_t last_tilemap_value = 0xFF;
if (tilemap_update_count < 100 || (value != 0x00 && last_tilemap_value == 0x00)) {
printf("[TILEMAP-UPDATE] PC:0x%04X | Addr:0x%04X | TileID:0x%02X\n",
debug_current_pc, addr, value);
tilemap_update_count++;
}
}
3. Tilemap-Tiles Correspondence Analysis
Added analysis inPPU::render_scanline()which checks if the tilemap points to tiles with real data when tiles are detected in VRAM. The analysis checks the first 32 tile IDs of the tilemap and counts how many point to tiles with data and how many point to empty tiles.
if (vram_has_tiles && ly_ == 0 && tilemap_tiles_analysis_count< 5 && (frame_counter_ % 60 == 0)) {
int tiles_pointing_to_real_data = 0;
int tiles_pointing_to_empty = 0;
for (int i = 0; i < 32; i++) {
uint8_t tile_id = mmu_->read(tile_map_base + i);
// Calculate tile address according to the addressing
// Check if the tile has data
if (tile_byte1 != 0x00 || tile_byte2 != 0x00) {
tiles_pointing_to_real_data++;
} else {
tiles_pointing_to_empty++;
}
}
}
4. Tile Address Calculation Verification
Added a check that confirms that the tile address calculation is correct for signed/unsigned addressing. The verification shows examples of tile IDs and calculated addresses.
if (signed_addressing) {
int8_t signed_id = static_cast(sample_tile_id);
calculated_addr = tile_data_base + ((int8_t)sample_tile_id * 16);
printf("[PPU-TILE-ADDR-VERIFY] TileID: 0x%02X (signed: %d) | Base: 0x%04X | Calculated: 0x%04X\n",
sample_tile_id, signed_id, tile_data_base, calculated_addr);
} else {
calculated_addr = tile_data_base + (sample_tile_id * 16);
printf("[PPU-TILE-ADDR-VERIFY] TileID: 0x%02X (unsigned) | Base: 0x%04X | Calculated: 0x%04X\n",
sample_tile_id, tile_data_base, calculated_addr);
}
5. Empty Tiles Detection Improvement
Improved rendering logic to check the ENTIRE tile (16 bytes) before considering it empty. This prevents legitimate tiles with some lines at 0x0000 from being considered empty.
bool tile_is_empty = true;
// Check if the ENTIRE tile is empty (all 8 lines = 16 bytes)
for (uint8_t line_check = 0; line_check< 8; line_check++) {
uint16_t check_addr = tile_addr + (line_check * 2);
uint8_t check_byte1 = mmu_->read(check_addr);
uint8_t check_byte2 = mmu_->read(check_addr + 1);
if (check_byte1 != 0x00 || check_byte2 != 0x00) {
tile_is_empty = false;
break;
}
}
Tests and Verification
Compilation
The C++ module was successfully recompiled without errors (only minor formatting warnings).
$python3 setup.py build_ext --inplace
running build_ext
building 'viboy_core' extension
...
copying build/lib.linux-x86_64-cpython-312/viboy_core.cpython-312-x86_64-linux-gnu.so ->
Testing with ROMs
Tests were run with the 3 ROMs (pkmn.gb, tetris.gb, mario.gbc) for 2.5 minutes each. The logs show:
- ✅ Detection of completely empty tiles working correctly
- ✅ Monitor changes in tilemap capturing updates
- ✅ Verification of real tiles by checking ALL VRAM
C++ Compiled Module Validation
The compiled module loads correctly and the C++ functions execute without errors. The implemented monitors and verifications are active and generate logs when the conditions are met.
Results
Implemented Fixes
- ✅ Real tile check now checks ALL VRAM (6144 bytes) instead of just 2048 bytes
- ✅ Improved tilemap change monitor to capture updates
- ✅ tilemap-tiles correspondence analysis implemented
- ✅ Tile address calculation check implemented
- ✅ Improved empty tile detection to check the entire tile before considering it empty
Findings
- Games write to tilemap (updates detected on PC:0x129F for mario.gbc)
- Empty tile detection works correctly (completely empty tiles are detected at 0x8000)
- The code compiles and runs without errors
Next Steps
If the rendering works correctly:
- Step 0326: Final verification of controls and compatibility
- Step 0327: Final evaluation of the strategic plan
If the problem persists:
- Step 0326: Deeper tilemap analysis and workaround
- Investigate if there are problems in synchronization or timing