Summary
Using wasmboy@0.7.1 through the direct core module (dist/core/core.esm.js), saving and restoring core memory with saveState() / loadState() can leave a Game Boy Color ROM running into a white frame after resume.
Observed behavior
A minimal direct-core flow can reproduce this without the higher-level wrapper:
- Import
wasmboy/dist/core/core.esm.js.
- Write a GBC ROM into
core.memory.wasmByteMemory at core.CARTRIDGE_ROM_LOCATION.
- Call
core.config(0, 1, 0, 0, 0, 0, 0, 0, 0, 0).
- Run frames with
core.executeFrameAndCheckAudio(...) until the frame buffer is non-blank.
- Call
core.saveState() and copy these regions:
CARTRIDGE_RAM_LOCATION
GAMEBOY_INTERNAL_MEMORY_LOCATION
GBC_PALETTE_LOCATION
WASMBOY_STATE_LOCATION
- Reset/reinitialize the core, write those copied regions back, call
core.loadState(), then resume frame execution.
Expected: the restored emulator continues rendering non-blank GBC frames.
Actual: after resume, the frame buffer can become a single white color. In one reproduced case, the 160x144 frame became 23,040 white pixels after loadState() and subsequent frame execution.
Suspicious restore-slot reads
In the distributed dist/core/core.esm.js, Graphics.saveState() stores the scanline register using Graphics.saveStateSlot:
store(getSaveStateMemoryOffset(0x04, Graphics.saveStateSlot), Graphics.scanlineRegister);
But Graphics.loadState() reads it using the current Graphics.scanlineRegister as the save-state slot:
Graphics.scanlineRegister = load(getSaveStateMemoryOffset(0x04, Graphics.scanlineRegister));
That appears inconsistent with the surrounding fields, which all use Graphics.saveStateSlot on load. If the live scanline is not the graphics save-state slot, this reads from the wrong save partition and corrupts graphics state.
There is a similar-looking pattern in Channel3.loadState():
Channel3.cycleCounter = load(getSaveStateMemoryOffset(0x00, Channel3.cycleCounter));
where the save path uses Channel3.saveStateSlot.
Environment
- Package:
wasmboy@0.7.1
- Core import:
wasmboy/dist/core/core.esm.js
- Runtime: Chromium / Node 22 direct-core probes
- ROM class: Game Boy Color
Notes
The failure is specifically with direct core state restoration. Cartridge RAM save/load remains usable when the ROM is reinitialized and RAM is restored without calling core.loadState().
Summary
Using
wasmboy@0.7.1through the direct core module (dist/core/core.esm.js), saving and restoring core memory withsaveState()/loadState()can leave a Game Boy Color ROM running into a white frame after resume.Observed behavior
A minimal direct-core flow can reproduce this without the higher-level wrapper:
wasmboy/dist/core/core.esm.js.core.memory.wasmByteMemoryatcore.CARTRIDGE_ROM_LOCATION.core.config(0, 1, 0, 0, 0, 0, 0, 0, 0, 0).core.executeFrameAndCheckAudio(...)until the frame buffer is non-blank.core.saveState()and copy these regions:CARTRIDGE_RAM_LOCATIONGAMEBOY_INTERNAL_MEMORY_LOCATIONGBC_PALETTE_LOCATIONWASMBOY_STATE_LOCATIONcore.loadState(), then resume frame execution.Expected: the restored emulator continues rendering non-blank GBC frames.
Actual: after resume, the frame buffer can become a single white color. In one reproduced case, the 160x144 frame became 23,040 white pixels after
loadState()and subsequent frame execution.Suspicious restore-slot reads
In the distributed
dist/core/core.esm.js,Graphics.saveState()stores the scanline register usingGraphics.saveStateSlot:But
Graphics.loadState()reads it using the currentGraphics.scanlineRegisteras the save-state slot:That appears inconsistent with the surrounding fields, which all use
Graphics.saveStateSloton load. If the live scanline is not the graphics save-state slot, this reads from the wrong save partition and corrupts graphics state.There is a similar-looking pattern in
Channel3.loadState():where the save path uses
Channel3.saveStateSlot.Environment
wasmboy@0.7.1wasmboy/dist/core/core.esm.jsNotes
The failure is specifically with direct core state restoration. Cartridge RAM save/load remains usable when the ROM is reinitialized and RAM is restored without calling
core.loadState().