feat(netplay,quality): lobby/matchmaking + delayed spectators + desync/RTT hardening + fuzz expansion + ROM Info browser (v2.2.0 "Capstone")#290
Merged
Conversation
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
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::deserializeis 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.
…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>
doublegate
force-pushed
the
feat/v2.2.0-netplay-quality
branch
from
July 12, 2026 14:35
43a49e5 to
3811f73
Compare
Addresses the five Copilot review threads on the v2.2.0 "Capstone"
netplay + quality PR, plus the workspace-clippy fallout of the new
`DisconnectReason::PeerTimeout` variant. All changes are behavior-faithful
for valid input; the deterministic core is untouched.
- netplay/signaling: `handle_quick_match` scans `self.rooms` directly
instead of `open_rooms(rom_hash)`. `open_rooms` truncates at
`MAX_ROOM_LIST` (256) for the DoS-bounded browse list, so routing
matchmaking through it silently made a joinable room unreachable once a
lobby held more than 256 open rooms — contradicting the documented
"QuickMatch still reaches them" contract. The direct scan is uncapped
and skips allocating/cloning a `Vec<RoomInfo>` just to pick one room;
the join predicate (non-empty, free slot, rom-hash match) is preserved.
- netplay/signaling: remove the dead `slots.is_empty()` room-removal
branch in the `add_to_room` room-full path. `max_players` is clamped to
2..=4 the moment `slots` is empty, so `slots.len() >= max_players` is
unreachable for a freshly-created room — there is no ghost room to drop.
- netplay/connection: gate the `last_recv` liveness refresh to the
adopted peer. Previously *any* decoded datagram refreshed the clock, so
after sync a third party able to reach the socket could hold the
connection out of `DisconnectReason::PeerTimeout` indefinitely. It now
refreshes only while unadopted (`remote_addr().is_none()`) or when the
source equals the bound peer (`is_none_or(|peer| peer == from)`); the
Sync adoption / anti-hijack logic is unchanged.
- frontend/rom_info_panel: `fmt_size` renders KiB for every size `>= 1024`
(was only exact multiples of 1024), matching its doc comment; the exact
byte count is always shown alongside the floored-KiB figure. Also
backtick-quote `RustyNES` in the module doc (`clippy::doc_markdown`).
- frontend/debugger: construct the zero-sized `RomInfoPanelState` directly
rather than via `::default()` (`clippy::default_constructed_unit_structs`).
- mobile: handle the new `DisconnectReason::PeerTimeout` arm in the
netplay disconnect-reason match ("peer stopped responding") — the added
variant made the match non-exhaustive (E0004).
- fuzz/ppu_reg_io: correct the `FuzzPpuBus` doc comment — the backing
store is a flat 16 KiB (`0x4000`) masked to the full PPU address space
(`addr & 0x3FFF`), not an "8 KiB CHR/pattern-table window".
- core/movie: hoist the `FRAME_COUNT_OFF` test const to the top of its
scope (`clippy::items_after_statements`).
rustynes-netplay: 102 passed, 2 ignored; workspace clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate
added a commit
that referenced
this pull request
Jul 12, 2026
…ripherals + quality/security) (#297) The milestone cut that closes the v2.1.5 -> v2.2.0 "deepen the existing project" run, landing its two remaining marquees plus a peripherals and quality/security pass. Every change already merged (PRs #290, #291) is additive or default-off; the deterministic core is untouched, so this cut itself moves only version strings + release docs and no framebuffer/audio byte. Marquees (already on main via #290/#291): - Netplay matchmaking + lobby (B5) — a browse-and-join room directory (`ListRooms`/`RoomList`) and server-side quick-play (`QuickMatch`/`Matched`) over the existing room-code / TURN transport, delayed-stream spectators (`SpectatorConfig.delay_frames`, reveal-horizon gated), a graded hysteresis desync verdict (`DesyncStatus`, threshold 3 ~= 1.5 s), and graded multi-second peer-liveness RTT timeouts (`PeerLink` + `DisconnectReason::PeerTimeout`). Signaling and telemetry only — the rollback/determinism contract is untouched. - FDS medium model completion (F4.3) — per-block CRC-16/KERMIT re-emitted on every BIOS write (`resynth_block_crc`) over a synthesized gap/mark wire image, an opt-in default-OFF continuous analog head-seek / velocity model (`Fds::set_analog_head_seek`) replacing the flat `HEAD_RESEEK_CYCLES` window, and a BIOS-free synthetic write-verify oracle (`Fds::medium_write_verify`) as the CI-verifiable half (the real-BIOS write-CRC path stays gitignored/local — copyright disksys.rom). Additive v4 FDS save-state tail; with the model off a non-writing `.fds` run is byte-identical to prior releases. Peripherals (#291): the Famicom `$4016`-bit-2 microphone (`Nes::set_microphone`, `$4016`-only, never `$4017`) and a 3x3-aperture Zapper light-timing model (`ZAPPER_APERTURE_*`), both additive/default-off. Quality/security (#290): cargo-fuzz targets grown 3 -> 8 (`ppu_reg_io`, `apu_reg_io`, the high-value `netplay_message`, `save_state`, `movie`), which surfaced and fixed two real OOM-DoS paths in `Movie::deserialize` (untrusted `frame_count` -> unbounded `Vec::with_capacity`, and a zero-`bytes_per_frame` infinite push loop) byte-identically for valid input; a read-only Tools -> ROM Info browser (dump-identity CRC32 keys + SHA-256 + per-game DB entry + decoded header, `&Nes`, never mutates); and four new MkDocs handbook pages. Cut mechanics: `[workspace.package] version` 2.1.10 -> 2.2.0 (Cargo.lock refreshed across all 18 workspace crates), CHANGELOG `[Unreleased]` renamed to `[2.2.0]` with a fresh empty `[Unreleased]`, `.github/release-notes/ v2.2.0.md` authored, and README badge / BibTeX / current-release paragraph updated. `release-auto.yml` tags + publishes on main going green. Sacred gate (verified on the merged #290+#291 tree): AccuracyCoin 141/141 (100.00%), nestest 0-diff, `pal_apu_tests` 10/10, `visual_regression` byte-identical, netplay rollback-determinism + snapshot-restore-replay + FDS synthetic write-verify green, `no_std` thumbv7em cross-compile, full host fmt/clippy/rustdoc/markdownlint, version consistency. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
signaling.rs): newRoomInfo+ fourSignalMessagevariants —ListRooms/RoomList(browse open rooms) andQuickMatch/Matched(server-side quick-play: join any open room for the ROM or create one). Sharedadd_to_roomprimitive;room-listarray parse bounded atMAX_ROOM_LIST(256).spectator.rs):SpectatorConfig.delay_frames(clamped toMAX_DELAY_FRAMES= 512) — a broadcast / anti-spoiler / jitter-smoothing hold; presentation-only, frames still produced byte-identically & in order. Wired toNetplayUi::spectator_delay_frames.diagnostics.rs): gradedDesyncStatus{InSync/Suspect/Desynced} with a hysteresis threshold (3 consecutive mismatches) + sticky-once-confirmed, so a lone reordered checksum can't flash a false desync.connection.rs): gradedPeerLink{Live/Interrupted/TimedOut} driven bylast_recvagainst 2 s / 5 s thresholds +DisconnectReason::PeerTimeout, builder-configurable. Deliberately far above Mesen's known-too-low ~150 ms (rationale documented onPeerLink— a single lost 1 Hz ping or LTE retransmit spike must not trip it; matches GGPO/Parsec grace windows).Quality pass
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. Themovietarget found two real OOM DoS paths inMovie::deserialize(untrustedframe_countpre-sizing a multi-GBVec; a zero-widthbytes_per_frameloopingframe_countempty pushes) — both fixed byte-identically for valid input, with a regression test.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).rustynes-netplay102 passed / 2 ignored (incl. new lobby / delay / desync-status / PeerLink tests) ·rustynes-coremovie 44 passed (incl. determinism round-trip + new OOM regression) · 8 fuzz targets build + smoke clean.Notes
docs/STATUS.md/to-dos/ROADMAP.mdreconciliation (the final capstone doc-sync step) and thedeploy/RA auth-proxy work (a concurrent v2.1.10 agent owns it).fd82485; the maintainer runs the release ceremony / rebase. Do not merge.🤖 Generated with Claude Code