Skip to content

Add real-time multiplayer support (MultiplayerAPI + plugin-multiplayer-sync + adapter-multiplayer-jatos)#3694

Open
htsukamoto5 wants to merge 26 commits into
jspsych:mainfrom
htsukamoto5:multiplayer
Open

Add real-time multiplayer support (MultiplayerAPI + plugin-multiplayer-sync + adapter-multiplayer-jatos)#3694
htsukamoto5 wants to merge 26 commits into
jspsych:mainfrom
htsukamoto5:multiplayer

Conversation

@htsukamoto5

Copy link
Copy Markdown
Member

Summary

This PR adds opt-in, real-time multiplayer support to jsPsych through a two-layer architecture:

  • MultiplayerAPI (in core jspsych) — high-level API on jsPsych.pluginAPI for pushing data, subscribing to group session changes, and waiting on conditions. Backend-agnostic: all network logic lives in a swappable adapter.
  • @jspsych/plugin-multiplayer-sync — a synchronization-barrier trial plugin. Pushes participant data and waits until a wait_for predicate over the group session is satisfied. Packages the common push → wait pattern as a single declarative trial.
  • @jspsych/adapter-multiplayer-jatos — a JATOS group session adapter implementing the MultiplayerAdapter interface. Handles WebSocket join, group session reads/writes, and optimistic-concurrency retry.

The architecture is intentionally minimal: adapters only need to implement network I/O (connect, push, subscribe, getAll). All higher-level behavior (subscribe replay, wait fast-path, subscription tracking) lives in MultiplayerAPI so it is consistent across backends.

What's included

  • Core API (packages/jspsych): MultiplayerAPI class with connect, disconnect, push, get, getAll, subscribe (replays current state on registration), wait, communicate, cancelAllSubscriptions
  • Plugin (packages/plugin-multiplayer-sync): multiplayer-sync trial with push_data, wait_for, message, timeout, on_timeout, minimum_wait params; saves group, wait_time, timed_out
  • Adapter (packages/adapter-multiplayer-jatos): JATOS group session adapter with exponential-backoff retry on version conflicts
  • Demo (examples/group-quiz/): live quiz game demonstrating the full host+player flow in a single JATOS component; includes a build script (scripts/build-jatos-group-quiz.js) that produces an importable .jzip
  • Tests: 14 core API tests, 4 plugin tests, 14 adapter tests (32 total across the three packages)
  • Docs: docs/plugins/multiplayer-sync.md, docs/reference/jspsych-multiplayer.md, docs/developers/adapter-development.md, all wired into mkdocs.yml
  • Changeset: minor bumps for all three packages

Design notes

The MultiplayerAdapter interface is intentionally minimal so dropping in a second adapter (Firebase, a custom WebSocket server) requires no changes to core or plugin code. The adapter's subscribe() is future-only; replay is handled once in MultiplayerAPI.subscribe() (register-then-replay ordering prevents a TDZ crash when wait() references its own unsubscribe handle inside the callback).

Test plan

  • Run npm test in packages/jspsych, packages/plugin-multiplayer-sync, and packages/adapter-multiplayer-jatos — all 32 tests should pass
  • Build the Group Quiz jzip: node scripts/build-jatos-group-quiz.js (after npm run build on the three multiplayer packages)
  • Import dist/group-quiz-jatos.jzip into a JATOS instance, run as a group study with 2+ participants — one selects Host, others select Player
  • Verify the Group Quiz lobby, questions, reveal, leaderboard, and podium screens all function correctly

🤖 Generated with Claude Code

