Skip to content

Save states: none in a run with no game (a disc_required game's input self-test without its disc) - #115

Merged
gascarcella merged 2 commits into
mainfrom
gascarcella/savestate-no-game
Oct 28, 2026
Merged

gascarcella merged 2 commits into
mainfrom
gascarcella/savestate-no-game

Conversation

@gascarcella

@gascarcella gascarcella commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

The bug

Since v0.3.7 every window run arms save states (runtime/main.c, the || window before port_savestate_arm). A game with game.json disc_required (#64) started with --input-test and no disc never starts game_main: its vsyncs run port_wait() from main's stack. At vsync 1, port_savestate_captured() found the frame on neither the game stack nor a fiber's and stopped the run: state: the vsync is not on the game stack or a fiber's, status 1. dw2003recomp has no disc_required, so only dcb_decomp's disc-free input self-test (scripts/port_build.sh --sdl, found while bumping its pin to v0.3.18) hit it.

The fix

No game means nothing for a state to hold, so main no longer arms states in that run (rather than arming them and skipping each capture: the game stack would be set up for a game that never runs).

  • port_savestate_no_game() records why states are off and logs state: off in this run (no game: nothing to hold); port_savestate_off() returns that reason (NULL when armed).
  • The input self-test skips the quick slot's round trip with the quick slot: skipped (no game in this run) and goes on to the menu's; the hotkeys and the debug channel's save/load give the same reason.
  • --save-state / --load-state with --input-test and no disc on a disc_required game are refused (status 64) instead of being silently ignored.
  • Docs: docs/RUNTIME.md "Save states" (when states are on), docs/GAME_CONTRACT.md (disc_required), CLAUDE.md's command line; CHANGELOG.md v0.3.19 (unreleased).

The test

tests/hello_test.py --menu (CI's launcher job) now also builds hello as a disc_required game: examples/hello with -DHELLO_DISC_REQUIRED=ON writes a copy of its game.json with disc_required: true and a made-up disc no image matches (no Steam artwork), into build/hello-menu-nodisc, with the window and the menu. Without a disc it must refuse to start (status 64); its --input-test offscreen must pass without the game: status 0, the quick slot skipped for no game in this run, the menu's round trip passed. Before the fix this run ended with the fatal above (reproduced).

Gates

  • python3 -m py_compile tools/*.py tools/mcp/*.py tools/replay/*.py tests/*.py
  • python3 tests/hello_test.py: pass
  • python3 tests/tasks_test.py and --sanitize: pass (states on fibers, resumed)
  • python3 tests/hello_test.py --menu --gpu: pass, the new no-game checks included; hello with a game still runs both quick slot round trips (running and paused: passed)
  • dw2003recomp (origin/main a836a9e, its submodule at this branch's head), DW3_JOBS=4 scripts/test.sh: the census, the probe, layers 1, 2 and 3, and the port layer's run.py and settings.py pass; tests/port/render_gpu.py fails 3 checks (the present into window sizes at vsyncs 900, 1300 and 1800), the same pixel counts at dw2003recomp's own pin v0.3.18 and at this branch's base 8e9153a, so not from this change (this machine: NVIDIA RTX 4070 Ti SUPER). The steps test.sh did not reach after it, run one by one against this branch: render_filters, textures, gpu_hw_replay, hz60, battle, vram, subpixel_jitter, debug, savestate, crash, field and mods all pass.
  • dcb_decomp (its v0.3.18 bump, -DPSXSTACK_DIR at this branch), scripts/port_build.sh --sdl in a build directory of its own: no disc refused (64); the input self-test without the disc exits 0 (the quick slot: skipped (no game in this run), the menu passed), where v0.3.18 ended with status 1. Its window boot with the disc (600 frames offscreen) exits 0 and loads OPENSEG.

🤖 Generated with Claude Code

gascarcella and others added 2 commits October 28, 2026 15:19
… self-test without its disc)

Since v0.3.7 every window run arms save states. A disc_required game's --input-test without its disc never starts
game_main: its vsyncs run on main's stack, so the first capture point stopped the run with "state: the vsync is not
on the game stack or a fiber's" (status 1), and dcb_decomp's disc-free input self-test failed.

No game means nothing to hold: main no longer arms states then, and says why (port_savestate_no_game). The input
self-test skips the quick slot's round trip with "no game in this run" and goes on to the menu's; the hotkeys and the
debug channel give the same reason; --save-state and --load-state without the game are refused (status 64).

tests/hello_test.py --menu now also builds hello as a disc_required game (examples/hello -DHELLO_DISC_REQUIRED=ON, a
made-up disc no image matches): without a disc it must refuse to start, and its input self-test must pass without
the game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gascarcella
gascarcella merged commit e39a327 into main Oct 28, 2026
5 checks passed
@gascarcella
gascarcella deleted the gascarcella/savestate-no-game branch October 28, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant