Skip to content

feat(nr-z4nw): players start a network game with one short room code - #3398

Merged
rmstdope merged 4 commits into
mainfrom
nr-z4nw
Oct 30, 2026
Merged

rmstdope merged 4 commits into
mainfrom
nr-z4nw

Conversation

@rmstdope

Copy link
Copy Markdown
Owner

Bead: nr-z4nw — Players start a network game with one short room code. Agreed experience: the bead's acceptance and docs/ui/nr-z4nw-room-code.html.

What changes

  • Network play no longer swaps two long NSR1-… codes. The host's browser opens a room on the liaison meeting service (wss://liaison.rmstdope.eu/ws) and shows its six-character code (K7Q-M4X) with Copy invite message and Copy code; the joiner types it into one field (capitals, dash put in by itself, I/O/0/1 and symbols never taken) and presses Join.
  • The WebRTC offer and answer cross through the room as signal {offer} / signal {answer}; each side leaves liaison once the direct link opens. Everything after that (the response-time check, sending the game, lockstep, resync) is unchanged.
  • A room that runs out (15 min) or whose joiner leaves before the swap is replaced by itself, with the agreed amber note. The joiner's errors (no such game, full, liaison out of reach or busy, host left) show under the field and keep the code; another build, too many wrong codes and no direct path end the step as agreed.
  • New: web/src/netplay/room_code.ts, web/src/netplay/liaison.ts (the only WebSocket user). Removed: web/src/netplay/code.ts. Reworked: session.ts, ui/play_together.ts, CSS, README, architecture.md.
  • The Playwright Play together spec plays liaison itself through routeWebSocket, so CI never reaches the real service.

Validation

  • ./scripts/gate-full.sh: passed (1078 Vitest, 14796 Rust lib, wasm tests, clippy ×3, Python, typecheck).
  • pnpm run test:integration:web: 136 passed, including the 9 Play together scenarios against the fake liaison.
  • Against the real liaison (two Playwright browsers on localhost, a temporary uncommitted spec): room Y8J-H6F was created, ZZZ-ZZZ gave "There's no game with that code.", typing the room code connected both ("Response time 0 ms · Great"), and Copy invite message put the agreed sentence on the clipboard.

Deviations from the plan

None. Two details settled during the build were added to the plan's Decided by me: a red message stays under the field until Join is pressed again, and reopening Join a game starts with an empty field.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WCDKexiF6SkDodmyKWmppK

rmstdope and others added 3 commits October 30, 2026 15:36
… instead of swapping codes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCDKexiF6SkDodmyKWmppK
@rmstdope

Copy link
Copy Markdown
Owner Author

Review (cold read, reviewed_head: cacd144cf551d510a2132acbd53c926d0c492cbf) — reviewed before merge under the producer's review practice, by an agent given the diff and the bead, and not the producer's reasoning.

The session state machine is sound. Traced: host renewal on peer-left and room-expired, the late peer-left ignored after the swap, the joiner reusing one socket across tries (the mine/attempt binding holds because joinTryFailed never tears down), Cancel while open() is still pending, and the 15 s limit on both sides. No defect found in session.ts or liaison.ts.

  1. Blocking: web/src/ui/play_together.ts drawJoin: field.maxLength = ROOM_CODE_LENGTH + 1 (7) cuts a pasted code short. It applies to the raw text before the input handler tidies it, and browsers truncate a paste to fit. Pasting k7q - m4x leaves k7q - m → K7Q-M, so Join stays disabled; pasting K7Q-M4X loses the X. The acceptance says the field "accepts a paste (of a code with or without the dash, spaces, or lower case)". The unit test "takes a pasted code…" passes only because it assigns field.value, which bypasses maxlength. Also, a full field takes no new paste unless the old code is selected first. Fix: drop maxLength (tidying already caps at six); test through a real paste/insertion path (e.g. Playwright insertText of k7q - m4x).
  2. web/src/ui/play_together.ts, the input listener: fixing a typo mid-code moves the caret to the end. Deleting the 7 of K7Q-M4X gives raw KQ-M4X, retidied to KQM-4X; reassigning field.value puts the caret after X, so the corrected character lands at the end. The acceptance chose one field because it is "easier to fix a typo". Fix: keep the caret by counting code characters before selectionStart and calling setSelectionRange; add a test. Suggestion, not blocking.
  3. Needs a navigator decision: web/src/netplay/liaison.ts, README.md: network play now works only from the origins liaison allows (https://neser.rmstdope.eu, http://localhost and http://127.0.0.1). Two computers using a self-hosted NESER reached by LAN IP (e.g. http://192.168.1.10:8000) are refused at the WebSocket upgrade and see "Network play can't start right now… Check that this computer is online". That used to work. The bead moves the meeting point to liaison, so this may be accepted, but neither the README nor the plan's Decided by me says so. At minimum, document it in the README.

…eps the caret in place

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCDKexiF6SkDodmyKWmppK
@rmstdope

Copy link
Copy Markdown
Owner Author

Answers to the review (fixes in the commit after cacd144c)

  1. Fixed. maxLength is gone; tidying already caps the code at six. A new unit test checks the field has no maxlength. The integration spec now pastes k7q - m4x through the keyboard (insertText over a selection), which a browser truncates when maxlength is set, and expects K7Q-M4X with Join enabled. A paste into a full field now keeps the first six code characters, as tidying does.
  2. Fixed. After retidying, the caret goes back after the same code character (setSelectionRange). A unit test deletes the 7 of K7Q-M4X, then types it back in place, then inserts past the dash.
  3. Documented, not changed. The navigator decided at filing (bead description, 2026-10-30) that liaison replaces the codes with no fallback, so pages liaison does not allow cannot play together; changing that is scope, not this build. The README's Play together paragraph now names the addresses that work (https://neser.rmstdope.eu, localhost/127.0.0.1), and the plan's Decided by me records it so the navigator can overrule it. The message shown is the agreed "Network play can't start right now." wording for the service being out of reach.

Follow-up review: not needed. The changes are two small, self-contained fixes to the field, each with its own test, plus a README sentence.

@rmstdope
rmstdope merged commit 402e55a into main Oct 30, 2026
11 checks passed
@rmstdope
rmstdope deleted the nr-z4nw branch October 30, 2026 15:22
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.

1 participant