Skip to content

core loadState can restore GBC graphics to a white frame #380

Description

@dskvr

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:

  1. Import wasmboy/dist/core/core.esm.js.
  2. Write a GBC ROM into core.memory.wasmByteMemory at core.CARTRIDGE_ROM_LOCATION.
  3. Call core.config(0, 1, 0, 0, 0, 0, 0, 0, 0, 0).
  4. Run frames with core.executeFrameAndCheckAudio(...) until the frame buffer is non-blank.
  5. Call core.saveState() and copy these regions:
    • CARTRIDGE_RAM_LOCATION
    • GAMEBOY_INTERNAL_MEMORY_LOCATION
    • GBC_PALETTE_LOCATION
    • WASMBOY_STATE_LOCATION
  6. 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().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions