feat: add plugin-multiplayer-countdown (synchronized group timer) - #41
Conversation
A synchronized group timer (countdown or count-up) for the jsPsych multiplayer API. Every participant pushes its own start timestamp into its own slot; each client derives the displayed time from the minimum timestamp across all slots — a coordination-free consensus (no anchor, no single point of failure) in the spirit of plugin-multiplayer-role. Late joiners and refreshes resume at the group's real remaining time. The pure consensus core (startedAtKey / resolveStartedAt / computeRemaining / computeElapsed / formatTime) is exposed as statics on the default export so demos can render their own synced display. Built against a local interface mirroring the multiplayer API (jsPsych#3694), so it carries no build-time dependency on the unreleased core. A visually-hidden aria-live region announces the final 5 seconds only, avoiding per-second screen-reader spam. Ships a README and an illustrative two-tab local-adapter example (examples/countdown-timer.html). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A synchronized public-goods game (two-tab, local adapter) that showcases plugin-multiplayer-countdown's exported statics rendering a shared contribution deadline inside a normal html-button-response — the plugin's flagship "synced timer during another trial" use. Two players simultaneously contribute to a multiplied common pool within one timed window; the deadline ticks identically on both screens off the minimum-across-slots consensus start. Duration-bound pacing, unlike the turn-based ultimatum demo. Adds an examples/README.md section for it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…p 5) Replaces plugin-multiplayer-draw's own per-client duration timeout with a consensus clock rendered from plugin-multiplayer-countdown's exported statics, so the room closes for every participant within skew/latency of each other instead of on independent unsynced timers. Also adds the draw-room.html README section that was missing since PR #34.
704c9cc to
0ba146e
Compare
…rder labels Strokes on the shared canvas aren't attributed by name anywhere in the UI, so asking for a display name bought nothing beyond a nicer-looking roster. Replace it with "Player N" labels derived from the joinedAt timestamp the lobby already pushes, removing a trial and the plugin-survey-text dependency.
Updates the jsDelivr preview SHA to 7b1d96a0576032d15bd0d7c434f01b2d614f3a85 so the demos load the update() function and recent fixes from #3694.
Replaces two manual get(id) -> spread -> push sequences (the "I'm done" button and the draw-start-timestamp registration) with the new update() convenience method added to #3694.
…ng design doc countdown-core.ts referenced docs/countdown-plugin-design.md, which is not part of the package; the analysis lives in the README's Limitations section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…kage - Drop the draw-room retrofit and the chat-room/ultimatum re-pins from this branch: the re-pins are PR #44's scope, and the draw-room rewrite moves to its own follow-up PR so this one stays 'add the countdown package' plus its two dedicated demos (countdown-timer.html, public-goods-local.html). - Regenerate package-lock.json from a clean npm install: the previous diff stripped resolved/integrity fields from 808 entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed 458e0bd: merged main and narrowed this PR to its core scope per review — the draw-room retrofit moved to #51 (stacked on this branch), and the chat-room/ultimatum re-pins were dropped as #44's scope. This PR is now purely 'add the countdown package' plus its two dedicated demos ( |
📦 New package — trusted-publishing bootstrap neededThis PR added one or more new packages. npm trusted publishing (OIDC) can't be configured for a package that doesn't exist yet, so a maintainer must do a one-time bootstrap per new package. After that, releases publish automatically via OIDC ( Before running the commands below: npm
|
Summary
Adds
plugin-multiplayer-countdown, a synchronized group timer (countdown or count-up) for the jsPsych multiplayer API, plus two illustrative examples.Every participant pushes its own start timestamp into its own slot on trial start, and each client derives the displayed time from the minimum timestamp across all slots — a coordination-free consensus (no elected anchor, no single point of failure) in the same spirit as
plugin-multiplayer-role's ordering. Min is order-independent, so every client converges on the same value with no coordination, and late joiners / refreshes resume at the group's real remaining time for free.Because
pushreplaces a whole slot, the timestamp is merged into this participant's existing slot (read-own → spread → push) so it never clobbers role/joinedAtmetadata, and the push is idempotent on refresh (keep-if-present).The pure consensus core is exposed as statics on the default export (
startedAtKey/resolveStartedAt/computeRemaining/computeElapsed/formatTime) so demos can render their own synced display during another trial.What's included
packages/plugin-multiplayer-countdown/— plugin + pure core (countdown-core.ts), coded against a local interface mirroring the multiplayer API (jsPsych#3694, unreleased), same seam pattern as the merged chat/ready plugins. 41 tests (24 core + 17 trial),tscclean, build emits all bundles with statics present.examples/countdown-timer.html— a "barrier sandwich" two-tab demo (sync lobby → countdown → sync barrier → results). Browser-verified.examples/public-goods-local.html— a timed public-goods game rendering a shared contribution deadline from the exported statics (the flagship "synced timer during another trial" use). Chosen over a timed ultimatum because its pacing is duration-bound (simultaneous), unlike the turn-based ultimatum game. Browser-verified two-tab; the min-across-slots consensus resolved starts 2 ms apart and preserved all unrelated slot keys.examples/README.mdsections.Design notes for review
[0, duration]is the mitigation (failure direction is "ends early"); a deterministic outlier-filter escape hatch is designed-but-deferred. The README states this plainly, alongside the monotone-not-smooth and slots-outlive-members caveats, and recommends composing behind a ready/sync barrier.plugin-multiplayer-sync/readyafterwards for a hard barrier.aria-liveregion announces only the final 5 seconds, the point an SR user needs to know the group deadline is about to auto-end the trial.pushis surfaced as a loudconsole.errorand the trial continues off the local-fallback start, rather than being made fatal — a sync subscribe-trial has no trial-promise to reject (unlike ready'sawait push). I believe continuing is the right call (the display can still run from local time), but flagging it in case you'd prefer it fatal.🤖 Generated with Claude Code