.cursor/skills/multiplayer-run-sync/SKILL.md
How WOD Brains timer runs are shared (leader/participant, presence, monotonic 10Hz simulation clock, countdown start, timeScale, share UX) and what to check before modifying run engine or UI.
npx skillsauth add jdconley/wodbrains multiplayer-run-syncInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill documents how “shared runs” work in WOD Brains, and the common gotchas when changing the run engine, API, or UI.
timerPlan + events + simNowMonoMs.tickMs=100) and renders smoothly at 60fps using interpolation.apps/worker/src/run-actor.tsResponsibilities:
onlineCount = this.ctx.getWebSockets().length (counts each tab/device connection).serverNowMonoMs) using performance.now() deltas.clock storage key) so it can recover across hibernation.timeScale is part of run settings and is included in every snapshot.start (409) to keep determinism simple.Snapshot envelope fields (key ones):
serverNowMonoMs (number, int-ish)onlineCounttimeScaletimerPlan, events, and derivedapps/worker/src/app.tsKey routes:
GET /api/runs/:runId → proxies DO /snapshotGET /api/runs/:runId/access → { canControl: boolean } based on timer_runs.ownerUserIdPOST /api/runs/:runId/events:
timer_runs.ownerUserId{ error: 'view_only' } for participantsatMs must be an int (round it before sending to DO)PATCH /api/runs/:runId/settings:
/settingstimeScale before startapps/web/src/pages/run.tsResponsibilities:
GET /api/runs/:runId/access and sets canControl.view_only; UI flips to participant mode.serverNowMonoMs.serverPerfOffsetMs so:
estimatedServerNowMonoMs() = performance.now() + serverPerfOffsetMsupdateMonotonicOffset) to avoid time jumps.advanceFixedStep each animation frame:
dttickMs=100maxCorrectionPerTickMs)start event in the future:
startAt = round(estimatedServerNowMonoMs()) + 10_000startedAtMs - simNowMonoMs so all clients show the same countdown.Participant · N online for participants.Leader label when onlineCount > 1.x N when timeScale !== 1.localStorage) for participants.split events, but UI merges local + server splits for display.packages/core/src/sim-clock.tsupdateMonotonicOffset(prev, sample, smoothing):
{ serverNowMonoMs, clientPerfNowMs }advanceFixedStep(state, dt, targetNow, config):
maxCatchupTicks prevents spiral-of-deathPolicy:
/r/<runId>?timeScale=100PATCH /api/runs/:runId/settings { timeScale }x N if it’s not 1 (but cannot change it).Gotchas:
navigator.share({ title, text, url }) when available.Workout at the same time with friends. You're invited to join my workout live on WOD Brains.Invite friends to this workout/w/<definitionId> (so others can create their own runs).Try this workout on WOD Brains. Start it and invite friends to join live.Date.now() for the simulation clock. Wall time is only for DB/history.atMs → round in the worker API and in the client event generation.pnpm -C packages/core test
deriveRunState pre-start behavior and sim-clock correctness.pnpm -C apps/worker test
pnpm -w exec playwright test --config /Users/jd/src/wodbrains/playwright.config.tsNotes:
STUB_PARSE=1.#startOverlay.page.addInitScript and assert the shared text/url.documentation
UI design guidelines for WOD Brains app - mobile-first, app-like design with consistent patterns. Use when making UI changes.
testing
Always create and run tests affected by changes, including Playwright for UI changes. Use when modifying Wodbrains features or UI.
development
Run Wodbrains worker parse evals and live Gemini tests locally using Wrangler `.dev.vars` keys. Use when you need to run `parse.evals.test.ts` / `parse.gemini.test.ts` against the real model (RUN_LIVE_AI_TESTS=1).
documentation
Regenerate README screenshots and the demo video for WOD Brains.