Skip to content

feat(netplay,quality): lobby/matchmaking + delayed spectators + desync/RTT hardening + fuzz expansion + ROM Info browser (v2.2.0 "Capstone")#290

Open
doublegate wants to merge 3 commits into
mainfrom
feat/v2.2.0-netplay-quality
Open

feat(netplay,quality): lobby/matchmaking + delayed spectators + desync/RTT hardening + fuzz expansion + ROM Info browser (v2.2.0 "Capstone")#290
doublegate wants to merge 3 commits into
mainfrom
feat/v2.2.0-netplay-quality

Conversation

@doublegate

Copy link
Copy Markdown
Owner

Summary

Implements the v2.2.0 "Capstone" netplay lobby (B5) workstream plus the quality pass, entirely in the netplay signaling / session / transport layers, the frontend, the fuzz harness, and docs. The deterministic core is untouched and the rollback determinism contract is preserved — AccuracyCoin holds 141/141 (100%), and save-state / movie replay stays byte-identical.

B5 — Netplay lobby / matchmaking

  • Lobby directory + matchmaking (signaling.rs): new RoomInfo + four SignalMessage variants — ListRooms/RoomList (browse open rooms) and QuickMatch/Matched (server-side quick-play: join any open room for the ROM or create one). Shared add_to_room primitive; room-list array parse bounded at MAX_ROOM_LIST (256).
  • Delayed-stream spectators (spectator.rs): SpectatorConfig.delay_frames (clamped to MAX_DELAY_FRAMES = 512) — a broadcast / anti-spoiler / jitter-smoothing hold; presentation-only, frames still produced byte-identically & in order. Wired to NetplayUi::spectator_delay_frames.
  • Hardened desync surface (diagnostics.rs): graded DesyncStatus {InSync/Suspect/Desynced} with a hysteresis threshold (3 consecutive mismatches) + sticky-once-confirmed, so a lone reordered checksum can't flash a false desync.
  • Tuned RTT timeouts (connection.rs): graded PeerLink {Live/Interrupted/TimedOut} driven by last_recv against 2 s / 5 s thresholds + DisconnectReason::PeerTimeout, builder-configurable. Deliberately far above Mesen's known-too-low ~150 ms (rationale documented on PeerLink — a single lost 1 Hz ping or LTE retransmit spike must not trip it; matches GGPO/Parsec grace windows).

Quality pass

  • Fuzz targets (fuzz/, 3 → 8): ppu_reg_io, apu_reg_io, netplay_message (untrusted network input — highest value), save_state, movie. All build under nightly cargo-fuzz and run clean for tens of thousands of iterations. The movie target found two real OOM DoS paths in Movie::deserialize (untrusted frame_count pre-sizing a multi-GB Vec; a zero-width bytes_per_frame looping frame_count empty pushes) — both fixed byte-identically for valid input, with a regression test.
  • ROM Info browser: read-only Tools → ROM Info panel surfacing the loaded ROM's two identity CRC32s (game-DB key + No-Intro full-file key), SHA-256, per-game DB entry, and decoded cartridge header.
  • MkDocs handbook: four new pages (expansion-audio, PAL/region, CRT/composite, creator-tools) + nav entries.

Verification (all green)

  • cargo fmt --all --check · cargo clippy --workspace --all-targets -D warnings · frontend feature combos (scripting / hd-pack / retroachievements) · both wasm32 gates (wasm-winit + wasm-canvas) · RUSTDOCFLAGS=-D warnings cargo doc · markdownlint (pinned v0.39.0).
  • AccuracyCoin 141/141 (byte-identical) · rustynes-netplay 102 passed / 2 ignored (incl. new lobby / delay / desync-status / PeerLink tests) · rustynes-core movie 44 passed (incl. determinism round-trip + new OOM regression) · 8 fuzz targets build + smoke clean.

Notes

  • Left out of this PR by design: docs/STATUS.md / to-dos/ROADMAP.md reconciliation (the final capstone doc-sync step) and the deploy/ RA auth-proxy work (a concurrent v2.1.10 agent owns it).
  • The branch is based on fd82485; the maintainer runs the release ceremony / rebase. Do not merge.

🤖 Generated with Claude Code

