Session save — capture netplay session state to disk (host) #3

Closed
opened 2026-08-25 06:03:45 +02:00 by pbwraith · 1 comment
Owner

Repo: gopher64-netplay (fork of https://github.com/gopher64/gopher64)
Context: Full scope doc at upstream analysis — see commit history. Netplay uses GGRS over WebRTC (matchbox_socket), reliable channel carries NetplayMessage { name, data } in 16KB chunks. Savestates serialize full Device via postcard + compress.

Goal: Host saves an active netplay session to a .netplay-state file so it can be resumed later.

Background:

  • savestates::create_savestate_in_memory (issue #2) provides the state snapshot
  • GGRS session exposes current_frame() — that frame must be captured so a resume can restart at the same point
  • src/ui/storage.rs paths struct owns file paths; add a netplay-state path

Acceptance:

  • Add storage::paths.netplay_state_file_path (persisted per-slot style, e.g. netplay-state next to other save files)
  • Add netplay::save_netplay_session(device, path):
    • captures in-memory state via create_savestate_in_memory
    • encodes { state, frame: session.current_frame(), input_delay, rom_hash, players } via postcard
    • compresses and writes atomically (tmp + rename like other saves)
    • shows onscreen message "Session saved — safe to quit"
  • Add hotkey binding (check the input/keybind config pattern in src/ui/input.rs and config) — default e.g. F7, configurable
  • Only the host (player_number==0) can save; others show "Only the host can save the session"
  • Tests: after N frames of netplay, save produces a parseable file with correct frame/rom_hash metadata; file round-trips through compress/decompress.

Verification: cargo test; manual: start 2-player netplay, play a few frames, save, inspect file.

**Repo:** gopher64-netplay (fork of https://github.com/gopher64/gopher64) **Context:** Full scope doc at upstream analysis — see commit history. Netplay uses GGRS over WebRTC (matchbox_socket), reliable channel carries `NetplayMessage { name, data }` in 16KB chunks. Savestates serialize full Device via postcard + compress. **Goal:** Host saves an active netplay session to a `.netplay-state` file so it can be resumed later. **Background:** - `savestates::create_savestate_in_memory` (issue #2) provides the state snapshot - GGRS session exposes `current_frame()` — that frame must be captured so a resume can restart at the same point - `src/ui/storage.rs` paths struct owns file paths; add a netplay-state path **Acceptance:** - Add `storage::paths.netplay_state_file_path` (persisted per-slot style, e.g. `netplay-state` next to other save files) - Add `netplay::save_netplay_session(device, path)`: - captures in-memory state via `create_savestate_in_memory` - encodes `{ state, frame: session.current_frame(), input_delay, rom_hash, players }` via postcard - compresses and writes atomically (tmp + rename like other saves) - shows onscreen message "Session saved — safe to quit" - Add hotkey binding (check the input/keybind config pattern in `src/ui/input.rs` and config) — default e.g. F7, configurable - Only the host (player_number==0) can save; others show "Only the host can save the session" - Tests: after N frames of netplay, save produces a parseable file with correct frame/rom_hash metadata; file round-trips through compress/decompress. **Verification:** `cargo test`; manual: start 2-player netplay, play a few frames, save, inspect file.
pbwraith added the
netplay
p1
rust
labels 2026-08-25 06:04:33 +02:00
pbwraith added the
in-progress
label 2026-08-25 06:16:56 +02:00
pbwraith added
in-review
and removed
in-progress
labels 2026-08-25 08:15:40 +02:00
Author
Owner

Stale in-review removed — this is an orphaned review that would eat a lane

in-review on this issue is no longer describing anything. Its PR !13 is CLOSED
and UNMERGED
(closed as subsumed on 2026-08-26).

That combination is actively harmful rather than merely untidy. In
gitea-worker-lane.py the pick order is:

0  bounced PR      1  issue with `in-review` (orphaned review)      2  in-progress
3  p1              4  p2                                            5  p3   6  unlabelled

An issue is only suppressed as a candidate while a PR links it. !13 is closed,
so this issue is no longer suppressed — and in-review promotes it to priority
1, ahead of every p1 on the board
. The next lane to run would claim #3 and find
the work already shipped elsewhere.

The work is not lost — verified this sweep, not inferred

Re-checked against the Gitea compare API rather than trusting the earlier review
comment:

compare issue-4...issue-3  ->  total_commits=0, ahead=0

Zero commits on issue-3 are absent from issue-4, i.e. issue-3 is fully
contained in issue-4
, which is !18. !18's own commit log carries it
explicitly:

dab9fabc  merge: netplay session save to disk (#3, PR #13) — dependency for session resume

So session-save-to-disk ships in !18.

What I changed and why

  • removed in-review — there is no open PR for #3 to review
  • added blocked, prerequisite named: !18 must merge. Not needs-guidance
    (nothing is being asked) and not closed (the acceptance criteria in this body
    have not been verified against a merged tree yet).

!18 is currently bounced with the specific rebase it needs. When it merges,
verify this issue's acceptance criteria against the merged state, then close.

The branch issue-3 is untouched.

## Stale `in-review` removed — this is an orphaned review that would eat a lane `in-review` on this issue is no longer describing anything. Its PR **!13 is CLOSED and UNMERGED** (closed as subsumed on 2026-08-26). That combination is actively harmful rather than merely untidy. In `gitea-worker-lane.py` the pick order is: 0 bounced PR 1 issue with `in-review` (orphaned review) 2 in-progress 3 p1 4 p2 5 p3 6 unlabelled An issue is only suppressed as a candidate while a PR *links* it. !13 is closed, so this issue is no longer suppressed — and `in-review` promotes it to **priority 1, ahead of every p1 on the board**. The next lane to run would claim #3 and find the work already shipped elsewhere. ### The work is not lost — verified this sweep, not inferred Re-checked against the Gitea compare API rather than trusting the earlier review comment: compare issue-4...issue-3 -> total_commits=0, ahead=0 Zero commits on `issue-3` are absent from `issue-4`, i.e. **`issue-3` is fully contained in `issue-4`**, which is `!18`. `!18`'s own commit log carries it explicitly: dab9fabc merge: netplay session save to disk (#3, PR #13) — dependency for session resume So session-save-to-disk ships in `!18`. ### What I changed and why - **removed `in-review`** — there is no open PR for #3 to review - **added `blocked`, prerequisite named: `!18` must merge.** Not `needs-guidance` (nothing is being asked) and not closed (the acceptance criteria in this body have not been verified against a merged tree yet). `!18` is currently `bounced` with the specific rebase it needs. When it merges, verify this issue's acceptance criteria against the merged state, then close. The branch `issue-3` is untouched.
pbwraith added
blocked
and removed
in-review
labels 2026-08-26 19:48:22 +02:00
pbwraith added the
manual-only
label 2026-08-26 20:25:40 +02:00
This repo is archived. You cannot comment on issues.
No Milestone
No project
No Assignees
1 Participants
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: pbwraith/gopher64-netplay#3
No description provided.