Preserve negotiated content and dual-raster pass gates. Retain both fixture assertion sets and HD-off seams. Regenerate both BIOS with actual native emitter. Fix type-only rollback corrections and exclude extra-pad changes from digital-only promote gates.
The episode kept one peer-baseline / ready / POST latch, so with three or
four seats the first reply answered for everyone, and the one early
BASELINE stash was overwritten by the next seat's. In R4's 4-peer run with
the host loaded every frame, two guests replayed a light-tip episode and
resumed while the host had not opened it; host and guests then waited on
each other (pcap_freeze / wire_hole) until the stall timeout.
- BASELINE, ready-ACK and POST go through rnet_rb_quorum: the episode moves
on only when every other occupied seat has answered, and a digest from
any seat that differs fails the comparison. Early BASELINEs stash per
seat. Two seats behave as before.
- Light tip skips the ready RTT only with one peer; with more, every seat's
ready is how the others know it rolled back to the same load.
- A follower that has not simulated the load tick yet defers and joins when
it gets there (no NACK, no zombie-load refusal for that join).
- Seal, ready and verify timeouts restart while a seat still owing a reply
advances its input tip: they fire only when nothing moves.
- psx_netplay_peer_seats(); pins recomp-net rb_quorum.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four-peer rollback desynced under load (R4, host behind): the guests
predicted the slow host's pad, hash-confirmed the tick among themselves
through the one shared peer ring, then promoted the host's real pad into
history only (hc-silent promote) while the host simulated it. The host
stayed forked; its recovery episode was then refused as a "zombie load"
behind the guests' (wrongly) agreed tip.
- Hash confirm is a quorum over every occupied peer seat
(rnet_hc_set_peer_mask / note_peer_from, recomp-net) for three or more
seats; two seats keep the one-peer chain.
- A peer more than 4 ticks behind the others' inputs sheds its own view
(psx_mod_render_local_view status FAST_FORWARD, held 60 ticks), so the
presentation-only pass goes before the simulation slips.
PSX_NET_LOCAL_VIEW_SHED=0 keeps it.
- The admit-stall watchdog counts time without progress: a remote input tip
that advances restarts it, so a slow peer is waited for.
PSX_NET_ADMIT_STALL_MS (20000) and PSX_NET_LIVENESS_MS (1500) configure it.
- Pins recomp-net fix/rollback-span-stall.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psx_mod_render_local_view() runs a title's draw code in the render-pass
sandbox on a forward netplay frame and keeps the drawn display rect in the
OpenGL presenter's surface only. The authoritative CPU VRAM, guest RAM,
rollback snapshots and digests keep the canonical frame; the presenter shows
this peer's own image. A committed image cancels any local-view crop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title whose netplay mode draws every seat's view into one frame (so all
peers keep identical guest state) can now ask the present path to show only
this peer's rectangle of the display: psx_netplay_present_local_view(x, y,
w, h), usually picked from psx_netplay_local_slot(). The GL dual-raster
present shows it from the internal-resolution surface, the software present
crops its staged frame, both at 4:3, and the headless present-image ring
records the same rectangle. The request is host-only (never serialized or
guest-visible), lapses PSX_NETPLAY_LOCAL_VIEW_HOLD ticks after its last
renewal so menus fall back to the full frame, survives rollback replays
without moving its stamp backwards, and is ignored offline. It wins over
[netplay] local_viewport.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PsxNetPad type 3 is NeGcon, packed lx = twist, rx = I, ry = II, ly = L
(the rollback row's stick view carries twist and L). Every decoder accepts
up to PSX_NETPAD_TYPE_MAX instead of clamping at JogCon, and normalization
keeps NeGcon pressures exact (only twist gets the centre deadzone).
psx_pad_apply_to_sio() is now the one PsxNetPad -> SIO path for offline
sampling, netplay delay/rollback apply and selfcheck replay, so a type
added once reaches the guest the same way everywhere. Selfcheck replay now
matches offline sampling on multitap seats with the analog-on-tap hack.
psx_netplay_normalize_pad moves to a pure TU so tests link the real one
instead of a mirror. Pins recomp-net to the branch that keeps type 3.
Other titles: pads never carry type 3, so SIO bytes are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- psx_netplay_seat_count() and psx_netplay_sim_pad(seat, out): the pad row
last published to SIO for each seat at the current simulation tick,
following delay-sync publication and rollback's sealed overrides, for a
game hook whose title reads more players than its SIO protocol exposes
(R4 Link Battle). Host-only cache, never serialized; republished on
every admitted or replayed tick.
- Load barrier: while a slower peer is still applying a save, neither
mode waits for confirmation from an already-frozen peer (rollback did
this already); after mutual ready, both modes resume on a confirmed
first tick instead of rollback inventing it, so peers cannot run
different inputs from the identical restored state.
From the R4 Link Battle branch; its headless input probe is dropped
because master already honours debug input on headless peers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port recomp-net's host-as-relay-over-ICE wiring into psxrecomp's forked lobby
client: waiting-room RNetHostIce proof (one agent per guest), relay_via "ice"
in published caps (pref default on, launcher toggle), launch handling for
transport_ice_hub (no bind, no dial; ice_not_connected refuses the launch),
and adoption of the connected agents into the session (hub on the host, 1:1 on
a guest). Lobby seat -> session slot goes through the launch's port_of_slot
table (host_spectates, moved seats); transport choice never sends an ICE-hub
launch to the single-agent ICE path or the SFU. Legacy advertised-port relay
and SFU paths are unchanged. ROM-free test: netplay_ice_hub_test.
lib/recomp-net is pointed at the local recomp-net feat/ice-hub (748f053);
that pin is real only once recomp-net is pushed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An online room this build hosts asks the lobby server for transport "host"
(match_caps.relay): the host carries the match on its own UDP port and
guests dial it; the server's relay (SFU) is the fallback when a guest's
probe fails (recomp-net-server WS_LOBBY.md "Host relay"). The mechanics are
recomp-net's recomp_net/host_relay.h, pinned here (lib/recomp-net b0751b7);
this engine's own lobby-client copy gets the same glue as rnet_lobby_client:
the cap (emitted only when asked), the launch's transport ->
PsxLobbyJoinInfo.transport_host (force_input_relay 0; the host accepts
first / hubs instead of dialling the guest's LAN address), each seat's
published path / path_fresh, host_relay_step() from psx_lobby_pump, and
unmap on leave / disconnect.
psx_netplay: transport_host takes the LAN / hub path instead of the "MotK
online needs the SFU" rule. main.cpp: relay_host_get/set + relay_status
for recomp-ui (Network Settings / MATCH SETTINGS / ROOM "Match Relay"),
transport_host on the launch and into PsxNetplayConfig, and the
host==guest-advertise SFU inference skipped on a host launch; all under
RECOMP_LAUNCHER_HAS_HOST_RELAY so older recomp-ui pins still build.
Compile-checked against workspace recomp-ui 58604b0 (MOTK scratch
configure: full build and link).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Host may sit in a spectator seat: session slot 0 stays the sim authority
with a muted pad and no port; slot->port map keeps players on the port of
their lobby seat (host first). Rematch path copies the same plan.
- Seat self-service on LAN (MOTK5 SEATMOVE / SWAPASK / SWAPANS / SWAPRES)
relayed by the host; online swap requests accept a gallery seat as target.
- Lobby chat ring + join/leave lines; country codes from the server.
- Keybinds: a key drives one input per player (binding S to Cross unbinds it
from Circle), and keybinds.ini is re-read after the rematch launcher too.
- LAN registry file anchored to the exe directory.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1dT6h1ZshMVUUVKTPxsCD
Seat 1 (P2) can bring its LOCAL slot-1 card to a match: it uploads the card
to the host at launch over the new recomp-net MEMCARD op, the host installs
it as the match's slot-2 card, and the usual card broadcast carries it to
everyone. The host's real slot-2 file is rebound to netplay/guest_card2.mcd
for the session and restored on shutdown; guests already sandbox both slots.
Lobby side: a per-seat memcard_offer on set_ready (has_card, share), a host
allow flag in match_caps, and guest_memcard_active settled ONCE by the host
at start and shipped with the launch, so a toggle racing the start cannot
split the room. LAN carries the same over MOTK5 UPDATE / MEMCARD / JOIN
tail / a trailing START line. PSX_NET_GUEST_MEMCARD=1 for CLI sessions;
a host waiting on a card that never comes stalls at a named phase
(mc_guest_wait), not a silent "ok".
Two latent guest-sandbox bugs fixed on the way: a disabled slot 2 had no
bound path, so a host blob carrying a card for it failed to import and the
guest stalled forever; and leaving the sandbox re-bound a disabled slot to
the sandbox file. Both slots are now bound in the sandbox and restored per
slot (memcard_rebind_path, which can also unbind).
Lobby chat: the client keeps a ring fed by the server's echo (op:chat);
LAN rooms relay through the host (MOTK5 CHATREQ guest->host, MOTK5 CHAT
host->everyone). Wired to the launcher's chat_send/chat_count/chat_get.
Also carries the spectator-seat work that was in the tree alongside this
(spectator seats in the lobby client, spectator_wire_slot in the launch).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1dT6h1ZshMVUUVKTPxsCD
Bumps recomp-net so empty lobby seats no longer block READY/admit; launch
builds the mask from seated members (online + LAN).
Co-authored-by: Cursor <cursoragent@cursor.com>
it stops checking the entire PSX disc for full hash check every time the launcher runs, assuming the user's hash checked it when selecting the disc the first time and saving it to settings, so it just validates the file name and size instead, then it shares lifecycle with the launcher and the game to load the game faster, and several optimizations were made to loadstate paths which I think in the end relied on reanchoring host timings to the restored clock that came with the state
Games pass MAX_PLAYERS (2..5, default 2) to bake PSX_MAX_PLAYERS. SCPH-1070
multitap maps pads 0–3 on port 1 and pad 4 on port 2. Lobby max_slots and
recomp-net slot_count follow the game player count; MotK stays at 2.
Co-authored-by: Cursor <cursoragent@cursor.com>