htsukamoto5 and others added 25 commits June 17, 2026 11:38
Adds MultiplayerAPI to plugin-api (Connect/Push/Get/Wait/Subscribe/Communicate
primitives, adapter pattern, subscription lifecycle tracking) and a
JatosAdapter package that implements the interface via JATOS group studies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 6 missing test cases: getAll, wait fast-path, subscribe fires,
disconnect state reset, pre-connect error guards, and double-connect error.
Move "types": ["jest"] into compilerOptions where TypeScript expects it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tsconfig: restore @types/node by using ["jest", "node"] so tsc passes
- MultiplayerAPI.connect() now rolls back adapter/participantId on failure
- add test for failed-connect rollback and retry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…TOS 3.x

- Use .NET ZipFile API on Windows to write forward-slash entry names
  (Compress-Archive produces backslashes which violate the ZIP spec)
- Rename metadata file from jatos_study_metadata.json to .jas extension,
  which is what JATOS 3.x import actually scans for
- Add required uuid fields on study, component, and batch
- Add missing JATOS 3.x fields: linearStudy, allowPreview, studyEntryMsg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Packages the multiplayer push -> wait pattern as a single declarative
trial, replacing the call-function (async/done) and NO_KEYS
html-keyboard-response + on_start + manual finishTrial idioms.

Params: wait_for (required), push_data, message, timeout, on_timeout,
minimum_wait. Data: group, wait_time, timed_out. Built on the existing
multiplayer plugin API; relies on jsPsych dynamic parameters so
push_data/message accept function forms.

Includes 4 jest tests (in-memory MockAdapter), README, and a rewritten
ultimatum-game example using the plugin for all sync points.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Point the JATOS archive builder at multiplayer-ultimatum-game-sync.html:
drop the plugin-call-function bundle (no longer used) and add the
plugin-multiplayer-sync bundle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port the 2-player-cap behavior from multiplayer-ultimatum-game.html: a
3rd+ participant sets gameIsFull in the lobby on_finish, disconnects,
and is routed to a "game full" screen; the game timeline is gated on
!gameIsFull. Keeps both example versions in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Windows branch that zips via .NET ZipFile with forward-slash
entry names (matching build-jatos-ultimatum.js), so the archive imports
correctly on Windows. The existing zip -r path still serves macOS/Linux.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug 1 — players who joined before the host were invisible. The host
subscribed to the group session but subscribe() only fires on future
updates, so anyone already sitting in the lobby barrier never triggered
a render. The host now renders the current snapshot once right after
subscribing.

Bug 2 — players could be stranded forever on a loading screen. Barriers
waited on exact phase equality (e.g. phase === REVEAL), but JATOS does
not guarantee a client observes every intermediate snapshot, so a
skipped phase made the condition permanently unsatisfiable. Introduce a
monotonic step counter (phaseStep) in protocol.js: the host stamps every
push with a step that only increases, and players wait with
hostStepValue(group) >= phaseStep(...). A >= test on a monotonic value
can never be missed.

