-
Notifications
You must be signed in to change notification settings - Fork 0
Netplay and Rollback
rusty2600-netplay adds 2-player rollback netplay, built on the mature, independently-maintained ggrs crate (a GGPO-style rollback engine for Rust) rather than a from-scratch reimplementation.
Rollback's resync() loop is fundamentally "restore a recent snapshot, replay forward" — exactly the primitive the already-real, versioned SaveState format provides (see Architecture-Decision-Records ADR 0007), and the determinism contract (ADR 0004) is explicitly what makes rewind, run-ahead, and netplay rollback all possible from the same substrate. No new determinism infrastructure was needed to build this crate — it's a thin, real integration layer over work that already existed.
A deliberate scope cut against a fully-connected N-peer mesh: real 2600 hardware rarely supported more than 2 controllers, so a roster protocol and 3-4-player UI would be pure implementation cost with little real payoff for this console. Console switches and paddles are not modeled per-player either — there's no natural "which peer owns this" mapping for shared machine-level state in a 2-player session, so a netplay session runs with console switches idle and paddles centered.
Three transport paths exist, each verified to a different, honestly-stated degree:
-
Direct-IP / LAN UDP — the original transport, using GGRS's own built-in
UdpNonBlockingSocket. Input-delay of 2 frames and an 8-frame max prediction window match GGPO convention exactly. -
STUN-assisted NAT traversal — a real RFC 5389 client (
stun_codec, a sans-IO codec adding no async/networking dependency) plus a best-effort UDP hole-punch helper. The STUN request/response round trip itself is live-verified against a real public STUN server. Actual traversal between two independently-NATed peers on two different real networks remains unverified — a single-host sandbox cannot provide that test, since loopback traffic never crosses a NAT boundary. -
Browser WebRTC (
wasm32-only,v2.6.0) — see Architecture-Decision-Records ADR 0008 for why the async surface this needs stays contained to one-time connection setup and never leaks into the synchronous core. Usesweb_sys'sRtcPeerConnection/RtcDataChannelbindings directly, with a minimal manual/copy-paste SDP offer-answer exchange rather than a signaling server. The entire connection-establishment code path — offer/answer generation, ICE-gathering-completion detection — was driven end-to-end against a real Chromium instance and proven correct; the data channel itself reaching"open"remained unverified in that sandbox due to an environment-specific restriction on ICE candidate gathering, diagnosed but not a bug in Rusty2600's own code.
The real proof the rollback logic works, not just that the crate compiles: ggrs::SyncTestSession drives a real System loaded with a tiny synthetic input-reactive ROM across many frames of varied two-player input, internally saving state, rewinding, and re-simulating forward, panicking on any checksum mismatch. This test was validated for real — an early version using no cartridge at all passed vacuously (state barely depends on input with nothing loaded), so a genuinely input-reactive ROM and a real checksum were added, then the bug was deliberately reintroduced once more to confirm the test actually catches it.
A netplay feature flag (off by default), a Connect dialog, and live per-frame input capture exist in rusty2600-frontend. A connected session bypasses the frontend's run-ahead entirely (the two features have no obvious combined semantics — run-ahead speculates on local input only, meaningless once a rollback session is authoritative over the timeline) and locks Lua script writes via the same WritesLocked gate RetroAchievements hardcore mode uses (see Scripting-Engine) — an unreplicated local write would silently desync the two peers' otherwise bit-identical timelines.
Architecture-Overview · Scripting-Engine · Testing-Strategy · Architecture-Decision-Records
Rusty2600 is a cycle-accurate Atari 2600 (VCS) emulator written in pure Rust. Licensed under MIT OR Apache-2.0. | GitHub Repository | Web Demo