This document describes the current Bricks adapter at tasks/bricks/src/index.ts.
The Bricks task is implemented using the createTaskAdapter factory:
export const bricksAdapter = createTaskAdapter({
manifest: { taskId: "bricks", ... },
run: runBricksTask,
terminate: async () => { /* module cleanup */ },
});run(context): Runs Bricks through coreTaskOrchestrator; instruction flow and module lifecycle are orchestrator-managed.terminate(): Stops all active task modules.
Bricks runs as a native task via the LifecycleManager.
Top-level planning keys:
blocks[](required;plan.blocks[]is also accepted)manipulations[](optional but typically present;plan.manipulations[]is also accepted)manipulationPools(optional;plan.manipulationPoolsis also accepted)
blocks[*]:
labeltrialsmanipulation(single id)manipulations(ordered list of ids)manipulationPool(draws id bundle from top-levelmanipulationPools)overrides
manipulations[*]:
idlabeloverridestrialPlan.scheduleortrial_plan.scheduletrialPlan.variants[]ortrial_plan.variants[](id,label,weight,overrides)
Per-trial config is deep-merged in this order:
- full base config clone
- selected manipulation overrides (for all resolved ids, in order)
- block overrides
- scheduled variant overrides
Top-level optional:
manipulationPools:poolId: [ [manipA, manipB], [manipC], ... ]- blocks with
manipulationPool: "poolId"draw one bundle - draws are participant-seeded and shuffled without replacement, then recycled
surveys:surveys.postTrial[]supports preset surveys ("atwit","nasa_tlx")- use
showQuestionNumbers: falseto hideQ1/1.labels when rendering survey items - use
showRequiredAsterisk: falseto hide required*markers while keeping required validation - use
questionBorder: "none"to remove per-question border boxes - optional
questionBorderRadiussets per-question border radius (for example"0"or"8px")
The merged per-trial config is passed to conveyor runtime. Common sections:
displayconveyorsbricksdrttrialexperimentexperiment.statsPresentation(optional HUD stats scoping + reset rules)debugdifficultyModelselfReportinstructions
Bricks reads DRT from task.modules.drt (and per-trial/per-block overrides via modules.drt on merged trial config).
For block-scoped DRT:
- You only need to configure
scope: "block"at task-level or trial-level module config. - Bricks now auto-projects block-scoped trial config into orchestrator block module config; no task-local scope wrapper config is required.
Key fields:
enabled: booleanscope:"trial"or"block"parameterTransforms: array of transform configs (for example[{ "type": "wald_conjugate" }])transformPersistence:"scope"(default) or"session"
wald_conjugate transform options:
t0Mode:"fixed"(default) or"min_rt_multiplier"(aliases:t0_mode,t0mod)t0: fixed non-decision time in ms whent0Mode = "fixed"t0Multiplier: multiplier for minimum observed finite DRT RT whent0Mode = "min_rt_multiplier"(aliases:t0_multiplier,t0mult)
transformPersistence controls moving-window/prior continuity for online transforms:
"scope": reset transform window/priors when each DRT scope ends."session": keep transform window/priors across all DRT scopes in the task run.
How to get per-block vs per-experiment transform history:
- Per-block moving window: set
scope: "block"andtransformPersistence: "scope". - Per-experiment moving window: set
transformPersistence: "session"(with eitherscope: "block"or"trial").
Important: parameterTransforms must be an array of objects. A string value like "wald_conjugate" is ignored by coercion.
instructions supports either simple strings or rich page objects:
- string page:
"Read this" - object page:
{ "title": "How To Process", "html": "<p>...</p>" } - shared slots:
pages|introPages|intro|screens,preBlockPages|beforeBlockPages|beforeBlockScreens,postBlockPages|afterBlockPages|afterBlockScreens,endPages|outroPages|end|outro - block flow controls:
blockIntroTemplate,showBlockLabel,preBlockBeforeBlockIntro - optional:
showBlockIntro(defaulttrue) to include/skip the automatic block-intro continue card - block-level pre-screen aliases in
blocks[]:beforeBlockScreensandpreBlockScreens(legacy:preBlockInstructions)
Instruction text/html supports {dot.path} interpolation against the merged Bricks config (and resolver-backed variables), for example:
{bricks.completionParams.target_hold_ms}
For detailed runtime field definitions, use:
Completion-mode note (hover_to_clear):
- Hover processing is rate-based.
- Bricks keep moving while hovered, and visible width is depleted from the right edge at a processing rate (
completionParams.hover_process_rate_px_s). - If that config key is omitted, the runtime default is the brick/conveyor progress-rate variable
brick.speed(not a fixed constant).
Completion-mode note (hold_to_clear):
- Hold processing is rate-based while the pointer is held down.
- Bricks keep moving while held, and visible width is depleted at
completionParams.hold_process_rate_px_s. - Supports all interaction targeting areas (
brick,conveyor,spotlight). - If that key is omitted, runtime falls back to
brick.speed.
Completion-mode note (hold_duration):
- Optional
completionParams.hold_floor_msapplies a low-end floor. - Holds shorter than this floor contribute zero progress, while overshoot still respects
completionParams.overshoot_tolerance_ms.
Interaction targeting note:
- Default targeting is direct brick hit-testing.
- Preferred selector is
bricks.interaction.targetingArea:"brick"(default)"conveyor"(click/hover anywhere on a lane targets the front-most brick on that lane)"spotlight"(click/hover anywhere in spotlight area targets the currently spotlighted brick)
- Legacy aliases remain supported:
bricks.interaction.conveyorWideHitArea: true->"conveyor"bricks.interaction.spotlightWideHitArea: true->"spotlight"
- Cursor/hover reconciliation runs each frame using tracked pointer position, so cursor/hover state updates when moving bricks/spotlight enter a stationary pointer.
Conveyor speed note:
conveyors.speedPxPerSecremains the baseline speed sampler (sampled once at trial init per conveyor).- Optional
conveyors.dynamicSpeedadds runtime speed changes (off by default):- each conveyor gets an independent interval timer and speed sampler
- supports per-conveyor overrides (
perConveyor.c0,perConveyor.c1, etc.) - when disabled, no runtime speed resampling logic is executed
Spotlight rendering note:
display.spotlight.snapModecontrols spotlight geometry snapping ("screen"default,"pixel","none").- Use
"screen"to reduce visible spotlight judder while keeping crisp edges; use"none"for fully subpixel motion.
Per-trial conveyor output (ConveyorTrialData) is appended to records.
Session finalization is handled internally by TaskOrchestrator (via the core data sink and JATOS submission pipeline). Task adapters do not call finalizeTaskRun directly.
Final payload shape:
{
"selection": {},
"records": [],
"drt_rows": [],
"events": []
}Notes on drt outputs:
- Trial-scoped DRT:
record.drtreflects that trial scope snapshot (includingtransform_latestwhen available); response-level DRT rows are attached asdrt_response_rows. - Block-scoped DRT:
record.drt.statsis converted to per-trial deltas for accurate trial/block summaries, and cumulative snapshots are preserved inrecord.drt_cumulative. - For trial-scoped DRT, Bricks gates DRT onset to the active trial run window only (from trial start trigger to trial end), so post-trial survey screens are outside DRT scope.
- Optional trial config
trial.stopDrtOnBrickQuotaMet: true(aliastrial.endDrtOnBrickQuotaMet) stops trial-scoped DRT immediately whenbrick_quota_metis reached, even if a post-end delay is configured. - Bricks does not manually start/stop DRT modules; it consumes active DRT handles exposed by core module orchestration.
drt_response_rows now carries per-response transform data directly:
estimate: primary transform estimate for that response (ornull)transformColumns: flattened scalar columns for analysis-ready long format (for exampledrift_rate,threshold,t0, and CI bounds likedrift_rate_ci_lower/drift_rate_ci_upper)
Notes on runtime performance outputs:
- Each trial record now includes
record.performancewith frame pacing summary (avg_fps, frame overrun ratios, tick cost) and renderer counters (active/peak effects, skipped effects at cap, clear-point effects queued, active/peak brick sprites). - Hold-duration practice records also include
practice_press_results(boolean per registered hold),practice_press_count,practice_correct_count, andpractice_required_presses.
Hold-duration practice helper config (trial.holdDurationPractice):
requiredPresses: force press-count quota by switching practice run logic tomax_bricks.fullWidthConveyor: when true (default), practice conveyor uses full canvas width.centerBrick: when true (default), practice brick is placed at conveyor midpoint.replenishDelayMs(aliases:trialTimeMs,nextTrialDelayMs): after each hold release, keep the visible clear-progress state for this many ms, then refill to full to start the next practice press-trial.useSpotlightWindow(alias:spotlightWindow): enable the spotlight window / forced-order focus frame in hold-duration practice.hideHud: when true (default), hides HUD counters/timer during hold-duration practice.
Quota note:
- Hold-duration
requiredPressesnow uses the internal count of registered practice hold trials (same source aspractice_press_results), notgame.stats.cleared.
Demo variant:
bricks/drt_block_demosetsdrt.scope = "block"for quick validation of continuous block-level DRT.
CSV export now uses long-format event records (bricks_events) as the primary CSV output.
- one row per timeline event/click/hold with shared trial identifiers (
participant_id,variant_id,bricks_trial_id, block/trial fields) - event payload flattened under
event_*columns
Additional CSV outputs:
bricks_trial_summary: one row per trial (compact trial-level summary payload)bricks_brick_outcomes: one row per brick per trial, including end state (brick_final_status:cleared/dropped/active_at_trial_end)bricks_drt_rows: one row per DRT response, with spotlight linkage at response time
DRT long-format rows remain available via task metadata (drt_rows) and include:
- Bricks trial linkage metadata (
participant_id,variant_id,bricks_trial_id, block/trial ids/labels/phase/manipulation`) - spotlight context at response time (
spotlight_brick_id,spotlight_conveyor_id) when available - all flattened columns from each
drt_response_rowsentry (including dynamic transform fields intransformColumns)
Bricks supports config-driven HUD stat scoping via experiment.statsPresentation:
- scopes:
trial,block,experiment - per-metric override:
scopeByMetric.spawned|cleared|dropped|points - optional reset rules (
reset[]):at: "block_start" | "block_end"- target
scope: "block" | "experiment" - optional metric subset (
metrics[]) - optional conditions (
when.isPractice,when.phaseIn,when.labelIn,when.manipulationIdIn)
Example pattern:
- show
cleared/droppedper trial - show
pointscumulative across experiment - then reset only experiment points after practice blocks
experiment supports click/space start trigger configuration:
startTrialsOn: "space" | "click"(preferred)- legacy aliases remain supported:
startTrialsOnSpace: true->"space"startTrialsOnClick: true->"click"
- click-start button styling can be set in
experiment.startOverlay.buttonStyle(same style fields as core continue buttons).
Use the capture script to export brick stimuli exactly as rendered by the Bricks Pixi runtime.
Command:
npm run render:stimuli -w @experiments/task-bricks -- \
--styles present,crate,target_present \
--mode brick \
--output-dir apps/web/public/assets/bricks-stimuliNotes:
--mode brickexports a crop around the brick (default),--mode sceneexports the full canvas scene.- Script starts
@experiments/webdev server automatically if needed. - Output filenames are
<style>.<mode>.png(for examplepresent.brick.png).