This reference tracks current exported behavior of @experiments/core in this repo.
The framework uses a standardized adapter pattern to manage the execution of diverse experimental tasks.
All task adapters must implement this interface to be compatible with the unified shell and core lifecycle.
readonly manifest: TaskManifest: Metadata about the task (ID, label, available variants).initialize(context: TaskAdapterContext): Promise<void>: (Optional) Called to set up the task, parse configuration, and prepare resources.execute(): Promise<unknown>: (Optional) Called to run the main task logic. Should return the task results.terminate(): Promise<void>: (Optional) Called after execution (success or failure) to clean up resources like global listeners or timers.
Context provided to task adapters during initialization and execution.
container: HTMLElement: The root element for the task UI.selection: SelectionContext: Metadata about the current task, variant, and participant.coreConfig: CoreConfig: The full core framework configuration.taskConfig: JSONObject: The task-specific configuration (automatically resolved at participant scope).resolver: VariableResolver: A pre-configured resolver for handling block and trial scoped variables.
Orchestrates the execution of a TaskAdapter.
constructor(adapter: TaskAdapter)run(context: TaskAdapterContext): Promise<unknown>: Executes the full lifecycle:initialize->execute(or legacylaunch) ->terminate.- Note:
run()automatically performs high-level variable resolution oncontext.taskConfigbefore callinginitialize. Onlyparticipantscoped variables are resolved at this stage;blockandtrialscoped variables remain as tokens for the adapter to handle.
A generalized RT trial runner that supports arbitrary phase sequences. Each phase can have a custom render function.
Provides utilities for generating non-overlapping spatial slots for stimuli.
generateSlots(args): Point[]: Supports"circular","grid", and"random"templates.
Standardized canvas renderer for SceneStimulus models. Handles rendering of shapes and supports slot-based positioning.
Utility to identify identity changes between two structured scenes.
Manages the loading, merging, and validation of experiment configurations.
load(path: string): Promise<JSONObject>: Fetches and parses a JSON config file.merge(base, taskDefault, variantOverride, runtimeOverride?): JSONObject: Sequentially deep-merges configuration levels.resolve(config: JSONObject, resolver: VariableResolver): JSONObject: Recursively resolves variable tokens in the configuration using the provided resolver.
Resolves task/variant/configPath/overrides/participant metadata from URL + JATOS.
Task/variant precedence:
- JATOS (
taskId,variantId) - URL (
task,variant) coreConfig.selection
Overrides precedence:
- JATOS
overrides - URL
overrides
Accepted URL keys:
task,variant,config,overrides,cc- participant keys:
PROLIFIC_PID,STUDY_ID,SESSION_ID,SONA_ID,participant,survey_code
Deep merge order:
basetaskDefaultvariantOverrideruntimeOverride
Fetches JSON and enforces object-only payload.
Renders each prompt and waits for continue (button click or space).
Handles block envelopes and trial iteration.
Key behavior:
- Uses
getTrials(block)when provided, elseblock.trials. renderBlockStart/renderBlockEndreturning non-null HTML triggers continue gates.- Cursor policy is configurable:
- default hides cursor during each trial
- set
hideCursorDuringTrial: false(or function) for mouse-first tasks.
Linear timed stage runner with optional timed response capture.
Returns:
key,rtMstotalDurationMsstageTimings[]
Lowercases and normalizes " " | "spacebar" | "space" -> "space".
Captures first valid key in [startMs, endMs] window over totalDurationMs.
Displays HTML screen with continue button and space shortcut.
Maps canonical keys to jsPsych choices ("space" -> " ").
Maps and deduplicates keys for jsPsych plugin choices.
Returns .jspsych-content host if present, else container.
Pushes a jsPsych call-function timeline node that renders a continue screen through core waitForContinue.
Resolves outer shell background with precedence:
taskConfig.ui.pageBackgroundcoreConfig.ui.pageBackgroundnull(caller may use CSS default)
Shared instruction-slot coercion used by task adapters.
Returned shape:
intro: string[]preBlock: string[]postBlock: string[]end: string[]
Accepted intro aliases (first key present wins):
pages(preferred)introPagesintroscreens
Accepted pre/post/end aliases:
- pre:
preBlockPages,beforeBlockPages,beforeBlockScreens - post:
postBlockPages,afterBlockPages,afterBlockScreens - end:
endPages,outroPages,end,outro
Behavior note:
- If a chosen key is explicitly present but blank (for example
""or[""]), the slot resolves to[](intentional clear) and does not fall back to defaults. - Blank entries inside arrays are ignored.
Shared helper that applies variable expansion across nested arrays/objects via resolver.resolveInValue(...) when a resolver is provided.
Useful for config fields that may be arrays/objects containing tokens (for example beforeBlockScreens: ["$between.preScreen"]).
Mode-aware callback bridge for local renderer/audio DRT presentation without embedding renderer logic into core.
Returns:
hasVisualModehasAuditoryModehasBorderModeonStimStart(stimulus)onStimEnd(stimulus)onResponseHandled()hideAll()
Typical usage:
- task-local DRT loop calls
onStimStart/onStimEndfrom engine/controller hooks - task-local key handler calls
onResponseHandledafter a handled DRT response - task cleanup calls
hideAll
The framework supports modular extensions that can be attached to specific scopes (task, block, or trial).
id: string: Unique identifier for the module.start(config, address, context): TaskModuleHandle: Called when a scope starts.
stop(): TResult: Called when the scope ends.step?(now: number): (Optional) Animation frame tick.handleKey?(key, now): (Optional) Keyboard event handler.
Manages the lifecycle of active modules.
constructor(modules?: TaskModule[])setOptions(options: { onEvent?: (event) => void })start({ module, address, config, context }): Starts a new module instance at the specified address.stop(address): Stops the module instance at the specified address and records the result.stopAll(): Stops all active modules.getResults(): TaskModuleResult[]: Returns all results from stopped modules.
The DrtController provides a static helper to use the DRT engine as a task module:
DrtController.asTaskModule(config): Returns aTaskModuleinstance configured for DRT.
Common display helpers used by tasks:
computeCanvasFrameLayoutdrawCanvasTrialFramedrawCanvasFramedScenedrawCanvasCenteredTextdrawCenteredCanvasMessagecreateScaledCanvasHostmountCanvasElementensureJsPsychCanvasCenteredrenderCenteredNotice
Supports:
weighted(default)sequencequota_shuffleblock_quota_shuffle(alias)
Schedule options include withoutReplacement and without_replacement.
Core also exports generic helpers used by task adapters for block-level manipulation assignment:
createManipulationOverrideMap(value):- converts
[{ id, overrides }]into an id -> overrides map.
- converts
createManipulationPoolAllocator(value, seedParts):- creates a participant-seeded pool allocator from a config object like:
{ "poolA": [ ["manipA"], ["manipB"] ] }
resolveBlockManipulationIds(blockLike, allocator?):- resolves
manipulationPool,manipulation, andmanipulationsinto an ordered id list.
- resolves
applyManipulationOverridesToBlock(blockLike, manipulationIds, overrideMap, errorContext):- deep-merges referenced manipulation overrides into a block object.
These are intentionally task-neutral primitives. Whether a task allows one manipulation per block vs multiple is controlled by the task adapter.
hashSeed(...parts): numbercreateMulberry32(seed): () => numberSeededRandom(next,int,shuffle)
Core now exports task-neutral stimulus pool primitives:
- Source loading:
coerceCsvStimulusConfig(value)loadCategorizedStimulusPools({ inlinePools, csvConfig, resolver?, context? })loadTokenPool({ inline?, csv?, normalize?, dedupe? })
- Draw planning:
collectPoolCandidates(pools, categories, excludedCategories?)createPoolDrawer(candidates, rng, drawConfig?)createCategoryPoolDrawer(pools, categories, rng, options?)
- Config coercion:
coercePoolDrawConfig(value, defaults?)coerceCategoryDrawConfig(value, defaults?)
Supported draw modes:
ordered(source order, loops)with_replacement(independent random draw)without_replacement(shuffle/consume/recycle)- category drawers also support
round_robin
These helpers are used by PM/NBack for participant-seeded deterministic pool behavior.
Core now exports additive PM utilities in prospectiveMemory.ts:
generateProspectiveMemoryPositions(rng, { count, minSeparation, maxSeparation })resolveProspectiveMemoryCueMatch(context, rules)
Cue-rule primitives support:
category_intext_starts_withstimulus_colorflag_equals
Current shared policy for concurrent keyboard modules (primary task + DRT):
- Primary task keys remain task-owned and are handled in task runtime order.
- DRT uses controller-level capture listeners and only consumes configured DRT response key.
- If keys overlap by configuration, overlap is allowed and task adapters must explicitly prevent default/propagation where needed.
- Recommended practice is non-overlapping key maps per task/module pair.
Methods:
nextStimulus()update(response: 0 | 1)estimateMode()exportPosterior()
Helpers:
buildLinearRangeluminanceToDbdbToLuminance
Converts flat object rows to CSV with escaping.
Behavior:
- Local save (
downloadJsonand optionaldownloadCsv) whencoreConfig.data.localSave !== false. - Submit to JATOS when available.
endStudy()unlessendJatosOnSubmit === false.- Resolve and apply redirect template if enabled.
Provides .emit(eventType, eventData?, meta?) and accumulated .events.
DrtEngine: pure timing/scoring engine for DRT probes (presented/hit/miss/false_alarm, event log export).DrtController: browser runtime wrapper overDrtEnginewith:- scoped
start()/stop() - keyboard listener lifecycle
requestAnimationFramestepping- sampler-based ISI generation via shared core
createSamplerspecs. - independent probe
displayDurationMsandresponseWindowMs responseTerminatesStimuluscontrol- optional online parameter transforms (
parameterTransforms) that consumedrt_responseevents and emit per-update estimates viaonTransformEstimate. - row-level export linking each
drt_responseto zero/one/many transform estimates (exportResponseRows()). - built-in presentation modes:
visual(default: top-center red square on viewport)auditory(WebAudio tone)border(flash outline around target display element)
- scoped
Core now exports reusable tracking primitives (tracking.ts):
TrackingMotionController:waypointmotion (sampled destinations + linear traversal)chaoticmotion (heading jitter + wall reflections)
TrackingBinSummarizer:- accumulates per-window sample counts
- stores
insideCount,outsideCount, and boundary-distance moments for weighted aggregation
- geometry helpers:
computeTrackingDistance(point, target)for circle/square boundary distance with inside=0 convention.
OnlineParameterTransformRunner: generic runtime for event-driven, online parameter estimation modules.OnlineParameterTransform: minimal interface (observe,reset,exportState) for reusable model adapters.- Included first transform type:
wald_conjugate:- Moving-window analytic shifted-Wald fit from RT observations.
- Configurable priors (
mu0,precision0,kappa0,beta0), window sizes, and credible interval bounds. - Optional trial-varying prior mean shift mode (
priorUpdate.mode: "shift_means") matching the provided R/Python pattern.
Standardized correctness evaluation output used by task adapters.
Core exports shared feedback helpers used by task adapters:
parseTrialFeedbackConfig(value, fallback, defaults?)resolveTrialFeedbackView({ feedback, responseCategory, correct, vars?, resolver?, resolverContext? })drawTrialFeedbackOnCanvas(ctx, layout, feedback, view)
Supported feedback config fields:
enableddurationMs/duration_msmessages.correct|incorrect|timeout|invalidmessages.byResponseCategory/messages.by_response_categorystyle.correctColor|incorrectColor|timeoutColor|invalidColor(snake_case variants also accepted)style.byResponseCategoryColors/style.by_response_category_colorsstyle.fontSizePx|fontWeight|canvasBackground|canvasBorder(snake_case variants also accepted)
resolveTrialFeedbackView supports {placeholder} interpolation from vars and core variable resolver context.
Builds full-factorial cells from named factors and levels.
Builds an exact-quota condition sequence using full-factorial cells + optional weights + optional adjacency constraints:
maxRunLengthByFactormaxRunLengthByCellnoImmediateRepeatFactors
Useful for balanced block construction in any trial task.
Creates normalized term -> label mappings from grouped vocabularies.
Resolver API for normalized semantic label lookup:
resolve(term): string | nullhas(term): boolean
parseCsvDictionary(csvText, keyColumn, valueColumn, options?)loadCsvDictionary(spec)loadSemanticIndexFromCsvColumns(csvPath, keyColumn, labelColumns, args?)loadTokenListFromCsvColumn(path, column, args?)
These support generic dictionary/lexicon ingestion from CSV-backed assets.
Creates a normalized key-to-category resolver for RT tasks where physical keys map to abstract response categories.
Capabilities:
allowedKeys(categories?)for scoped key sets (for example block-specific key availability)responseCategoryFromKey(key)with built-intimeoutandinvalidcategoriesexpectedCategoryFromKey(key, fallback?)for key-coded expected responsesexpectedCategoryFromSpec(spec, fallback?)for expected responses that may be either:- a physical key (mapped to category), or
- a category label directly (including omission categories like
timeout)
keyForCategory(category)to resolve canonical key output for a category
This is used by current tasks (PM, Stroop, SFT) to avoid task-local key classification logic.
Creates a normalized token -> CSS color registry with validation and fallback support:
resolve(token): string | nullhas(token): booleanentries()
normalizeColorToken(token)
Core now exposes a generic extension hook runtime intended for cross-task overlays (for example embedding an auxiliary N-back stream into another primary task).
prepareTaskHooks(hooks, options?)- filters disabled hooks (
enabled: false) - resolves stable IDs
- orders hooks by
prioritythen declaration order
- filters disabled hooks (
createHookStateStore(initial?)- shared mutable state map for hook instances (
get,set,update,delete,entries)
- shared mutable state map for hook instances (
runTaskHookLifecycle(args)- async lifecycle fanout for
task_start | task_end | block_start | block_end | trial_start | trial_end
- async lifecycle fanout for
emitTaskHookEvent(args)- async custom event fanout (
TaskHookEvent) for task-specific signals - suitable for per-trial/per-stage side channels (audio onset, cursor stream ticks, etc.)
- async custom event fanout (
applyTrialPlanHooks(trial, context, hooks?)applyTrialPlanHooks({ trial, context, hooks, state?, options? })applyTrialPlanHooksAsync(args)
Hooks can transform trial plans and access shared hook state (state) plus per-hook IDs (hookId).
Sync API throws if a hook returns a Promise.
evaluateTrialOutcomeWithHooks(args)evaluateTrialOutcomeWithHooksAsync(args)
Hooks can patch:
- inputs before evaluation (
beforeEvaluate) - output after evaluation (
afterEvaluate)
Sync API throws if a hook returns a Promise.
Both APIs support HookExecutionOptions:
continueOnError(defaultfalse)onError(error, context)callback
Computes common RT phase durations from a timing config:
- fixation
- blank
- pre-response stimulus
- response window
- post-response stimulus
options.responseTerminatesTrial (default false) forces postResponseStimulusMs to 0.
Runs a generic single-trial RT lifecycle with:
- normalized timing decomposition
- shared response capture window
- user-provided render hooks for fixation/blank/stimulus
- optional
responseTerminatesTrialphase shaping (for fixed-trial tasks keep thisfalse)