This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Real Verification + "True" Tests (Post-Fix 0464)
Summary
Fixed critical issues identified in Step 0464: tests that "passed" but did not actually test base tilemap or scroll (only checking MMU.write), use ofmmu.read()ratherread_raw()inrom_smoke_0442.py(can "lie" due to access restrictions), log[ENV]contaminating runtime, and incorrect frame stepping (iterated instead of accumulating cycles). Fixed tests to use real asserts from framebuffer indices, changed toread_raw()for tilemap stats, gated instrumentation was added[IO-SCROLL-WRITE], and the log was cleaned[ENV].
Hardware Concept
Identified problem: The Step 0464 tests gave "false security" because:
- They didn't check the actual framebuffer (they only read VRAM with
mmu.read()) - They did not prove that the PPU selected the correct tilemap according to LCDC bit3
- They did not verify that the scroll (SCX/SCY) was applied correctly to the framebuffer
VRAM access restrictions: During certain PPU modes (especially Mode 3), read VRAM viaread()may return locked values or 0xFF. That's why it existsread_raw()that bypass these restrictions for reliable diagnosis.
Framebuffer indices: The PPU renders tiles to the framebuffer as color indices (0-3), not RGB. These indexes can be read withget_framebuffer_indices()to verify that the rendering is correct.
Reference: Pan Docs - VRAM Access Restrictions, PPU Modes. Step 0457 - Debug API for tests.
Implementation
The fix was implemented in five phases:
Phase A: Correct Tests - Framebuffer Real Asserts
Rewrote the tests to actually test base tilemap and scroll using framebuffer indices:
- Added helper
run_one_frame()that accumulates cycles correctly (does not iterate fixed 70224 times) - Rewritten tests to use
ppu.get_framebuffer_indices()with real asserts on the rendered pixels - Tests verify that the rendered pattern corresponds to the selected base tilemap and the applied scroll
Known issue: The tests fail because the framebuffer returns 0 instead of the expected indices. Requires further investigation to determine if it is a rendering, timing, or PPU configuration issue.
Phase B: Fix rom_smoke_0442.py – Use RAW VRAM
Changed sampling of tilemap/tile IDs to useread_raw()to avoid "lies" due to access restrictions:
# BEFORE (can "lie" due to access restrictions in Mode 3):
tilemap_nz_9800 = 0
for addr in range(0x9800, 0x9C00):
if mmu.read(addr) != 0:
tilemap_nz_9800 += 1
# AFTER (RAW, no restrictions):
tilemap_nz_9800 = 0
for addr in range(0x9800, 0x9C00):
if mmu.read_raw(addr) != 0: # Use read_raw()
tilemap_nz_9800 += 1
Phase C: Gated Instrumentation - IO Write Trace
Added gated write logging to SCX/SCY to demonstrate if "strips going down" are due to game writes or a bug:
// In MMU::write(), when addr == 0xFF42 or 0xFF43:
if ((debug_ppu || debug_io) && (addr == 0xFF42 || addr == 0xFF43)) {
uint8_t old_val = memory_[addr];
uint8_t new_val = value;
uint8_t ly = ppu_->get_ly();
const char* reg_name = (addr == 0xFF42) ? "SCY": "SCX";
printf("[IO-SCROLL-WRITE] addr=0x%04X %s old=%d new=%d LY=%d\n",
addr, reg_name, old_val, new_val, ly);
}
It only appears whenVIBOY_DEBUG_PPU=1eitherVIBOY_DEBUG_IO=1.
Phase D: Cleanup - Delete/Crack [ENV] Log
The log was deleted[ENV]always-onviboy.pyand moved totools/rom_smoke_0442.py(only in tools, not in runtime):
# In tools/rom_smoke_0442.py, at the start of run():
import you
env_vars = [
'VIBOY_DEBUG_INJECTION',
'VIBOY_FORCE_BGP',
'VIBOY_AUTOPRESS',
'VIBOY_FRAMEBUFFER_TRACE',
'VIBOY_DEBUG_UI',
'VIBOY_DEBUG_PPU',
'VIBOY_DEBUG_IO'
]
env_status = []
for var in env_vars:
value = os.environ.get(var, '0')
env_status.append(f"{var}={value}")
print(f"[ENV] {' '.join(env_status)}")
Phase E: Actual Validation (Pending)
The actual validation with grid UI and table by ROM will be done in a later step, once the tests work correctly.
Affected Files
tests/test_bg_tilemap_base_and_scroll_0464.py- Rewritten tests with real framebuffer asserts and helperrun_one_frame()that accumulates cycles correctlytools/rom_smoke_0442.py- Changed toread_raw()for tilemap stats (lines 380-393) and added log[ENV]at the beginning ofrun()src/core/cpp/MMU.cpp- Added gated instrumentation[IO-SCROLL-WRITE](lines 2538-2560)src/viboy.py- Deleted log[ENV]always-on (lines 677-691, 705-721)
Tests and Verification
Command executed: pytest -q tests/test_bg_tilemap_base_and_scroll_0464.py
Result: ⚠️ Tests fail - framebuffer returns 0 instead of expected indices
Known issue: The framebuffer is not rendering correctly in tests. Possible causes:
- Framebuffer not updating after a frame
- Incorrect tile data pattern
- Rendering conditions not met (LCDC bit 0, timing, etc.)
Test Code:
def run_one_frame(self):
"""Helper: Execute exactly 70224 cycles (not 70224 iterations)."""
cycles_per_frame = 70224
cycles_accumulated = 0
while cycles_accumulated< cycles_per_frame:
cycles = self.cpu.step()
cycles_accumulated += cycles
self.timer.step(cycles)
self.ppu.step(cycles)
def test_tilemap_base_select_9800(self):
"""Test 1: tilemap base select (0x9800 vs 0x9C00) - Caso 0x9800."""
# Crear tile 0 con patrón P0: [0,1,2,3,0,1,2,3] por línea
for line in range(8):
byte1 = 0x55 # Bits bajos: 0,1,0,1,0,1,0,1
byte2 = 0x33 # Bits altos: 0,0,1,1,0,0,1,1
self.mmu.write(0x8000 + (line * 2), byte1)
self.mmu.write(0x8000 + (line * 2) + 1, byte2)
# Poner en 0x9800: tile IDs = 0
for i in range(32 * 32):
self.mmu.write(0x9800 + i, 0x00)
# Setear LCDC bit3=0 (tilemap base 0x9800)
self.mmu.write(0xFF40, 0x91) # Bit3=0 → 0x9800
# Correr 1 frame
self.run_one_frame()
# Verificar framebuffer: fila0 px[0..7] == P0
indices = self.ppu.get_framebuffer_indices()
expected_p0 = [0, 1, 2, 3, 0, 1, 2, 3]
for i in range(8):
actual_idx = indices[row0_start + i] & 0x03
assert actual_idx == expected_p0[i]
Native Validation: C++ compiled module validation usingget_framebuffer_indices()which returns bytes of 23040 bytes (160×144) with values 0..3 from the front buffer.
Results
Completed deployments:
- ✅ Helper
run_one_frame()that accumulates cycles correctly - ✅ Tests rewritten with real framebuffer indices asserts
- ✅
rom_smoke_0442.pyuseread_raw()for tilemap stats - ✅ Gated instrumentation
[IO-SCROLL-WRITE]added - ✅Log
[ENV]removed from runtime, moved to tools
Known issues:
- ⚠️ Tests fail - framebuffer returns 0 (requires further investigation)
Next Steps
- Investigate why the framebuffer returns 0 in tests
- Verify BG rendering conditions (LCDC bit 0, timing, etc.)
- Validate tile data pattern encoding
- Run actual validation with grid UI once the tests work