doublegate and others added 3 commits July 12, 2026 00:32
…ndbook (v2.2.0)

Frontend — read-only ROM Info browser (v2.2.0 "Capstone"):
- New Tools -> ROM Info panel (crates/rustynes-frontend/src/debugger/
  rom_info_panel.rs). Surfaces, for the loaded ROM, the two dump-identity
  CRC32 keys (header-excluded game-DB key + full-file No-Intro key), the
  SHA-256, the effective per-game DB entry (title/mapper/region/mirroring/
  submapper), and the decoded cartridge header read off the running Nes
  (mapper id, region, PRG-ROM/CHR-ROM sizes). Read-only (&Nes) — never
  mutates the emulator or DB overlay; the core never consults it.
- Wired through the standard 5 points: ToolPanel::RomInfo variant, the
  show_rom_info flag + rom_info_ui state, open_panel arm, the render call,
  and the any_nes_tool_open lock-gate predicate (the one non-obvious step
  — a nes-reading tool panel must be OR'd in). The game_db block now
  reborrows nes via as_deref_mut() so the new block can as_deref() it.
  Tools-menu entry reuses MenuAction::OpenPanel; CIRCLE_INFO glyph reused.
- netplay_ui: DisconnectReason::PeerTimeout arm + a configurable
  spectator_delay_frames (default 0) feeding SpectatorConfig.delay_frames.

Docs:
- docs/netplay-webrtc.md: new §4b (lobby/matchmaking, PeerLink RTT with the
  "why not Mesen's 150 ms" rationale, DesyncStatus), the delayed-stream
  spectator buffer in §4, and six new Verified-table rows.
- docs/frontend.md: the ROM Info browser + the updated any_nes_tool_open /
  tool-panel list.
- MkDocs handbook: four new pages (expansion-audio, pal-region,
  crt-composite, creator-tools) cross-linked to the authoritative specs,
  with mkdocs.yml nav entries (markdownlint v0.39.0 clean).
- CHANGELOG [Unreleased]: comprehensive v2.2.0 "Capstone" Added/Changed
  entries (house style).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- spectator.rs: `MAX_DELAY_FRAMES` doc used a plain code span instead of an
  intra-doc link to the private `MAX_SPECTATOR_FRAME_LOOKAHEAD` const (the
  `rustdoc::private_intra_doc_links` warning-as-error gate).
- connection.rs: add `synced_peer_liveness_grades_then_times_out` — drives two
  loopback connections to Synced, silences one, and asserts the other grades
  Live -> Interrupted -> TimedOut and disconnects with `PeerTimeout` under
  tight test-scale `with_peer_timeouts` thresholds.

rustynes-netplay: 102 passed, 2 ignored; rustdoc -D warnings clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r client

The browser signaling client's `handle_signal` match over `SignalMessage`
(wasm-only, `wasm_netplay.rs`) became non-exhaustive once the lobby /
matchmaking variants landed. Add inbound arms: `Matched` (a QuickMatch reply —
record slot + room size like `Joined`, surface the room code), `RoomList`
(the lobby directory — record the open-room count pending a browse UI), and
group the client->server-only `ListRooms` / `QuickMatch` with the existing
`Join` / `PublicAddr` ignore arm. Both wasm clippy gates (wasm-winit +
wasm-canvas) are clean again. (rustfmt reflow of rom_info_panel.rs included.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 12, 2026 04:44
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the v2.2.0 “Capstone” netplay lobby/matchmaking + spectator-delay + desync/RTT hardening features, expands fuzz coverage for key untrusted-input boundaries, and introduces a read-only ROM Info tool panel with supporting handbook docs.

Changes:

  • Netplay signaling gains lobby browse (ListRooms/RoomList) and matchmaking (QuickMatch/Matched), plus spectator delayed-stream buffering and a graded/sticky desync status surface.
  • Fuzz surface expands (3 → 8 targets) and Movie::deserialize is hardened against OOM/DoS patterns found by fuzzing.
  • Frontend adds Tools → ROM Info panel and docs/MkDocs navigation is updated with new handbook pages.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
mkdocs.yml Adds handbook pages to the MkDocs nav.
fuzz/README.md Documents new fuzz targets and LSan note; updates CI-friendly target loop.
fuzz/Cargo.toml Adds fuzz binaries + deps for new targets (PPU/APU/netplay/save-state/movie).
fuzz/fuzz_targets/save_state.rs New fuzz target for .rns parsing/restoring surfaces.
fuzz/fuzz_targets/ppu_reg_io.rs New fuzz target driving PPU CPU-facing register I/O.
fuzz/fuzz_targets/apu_reg_io.rs New fuzz target driving APU register/status I/O.
fuzz/fuzz_targets/netplay_message.rs New fuzz target for UDP + JSON signaling parsers.
fuzz/fuzz_targets/movie.rs New fuzz target for .rnm deserialization/serialization.
docs/expansion-audio.md New curated handbook entry point for expansion-audio subsystem.
docs/pal-region.md New curated handbook entry point for PAL/region behavior.
docs/crt-composite.md New curated handbook entry point for display pipeline/composite/CRT filters.
docs/creator-tools.md New curated handbook entry point for creator/debugging tools.
docs/netplay-webrtc.md Documents spectator delay + lobby/matchmaking + desync/liveness changes.
docs/frontend.md Documents ROM Info panel and tool-panel locking behavior updates.
crates/rustynes-netplay/src/signaling.rs Implements RoomInfo, lobby list/matchmaking messages, bounded room-list parsing, relay support.
crates/rustynes-netplay/src/spectator.rs Adds spectator delay_frames with clamping and reveal-horizon logic + tests.
crates/rustynes-netplay/src/diagnostics.rs Adds DesyncStatus (hysteresis + sticky-confirmed) and related APIs/tests.
crates/rustynes-netplay/src/connection.rs Adds PeerLink liveness grading + PeerTimeout disconnect reason + tests.
crates/rustynes-netplay/src/lib.rs Re-exports new netplay types/constants.
crates/rustynes-frontend/src/netplay_ui.rs Wires spectator delay config; surfaces new disconnect reason string.
crates/rustynes-frontend/src/wasm_netplay.rs Handles Matched / RoomList in browser netplay client.
crates/rustynes-frontend/src/ui_shell.rs Adds Tools menu item for ROM Info panel.
crates/rustynes-frontend/src/debugger/mod.rs Adds ROM Info tool panel plumbing + lock-path inclusion.
crates/rustynes-frontend/src/debugger/rom_info_panel.rs New read-only ROM Info tool panel implementation.
crates/rustynes-core/src/movie.rs Hardens .rnm deserializer (bytes/frame=0 + frame_count OOM cap) + regression test.
CHANGELOG.md Documents netplay lobby/delay/desync/liveness, fuzz expansion, ROM Info, and movie hardening.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +668 to +675
// Prefer an existing open room for this exact game with a free slot.
let target = self
.open_rooms(rom_hash)
.into_iter()
.find(|r| r.is_joinable() && (rom_hash.is_empty() || r.rom_hash == rom_hash))
.map(|r| r.code);

let room_code = target.unwrap_or_else(|| self.next_room_code());
Comment on lines +705 to 712
// Reject a joiner past the room's player count. Drop a freshly-created
// but now-unused room so a rejected QuickMatch race leaves no ghost.
if room.slots.len() >= usize::from(max_players) {
return vec![
Action::Send {
to: client,
msg: SignalMessage::Error {
reason: "room full".to_string(),
},
},
Action::Close { who: client },
];
if room.slots.is_empty() {
self.rooms.remove(room_code);
}
return Err("room full");
}
Comment on lines +691 to +693
// Any datagram from the peer is proof of life — refresh the liveness
// clock that drives `PeerLink` / `DisconnectReason::PeerTimeout`.
self.last_recv = now;
(hi, lo)
}

/// Human-readable size: bytes for < 1 KiB, else KiB.
Comment on lines +22 to +25
/// Minimal `PpuBus`: an 8 KiB CHR/pattern-table window backing every PPU-space
/// read/write. Nametable mirroring uses the trait's default `nametable_address`.
/// This is exactly the surface `cpu_read_register`/`cpu_write_register` reach
/// through for PPUDATA ($2007) — no mapper side effects, no A12 notifications.
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.

2 participants