Skip to content

feat: play the real game together in co-op rooms - #7

Merged
Tanner-Scadden merged 7 commits into
mainfrom
coop-real-world
Oct 30, 2026
Merged

Tanner-Scadden merged 7 commits into
mainfrom
coop-real-world

Conversation

@Tanner-Scadden

@Tanner-Scadden Tanner-Scadden commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Co-op rooms now run and draw the real game instead of the stand-in's dots (MULTIPLAYER.md, "What Phase 2 built"). Co-op stays hidden in production behind VITE_COOP; previews have it on.

  • The room's World (src/roomWorld.ts, swapped into infra/worker/gameRoom.ts and src/coop/seam.ts): stepWorld over the lobby's seats, inputs to intents, presses to seat actions as held values applied once, presence, interpolation, own-player prediction.
  • A binary wire (src/worldCodec.ts) read off the World's Schemas: quantized numbers by field, entity lists keyed by id, nested masks, list diffs, events by tick. Four players over a 60 ms round trip: 4.87 KB/s a player on average, 6.21 KB/s crowded, 8.81 KB/s at the crowded p95 (simulated); on this preview 2 and 3 pages played a real wave with round trips of 28 to 40 ms.
  • The client (src/coopPlay.ts, src/update/coopRun.ts): the game's own scene, HUD, shop, and touch controls from the room's World with the player's own seat focused and own camera; cosmetics and sounds from events; local hit-stop; presses to the room; no pause (LEAVE in the corner); READY in breaks; teammates' names, READY, and edge badges.
  • Checks: round trip and size tests, Story tests for the Submodel and the root's online run, a 4-player sim measurement, world:check with the room's adapter in workerd, mp:e2e with the real World, and tools/audit/coop.ts playing a real wave (shooting, a revive, the stall, READY) with 2 or 3 pages.
  • The pacing sims got longer timeouts: they ran close to 60 s on main's CI and timed out beside the new wire measurement.

🤖 Generated with Claude Code

The GameRoom ran the netcode stand-in (dots and walkers), and the World's
wire was JSON per field, flow fields and all.

src/roomWorld.ts is the netcode World over stepWorld: a fresh run seated
with the lobby's slots and the room's seed, each PlayerInput turned into
an intent and its held action value into a seat action on the tick it
changes, presence into setPresence, interpolation of players, zombies,
and bullets, and own-player prediction from walkLeadSeat, a new export of
the step's own walk. infra/worker/gameRoom.ts runs it in place of the
stand-in, a Normal campaign until the lobby picks a difficulty.

src/worldCodec.ts reads its codec off the World's Schemas: positions,
velocities, directions, and timers quantized by field name, literals and
tags as indexes, entity lists keyed by id, nested field masks, list diffs
that name only what left or changed, and the tick's events beside each
diff. Seeds, flow fields, stats, and single player's gates stay home.
Four scripted players over a 60 ms round trip average 5.5 KB/s each, 6.6
KB/s in a crowded wave (src/roomWorld.sim.test.ts).

The room now sends its last world before RoomClosed when a run ends
between messages. world:check bundles and plays the room's adapter in
workerd too, and fails on any runtime netcode code in it. SoundEffect
moved to src/soundEffect.ts so the step's types no longer reach Web Audio.
The dev page and mp:e2e play the real World, drawn as dots.
The co-op arena drew the stand-in's dots on a canvas of its own, moved by
the arrow keys, with no shooting, shop, or waves.

An online run is now the game's own scene, HUD, shop, and touch controls,
drawn from the room's World (src/coopPlay.ts). Each frame the client
derives its intent from its own input and camera, the co-op Submodel
sends it with any held press and hands back the World to draw (everyone
else 100 ms behind, its own player predicted from walkLeadSeat and
reconciled) and the events the drawing reached, each once. The root puts
its own seat in the player fields with focusSeat, so every view draws it
as the player with its own camera, and makes particles, shake, popups,
and sounds from the events on its cosmetic stream. Hit-stop freezes only
its own picture while the events wait in the session.

Swap, reload, traps, READY, buying, the exit offer, and leaving the
counter go to the room as seat actions (src/update/coopRun.ts); the
stall's dialog opens when the room puts the player at the counter. There
is no pause: the corner holds LEAVE instead, and START WAVE reads READY,
then READY! while the others choose. Teammates wear their seat's look,
show their names, READY over everyone ready in a break, and a badge of
their seat's color at the screen's edge when off screen. A run counts
toward the player's own bests and Munchopedia kills when it ends, sends
no analytics summary until RunSummary has co-op fields, and leaving the
room goes home.

tools/audit/coop.ts now plays a real wave with 2 or 3 pages: walking,
READY, shooting, a player going down and a teammate reviving them, the
wave cleared, a purchase at the stall, READY again, a rejoin, and LEAVE,
and reports each page's round trip, bandwidth, and corrections.
VIEWPORT=l844 or p390 shoots phones.
MULTIPLAYER.md still described the room's stand-in world and the JSON
wire as what remained.

It now has "What Phase 2 built": the room's World adapter, presses as
held action values, the binary codec, the client drawing the room with
the game's own scene, teammates' marks, the rules around an online run,
the measured wire (5.15 KB/s a player on average and 6.30 KB/s in a
crowded wave with four players), the checks, and what is left for later.
AGENTS.md lists the new modules and the co-op audit, and the audit
README describes the real wave it plays.
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

The four-player wire measurement played 240 s of game time beside the
other sim tests on CI's small runner, and the pacing sims timed out
waiting for the CPU.

It now plays 150 s, still into the crowded waves of wave 4, and the
recorded numbers follow it: 5.08 KB/s a player on average, 6.32 KB/s
crowded. The root's online run check also returns at once outside a
room, so single player's frames skip the run's Message match.
The pacing sims still timed out on CI beside the four-player wire
measurement, which held a worker for most of their run.

The measurement now plays 100 s, into wave 3's crowds, and each scripted
client draws every fourth frame while its inputs and prediction still run
every frame. The recorded numbers follow it: 4.87 KB/s a player on
average, 6.21 KB/s crowded, 8.81 KB/s at the crowded p95.
The dead air sweep took 37 to 53 s on main's CI runs against a 60 s
limit, and timed out at 68 to 85 s on runs that also play the co-op wire
measurement. It and the spawn variety sweep now have 150 s and 90 s,
which a stalled sweep still trips.
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Munch Manor preview

URL: https://munch-manor-pr-7.tanner-scadden.workers.dev

Analytics API: https://munch-manor-api-pr-7.tanner-scadden.workers.dev/api/health

Co-op dev page: https://munch-manor-mp-dev-pr-7.tanner-scadden.workers.dev (the stand-in world in a room: open it in 2 to 4 tabs)

Built from commit 6a8ac32. This comment updates on every push, and the preview is destroyed when the pull request closes.

On the preview, with 30 to 60 ms round trips, the scripted feeder walked
into a doorway's crowd and the whole team went down before the revive.

The feeder now walks unarmed only into zombies already in the parlour
while the others hold their fire, and the link is measured over the
fight while the room still sends. MULTIPLAYER.md records the preview
run: 2 and 3 pages through a real wave with no problems, round trips of
28 to 40 ms, and 1.7 to 2.1 KB/s a page in wave 1.
@Tanner-Scadden
Tanner-Scadden merged commit 08fae5e into main Oct 30, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
preview — fea2d812 Deployed Oct 30, 2026 by Tanner-Scadden via deploy #143
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