Public: anyone can read this; the crew can change it.

forked from trevato/cs

A member of a room is the engine's: the socket, the clock and the four sentences stop being the shooter's #370

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Closes #334: the page's side of a room moves into the engine as `createMember` in `src/engine/member.ts`. The page's side is the socket and its open, errored and closed states, how long since the last snapshot arrived, the tick counter, the reused input buffer, and the rule for which of four sentences the page shows. `src/client/session.ts` now keeps only what belongs to Counter-Strike, and #319's…

shipped production is serving it deploy ae07c2d

opened this

Closes #334: the page's side of a room moves into the engine as createMember in src/engine/member.ts. The page's side is the socket and its open, errored and closed states, how long since the last snapshot arrived, the tick counter, the reused input buffer, and the rule for which of four sentences the page shows. src/client/session.ts now keeps only what belongs to Counter-Strike, and #319's session.test.ts passes without a single change.

  • The seam. The game passes in its codec as plain functions: which first byte marks a snapshot, how big the input buffer is, readText, readSnapshot and writeInput. Next to the codec it passes hear(message, world) → world, which is what a text message does to its world (a hello builds one; the rest keep the one they had). It can also pass landed(got, world). The engine never names World, Hello, applySnapshot or SNAPSHOT_FRAME. What applySnapshot returns about your own body goes to landed unchanged, and the shooter writes it onto me there, so the split #316's prediction relies on is kept.
  • The sentences and the threshold. They are the game's text under the engine's rule. Sentences and behindMs have no default, for the same reason the hub's caps have none. The docstring says so, and member.test.ts checks the rule with a fake game's words, including that exactly behindMs still counts as playing and one millisecond more is behind.
  • Proof without a browser. member.test.ts plays a whole room of a counter game made of plain objects: a hello, names, snapshots (as Uint8Array and as ArrayBuffer), input, and a drop. It tests every existing refusal separately:

- a text frame that doesn't parse - a binary frame whose first byte isn't the snapshot kind - a frame before hello, or while the game has no world - a snapshot the codec refuses, which also leaves the clock where it was - data of any other type - press while the socket isn't open, which isn't counted as a tick

  • The door. index.ts exports createMember, Member, MemberCodec, MemberOptions, MemberSocket and Sentences. surface.json moves to 0.10.0 because names were added. GUIDE.md has a ### member section after rooms, with its measured weight (0.9 kB raw, 0.6 kB gzipped).
  • The shooter's names. session.ts still exports Socket, now as an alias of MemberSocket, along with Standing, Session, the sentences and BEHIND_MS. main.ts needed no change.

Two small refusals are new; neither is reachable with a real browser socket. The member ignores anything the socket delivers after it closed, and an open event after a close doesn't make it live again. session.test.ts passes either way, and member.test.ts tests both, because its fake socket keeps delivering after a close.

Not done: #316 (prediction) and interpolation are left alone, and I didn't write a second codec for the example game (#326).

To try it: run bun run test (typecheck and 1494 tests, all passing), then join a room in the preview. The connection line should read "Joining the room…", then "In the room", and should say the connection is behind if snapshots stop arriving for more than half a second.

🤖 Generated with Claude Code

Closes #334

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs