Skip to content

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

243 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

recomp-net

Portable delay-sync netcode library for recompilation / modern-runtime hosts (N64Recomp, PSX recomp, and similar). Classic lockstep: every peer stalls until remote inputs for wire = sim + D arrive. Optional ICE transport via libjuice.

BattleShip’s netplay stack is a design reference only — this repo does not vendor SSB64 code and does not implement rollback, automatch, or game UI.

Features (v0.1)

  • Opaque per-slot input blobs (RNetInputSample)
  • Fixed input delay D with try_admit / advance host loop
  • Optional fixed startup profile agreement from actual capabilities and qualified profiles, with a common portable fallback
  • UDP LAN transport and optional ICE mux
  • Portable, ranked local IPv4 interface discovery for LAN launchers
  • Synchronous RFC 5389 external IPv4 discovery through configurable STUN
  • Host-owned signaling callbacks (ICE); lobby / matchmaking is not in this repo — see docs/lobby.md and open-source recomp-net-server
  • C11, CMake, MIT license

Lobby

This library has no lobby binary. The MotK / psxrecomp / SNES WebSocket lobby server is the open-source sibling recomp-net-server (default ws://netplay.retcomm.net:8765, or self-host locally). Client-facing protocol notes: docs/lobby.md.

Build (LAN, no ICE)

cmake -S . -B build -DRNET_ENABLE_ICE=OFF
cmake --build build -j
ctest --test-dir build --output-on-failure

Build with ICE

Optional ICE transport uses libjuice.

By default (RNET_ICE_BUNDLE_STATIC=ON) CMake FetchContent-builds a static libjuice and links it into recomp_net, so host binaries do not need a distro libjuice.so at runtime (network required at configure if juice is not already cached). Set -DRNET_ICE_BUNDLE_STATIC=OFF to use find_package(Libjuice) / RNET_LIBJUICE_ROOT / third_party/libjuice instead (shared system juice OK).

cmake -S . -B build-ice -DRNET_ENABLE_ICE=ON
cmake --build build-ice -j

ICE needs juice at link time — without a local copy or FetchContent access, -DRNET_ENABLE_ICE=ON will fail configure. Host apps supply signaling via RNetHostVTable.on_signal / rnet_session_push_signal (no automatch server).

Quick LAN demo

# terminal A (slot 0 / sim authority)
./build/lan_delay_2p 0 7777

# terminal B (slot 1)
./build/lan_delay_2p 1 0 127.0.0.1:7777

Integrate

add_subdirectory(path/to/recomp-net)
target_link_libraries(your_host PRIVATE recomp_net)
#include "recomp_net/recomp_net.h"

/* Implement RNetHostVTable: sample_local, publish, optional on_signal/now_ms */
RNetSession *s = rnet_session_create(&cfg, &host);
/* Host-style listen: pass NULL/empty peer and learn the first inbound client. */
rnet_session_start_lan(s, "0.0.0.0:7777", NULL);
for (;;) {
    rnet_session_pump(s);
    if (rnet_session_try_admit(s, rnet_session_sim_tick(s))) {
        /* run one authoritative sim step */
        rnet_session_advance(s);
    }
}

See docs/host_integration.md.

Online topology and host relay

The lobby server launches every online match on one of three paths and always falls back to its own UDP relay (the SFU) unless every guest proved the host path:

  • Lobby UDP relay (SFU) -- the fallback; everyone dials one server endpoint.
  • Host relay over an advertised port (recomp_net/host_relay.h) -- the host holds a UPnP / NAT-PMP / STUN-mapped UDP port and runs the LAN hub; guests probe it.
  • Host relay over ICE (recomp_net/host_ice.h, default on, needs RNET_ENABLE_ICE) -- no port to forward: the host answers one ICE agent per guest, guests offer, the agents connected in the waiting room carry the match (rnet_session_start_ice_hub_adopt / rnet_session_adopt_ice_agent). No TURN; a guest that cannot connect directly reports fail and the server relay carries the match. rnet_lobby_set_relay_via_ice(0) opts out. Details: docs/architecture.md, docs/signaling.md, docs/host_integration.md.

Recommended host / recomp-engine patches

recomp-net alone does not make a recomp title feel good online. The host loop and engine (snesrecomp / psxrecomp / game main) need a few patterns that shipping MotK / SNES titles already use. Prefer putting these in the shared engine / facade so every game inherits them — not one-off copies in each title’s main.c.

Frame loop (stability + feel)

Do Why
On admit stall: re-present the last framebuffer, poll events, pace ~1 frame (~16 ms). Do not busy-spin or skip present. Avoids frozen window / OS “not responding” and audio underrun while waiting on the peer.
Prefer rnet_session_wait_recv (LAN) over bare Sleep in the admit barrier. Wakes when UDP is readable instead of burning the delay budget blind.
Latch delay-sync starvation: after sustained admit misses, keep pump (retransmit) while holding; clear when remote_lead >= D for a few frames. Recovers from jitter without inventing inputs.
After starvation clears, resume ~1 sim / wall frame and let the input buffer rebuild. Avoid large “recovery burst” catch-up by default. Turbo catch-up feels worse than a short soft buffer rebuild.
Optional catch-up only when remote_lead > D, capped (e.g. env SNES_NET_CATCHUP_CAP; default 0 in snesrecomp). Drains surplus lead without fighting the delay runway.

Reference shape (SNES facade): snes_host_barrier_admit + snes_host_catchup_budget in snesrecomp — stall → held present; admit → RtlRunFrame (+ optional burst).

Pads, threading, audio (determinism)

Do Why
Use publish / published pads as the only controller source for locked sim ticks. Local-only pad reads desync peers immediately.
One thread owns pump + try_admit + sim advance (or an external mutex). Session API is not internally locked.
Disable wall-clock APU / audio “catch-up” while netplay is active; keep guest-frame–coupled audio. Independent audio catch-up advances time differently per peer.
Keep RNG, timers, and frame pacing deterministic; put host-authoritative entropy in the pad blob if needed. Library does not fix host desyncs.

Session lifecycle

Do Why
Poll rnet_session_peer_disconnected(~1500) and input_desync while waiting; soft-exit to lobby. Snappy leave instead of infinite stall.
Call rnet_session_send_bye before destroy. Peer can drop without waiting out the silence timeout.
After LOAD / savestate: host probe → apply → hard_resync + prime_delay_inputs on both sides; keep the app barrier until try_admit works again. Avoid tip/history collisions across the load epoch.
Rematch / soft-return: session_reset sticky LLE / frame gates; do not reuse a stale UDP session_id. Second match in one process otherwise inherits dead state.

Transport / ICE (online)

Do Why
Online MotK-style lobbies: always ICE (do not demote to LAN because of a rewritten private peer IP). Hairpin / wrong advertise breaks P2P.
Mint Coturn TURN; for carrier CGNAT / mobile hotspot, host Force TURN (force_relay) for all peers. STUN/prflx “success” is often flaky on CGNAT.
Keep library auto TURN fallback (FAILED / stuck / dead non-relay path) — both peers need a build that supports it. Answerer alone cannot re-offer; controlling must restart.
Ensure INPUT bundles cover the full delay prefix at start (D+1 ticks including 0). RNET_MAX_BUNDLE is 21; never truncate the low end of the window. Truncation deadlocks admit at sim_tick==0 (wait_remote_input).
Lobby match_caps host-authoritative for input_delay, force_turn, force_input_relay. Guests must not overwrite host delay/TURN policy on fill.

Where to implement

Layer Put here
recomp-net Session, ICE, TURN fallback, bundle size, protocol
Engine facade (e.g. snes_netplay / psx_netplay, snes_host_*) Barrier admit, starvation latch, catch-up budget, soft-exit, diag JSONL
recomp-ui Lobby Settings (delay, Force TURN, server input relay), UDP bind policy
Game title Thin identity / fill_match_caps, pad sample hook, present-held callback, connect-timeout modal

SNES-oriented checklist: snesrecomp docs/RECOMP_NET.md (“Per-game patches” + host loop). Library API detail: docs/host_integration.md.

Docs

Doc Topic
docs/architecture.md Layers, phases, admission
docs/protocol.md Wire packets
docs/signaling.md ICE signaling contract
docs/host_integration.md Hooking a recomp host
docs/address_discovery.md Selecting a LAN address to advertise
docs/lobby.md Lobby server contract (sibling repo)
docs/rollback.md Rollback mode contracts (feat/rollback)

Modes

  • Delay-sync (main, v0.1): shipped RNetSession lockstep used by MotK / snes / psx.
  • Rollback (feat/rollback branch): shared rollback architecture; first layer is the portable input contract (recomp_net/input_contract.h), with episode orchestration over a host snapshot/hash vtable planned next. See docs/rollback.md.

Non-goals

  • Automatch or matchmaking HTTP clients
  • Game-specific pad layouts, snapshots, or determinism fixes (host responsibility)

Chat filter

include/recomp_net/chat_filter.h: mask profanity and slurs in a chat line (rnet_chat_filter_apply). The word list is data/chat_filter_words.txt (many languages; see its header for the matching rules); after editing it run tools/gen_chat_filter_words.py to regenerate the baked-in copy, and copy the list to recomp-net-server's data/ so the server's Rust port stays identical. Every client calls it as a line lands in its chat ring, so rooms with no server (LAN) are filtered the same way. RNET_CHAT_FILTER=0 disables it. Tests: tests/chat_filter_test.c.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages