A couch multiplayer game
Server-authoritative realtime with optimistic CAS — a shared tally game two people play from their phones.
Multiplayer on quickable is the realtime primitive used symmetrically: every player's browser sends intents, the server commits them one CAS at a time against a single KV key, and every committed move fans out to all connected sandboxes. No game server to write, no websocket plumbing — the platform is the game server.
What the platform guarantees
- No lost acknowledged move. An intent is acknowledged only after its KV commit; a move computed from stale state fails CAS server-side and is retried from a re-read.
- Idempotent retries. Each intent carries a client
cmdId; a retry within the dedup window (60 s) returns the recorded result without re-applying. - Durable resume. State lives in KV. A player who disconnects re-syncs with one
rt.resume {fromRev}and receives the state plus every missed move. - Cheap ephemera. Cursor-class traffic (
rt.cursor) is fire-and-forget fanout with no persistence — right for pointers and presence, wrong for score.
The game
A shared tally: anyone can add points to either team; everyone sees the same committed truth. Create it like any app (see the companion guide for the bootstrap and MCP/REST calls):
api_app_create { "name": "tally" }
api_material_put { "app": "tally", "path": "app.tsx", "src": "<below>" }import { rt, state } from "@quickable/bridge";
import { useEffect, useState } from "react";
const TEAMS = ["red", "blue"] as const;
export function App() {
const [snap, setSnap] = useState(state.get());
useEffect(() => state.subscribe(setSnap), []);
const doc = (snap.state ?? {}) as Record<string, number>;
return (
<main style={{ fontFamily: "sans-serif", padding: 16, display: "flex", gap: 24 }}>
{TEAMS.map((team) => (
<section key={team} style={{ textAlign: "center" }}>
<h1 style={{ color: team }}>{doc[team] ?? 0}</h1>
<button type="button" onClick={() => rt.intent("inc", { key: team })}>
+1 {team}
</button>
</section>
))}
<small>rev {snap.rev}</small>
</main>
);
}Both players open $BASE/a/<ns>/tally on their phones. Each tap is
api.rt.tally.intent {type:"inc", payload:{key:"red"}} under the hood; the committed
result — delta plus full post-commit state — arrives at every open sandbox and both
screens tick in lockstep. Mash both buttons at once from both phones: CAS serializes every
move, and revisions stay gapless per client (a gap triggers an automatic resume).
The sandbox API in one look
Inside the iframe, @quickable/bridge (injected, no install) is the whole surface:
| Call | Meaning |
|---|---|
rt.intent(type, payload) | Apply one server-authoritative move; resolves {rev, result, dedup} after commit. |
rt.resume(fromRev) | Authoritative state + missed deltas — reconnect/first paint. |
rt.cursor(payload) | Fire-and-forget ephemeral fanout (presence, pointers). |
rt.onEvt(fn) | Raw app-scoped events (evt.<app>.…), unsubscribe returned. |
state.get() / state.subscribe(fn) | The live {rev, state} snapshot, kept fresh from the event stream. |
call(subject, payload) | Generic typed call — the parent forwards only bridge-grant subjects. |
The deployed reducer is the shared JSON document (set / merge / inc / del). Game
rules beyond that (turn order, legality checks) belong in a custom server-side reducer —
the CAS/dedup/replay machinery is shared and reducer injection is a core-service
extension point (RtDeps.reduce), not something untrusted clients can override.
Who may join
Rendering and playing requires a bridge session for the app
(api.session.<app>.mint, owner-only, bearer TTL ≤ 1h) — the shell connects with it and
it can reach exactly api.rt.<app>.> plus that app's events, nothing else (broker-denied
from material and app management). Sharing a game means the owner mints sessions for its
viewers; an expired session re-resumes with a fresh mint. Both players share one app's
state by that app's design — that is the multiplayer feature, scoped to exactly that
app (D4).
An LLM builds a visual companion
Bootstrap a namespace, hand your agent the MCP endpoint, and let it create and hot-modify a visual the user interacts with.
Operations
Observability through Victoria, the durable-state inventory, ramp-to-zero and restart-idempotency, the standing probes, and the honest residuals.