Applied across all three example files (index.html is the one the JATOS
build ships; host.html/player.html are the split versions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers the full MultiplayerAdapter contract against a mock of the jatos
global: construction (missing-global error, participantId derivation),
the connect handshake (resolve on open, reject on error), getAll/get
reads, subscribe fan-out + unsubscribe, and push — including the
optimistic-concurrency retry path (retries a version conflict to success,
and throws after exhausting all 8 attempts), driven with fake timers.

Documents that the adapter's subscribe() is intentionally future-only;
whether core should replay current state on subscribe is a separate open
question deferred pending maintainer input.

No changeset: test-only change, no published behavior affected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
subscribe() now emits the current group session snapshot once synchronously
on registration (register-then-replay ordering), matching Firebase onSnapshot
semantics and eliminating the asymmetry with wait()'s existing fast-path.
The replay fires after the unsubscribe handle is built, preventing the TDZ
crash that would occur if wait()'s callback tried to call unsubscribe() before
it was assigned. Two core tests updated to account for the leading replay emit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete host.html and player.html — both were stale divergent copies of
the host and player flows that had since been fixed and unified into
index.html. The jzip build already shipped index.html as the sole
component; the deleted files were never referenced by the build script.

Also clarify the adapter.subscribe() comment in index.html (replay is a
MultiplayerAPI wrapper concern, not the raw adapter's) and add a
stale-dist warning to build-jatos-kahoot.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three new pages:
- docs/plugins/multiplayer-sync.md — plugin-style reference with parameters,
  data-generated, install snippet, and three usage examples
- docs/reference/jspsych-multiplayer.md — full MultiplayerAPI method reference
  (connect, disconnect, push, get, getAll, subscribe, wait, communicate,
  cancelAllSubscriptions, participantId)
- docs/developers/adapter-development.md — adapter authoring guide with the
  full MultiplayerAdapter interface contract, a minimal in-memory example,
  a pointer to the JATOS adapter as a real-world reference, and a checklist

mkdocs.yml: wired all three pages into Plugins, Reference, and Developers nav.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renames examples/kahoot → examples/group-quiz, the build script, and
the docs plan file. Updates all internal references (study title, dir
name, zip name, asset paths, heading). Replaces context-specific
trivia questions with general-knowledge questions. Switches the color
scheme from Kahoot purple/red/blue/yellow/green to dark teal with
blue/amber/violet/emerald answer tiles, and replaces ▲◆●■ symbols
with A/B/C/D labels. Removes the stale root-level kahoot-plan.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 01b8ee4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
jspsych Minor
@jspsych/plugin-multiplayer-sync Minor
@jspsych/adapter-multiplayer-jatos Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

jodeleeuw added a commit that referenced this pull request Jun 26, 2026
The fork-PR fallback used listPullRequestsAssociatedWithCommit, which
returns an empty list for fork-PR head commits (they don't live in a
base-repo branch). The publish job therefore failed with "No open PR
found" before it could push the preview branch or post the comment, so
fork PRs (e.g. #3694) never received a preview comment.

Look the PR up by its head ref (headOwner:headBranch) from the trusted
workflow_run payload via pulls.list instead. The PR number and head SHA
are still sourced only from the trusted event payload and validated
downstream, so trust guarantees are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📦 Preview build ready

Built from PR head 01b8ee4 and published at c43b3a1 on branch preview/pr-3694.
URLs below are pinned to an immutable commit SHA, so they are safe to share and are cached permanently by jsDelivr.

Changed packages: adapter-multiplayer-jatos, jspsych, plugin-multiplayer-sync

Quick-start HTML:

<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/jspsych/dist/index.browser.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/jspsych/css/jspsych.css">
<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-multiplayer-sync/dist/index.browser.min.js"></script>
All package URLs
  • @jspsych/adapter-multiplayer-jatoshttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/adapter-multiplayer-jatos/dist/index.browser.min.js
  • @jspsych/extension-mouse-trackinghttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/extension-mouse-tracking/dist/index.browser.min.js
  • @jspsych/extension-record-videohttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/extension-record-video/dist/index.browser.min.js
  • @jspsych/extension-webgazerhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/extension-webgazer/dist/index.browser.min.js
  • jspsychhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/jspsych/dist/index.browser.min.js
  • @jspsych/plugin-animationhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-animation/dist/index.browser.min.js
  • @jspsych/plugin-audio-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-audio-button-response/dist/index.browser.min.js
  • @jspsych/plugin-audio-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-audio-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-audio-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-audio-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-browser-checkhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-browser-check/dist/index.browser.min.js
  • @jspsych/plugin-call-functionhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-call-function/dist/index.browser.min.js
  • @jspsych/plugin-canvas-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-canvas-button-response/dist/index.browser.min.js
  • @jspsych/plugin-canvas-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-canvas-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-canvas-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-canvas-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-categorize-animationhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-categorize-animation/dist/index.browser.min.js
  • @jspsych/plugin-categorize-htmlhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-categorize-html/dist/index.browser.min.js
  • @jspsych/plugin-categorize-imagehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-categorize-image/dist/index.browser.min.js
  • @jspsych/plugin-clozehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-cloze/dist/index.browser.min.js
  • @jspsych/plugin-external-htmlhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-external-html/dist/index.browser.min.js
  • @jspsych/plugin-free-sorthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-free-sort/dist/index.browser.min.js
  • @jspsych/plugin-fullscreenhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-fullscreen/dist/index.browser.min.js
  • @jspsych/plugin-html-audio-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-html-audio-response/dist/index.browser.min.js
  • @jspsych/plugin-html-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-html-button-response/dist/index.browser.min.js
  • @jspsych/plugin-html-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-html-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-html-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-html-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-html-video-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-html-video-response/dist/index.browser.min.js
  • @jspsych/plugin-iat-htmlhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-iat-html/dist/index.browser.min.js
  • @jspsych/plugin-iat-imagehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-iat-image/dist/index.browser.min.js
  • @jspsych/plugin-image-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-image-button-response/dist/index.browser.min.js
  • @jspsych/plugin-image-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-image-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-image-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-image-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-initialize-camerahttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-initialize-camera/dist/index.browser.min.js
  • @jspsych/plugin-initialize-microphonehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-initialize-microphone/dist/index.browser.min.js
  • @jspsych/plugin-instructionshttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-instructions/dist/index.browser.min.js
  • @jspsych/plugin-maxdiffhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-maxdiff/dist/index.browser.min.js
  • @jspsych/plugin-mirror-camerahttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-mirror-camera/dist/index.browser.min.js
  • @jspsych/plugin-multiplayer-synchttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-multiplayer-sync/dist/index.browser.min.js
  • @jspsych/plugin-preloadhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-preload/dist/index.browser.min.js
  • @jspsych/plugin-reconstructionhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-reconstruction/dist/index.browser.min.js
  • @jspsych/plugin-resizehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-resize/dist/index.browser.min.js
  • @jspsych/plugin-same-different-htmlhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-same-different-html/dist/index.browser.min.js
  • @jspsych/plugin-same-different-imagehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-same-different-image/dist/index.browser.min.js
  • @jspsych/plugin-serial-reaction-time-mousehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-serial-reaction-time-mouse/dist/index.browser.min.js
  • @jspsych/plugin-serial-reaction-timehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-serial-reaction-time/dist/index.browser.min.js
  • @jspsych/plugin-sketchpadhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-sketchpad/dist/index.browser.min.js
  • @jspsych/plugin-survey-html-formhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-survey-html-form/dist/index.browser.min.js
  • @jspsych/plugin-survey-likerthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-survey-likert/dist/index.browser.min.js
  • @jspsych/plugin-survey-multi-choicehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-survey-multi-choice/dist/index.browser.min.js
  • @jspsych/plugin-survey-multi-selecthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-survey-multi-select/dist/index.browser.min.js
  • @jspsych/plugin-survey-texthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-survey-text/dist/index.browser.min.js
  • @jspsych/plugin-surveyhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-survey/dist/index.browser.min.js
  • @jspsych/plugin-video-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-video-button-response/dist/index.browser.min.js
  • @jspsych/plugin-video-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-video-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-video-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-video-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-virtual-chinresthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-virtual-chinrest/dist/index.browser.min.js
  • @jspsych/plugin-visual-search-circlehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-visual-search-circle/dist/index.browser.min.js
  • @jspsych/plugin-webgazer-calibratehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-webgazer-calibrate/dist/index.browser.min.js
  • @jspsych/plugin-webgazer-init-camerahttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-webgazer-init-camera/dist/index.browser.min.js
  • @jspsych/plugin-webgazer-validatehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@c43b3a1f285ef2d899cd43960c889a4a9d630dc9/packages/plugin-webgazer-validate/dist/index.browser.min.js

Last updated 2026-06-26 15:21 UTC for PR head 01b8ee4.

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.

3 participants