This is the runtime-facing schema used by tasks/bricks/src/runtime/* and block planner code in tasks/bricks/src/index.ts.
Start app:
npm run devRun Bricks baseline:
http://localhost:5173/?task=bricks&variant=baseline
Run Bricks Moray baseline (ported from DiscoveryProject):
http://localhost:5173/?task=bricks&variant=moray1991_vpt_vdd_baseline
Run Bricks spotlight:
http://localhost:5173/?task=bricks&variant=spotlight
Force Bricks spotlight config while keeping baseline variant:
http://localhost:5173/?task=bricks&variant=baseline&config=bricks/spotlight
Recognized top-level keys:
displayconveyorsbricksdrttrialexperimentdebugdifficultyModelselfReportinstructionsblocksmanipulations
blocks/manipulations are planning-level keys. Other sections are consumed by runtime.
Planning keys can be provided either at top-level (blocks, manipulations, manipulationPools) or under plan.*.
{
label?: string;
trials?: number; // >= 1, default 1
phase?: string; // optional block phase tag (e.g., "practice", "main")
isPractice?: boolean; // explicit practice marker; if omitted, inferred from phase containing "practice"
manipulation?: string; // manipulation id
overrides?: object; // deep-merged into per-block config
}{
id: string;
label?: string;
overrides?: object;
trialPlan?: {
// alias: trial_plan
schedule?: {
mode?: "weighted" | "sequence" | "quota_shuffle" | "block_quota_shuffle";
sequence?: Array<string | number>;
withoutReplacement?: boolean;
without_replacement?: boolean;
};
variants?: Array<{
id?: string;
label?: string;
weight?: number; // > 0, default 1
overrides?: object; // deep-merged into per-trial config
}>;
};
}Core:
{
canvasWidth: number;
canvasHeight: number;
viewportPaddingPx?: number; // default 24
backgroundColor?: string | number;
beltColor?: string | number;
beltHeight?: number;
beltGap?: number;
brickWidth?: number;
brickHeight?: number;
brickCornerRadius?: number;
brickColor?: string | number;
brickBorderColor?: string | number;
brickShape?: "rect" | "rounded_rect" | "pill" | "diamond" | "hex" | "triangle" | "parallelogram" | "trapezoid";
}Shape aliases accepted:
"square"/"rectangle"->"rect""rounded"/"rounded_rectangle"->"rounded_rect"
{
showHUD?: boolean;
hudFontFamily?: string;
hudFontSize?: number;
hudFontWeight?: number | string;
hudPanel?: boolean;
hudPanelColor?: string | number;
hudPanelAlpha?: number; // 0..1
showTimer?: boolean;
showRemainingBlocks?: boolean;
showDroppedBlocks?: boolean;
showPoints?: boolean;
showDRT?: boolean;
}{
maxDevicePixelRatio?: number; // default 1.5
maxFrameDtMs?: number; // default 50
maxActiveEffects?: number; // default 180
antialias?: boolean; // default false (sharper edges)
pixelSnapBricks?: boolean; // default false (smoother moving bricks)
imageRendering?: string; // default "crisp-edges"
}{
snapMode?: "screen" | "pixel" | "none"; // default "screen"
cornerRadiusPx?: number; // default 10
ringWidthPx?: number; // default 3
ringColor?: string | number; // default "#f8fafc"
ringAlpha?: number; // 0..1, default 0.95
signatureQuantizePx?: number; // redraw threshold in px; default 1 for snapped, 0.25 for none
}Notes:
snapMode: "screen"mirrors conveyor screen-space snapping and uses renderer resolution, reducing visible spotlight stepping/judder.snapMode: "none"keeps fully subpixel spotlight motion (smoothest motion; potentially softer edges on low-DPR displays).
{
enable?: boolean;
color?: string | number;
widthPx?: number;
heightPx?: number;
alpha?: number; // 0..1
}Supports enable, offsetX, and visual style values used by renderer presets:
wallColorwallShadeColorrimColormouthColoremberColorstyle("furnace" | "crusher" | "shredder" | "plasma_recycler")bodyPanelLines(bool)hazardStripes(bool),hazardColorA,hazardColorBsideRivets(bool)
{
mode?: "furnace";
durationMs?: number;
markerFlashMs?: number;
flashColor?: string | number;
disintegrate?: {
shardCount?: number;
durationMs?: number;
gravityPxPerSec2?: number;
speedMinPxPerSec?: number;
speedMaxPxPerSec?: number;
furnaceBias?: number;
};
}Optional points pop animation shown when a brick is cleared.
{
enable?: boolean;
timeoutMs?: number; // pop lifetime in ms
durationMs?: number; // alias of timeoutMs
risePx?: number; // upward drift over lifetime
startOffsetYPx?: number; // initial vertical offset above brick center
textColor?: string | number;
textStrokeColor?: string | number;
textStrokeThickness?: number;
textFontFamily?: string;
textFontWeight?: string | number;
textMinSizePx?: number; // clamp min for size-aware text
textMaxSizePx?: number; // clamp max for size-aware text
textSizeFactor?: number; // scales with brick height
textShadowColor?: string | number;
textShadowBlur?: number;
textShadowDistance?: number;
coin?: {
enable?: boolean;
showInPointsAnimation?: boolean;
showInHud?: boolean;
sizePx?: number;
hudSizePx?: number; // optional fixed HUD coin size; default auto from HUD cap height
hudSizeScale?: number; // multiplier for auto HUD size (default 0.95)
gapPx?: number;
rimColor?: string | number;
bodyColor?: string | number;
shineColor?: string | number;
shadowColor?: string | number;
symbolColor?: string | number;
ridgeCount?: number;
};
}{
enable?: boolean;
src?: string; // image mode
renderMode?: "image" | "procedural_warehouse";
style?: string; // built-in or custom style id
styles?: Record<string, object>; // optional style map by id
alpha?: number;
scale?: number;
scrollFactor?: number;
proceduralWarehouse?: {
tileSizePx?: number;
paverWidthPx?: number;
paverHeightPx?: number;
groutPx?: number;
layout?: "grid" | "staggered";
rowOffsetPx?: number;
pattern?: "none" | "checker_alternating";
alternationStrength?: number;
variation?: number;
edgeShadingAlpha?: number;
noiseCount?: number;
seamDashCount?: number;
rivetCount?: number;
dentCount?: number;
crackCount?: number;
baseColor?: string | number;
groutColor?: string | number;
seamDarkColor?: string | number;
seamLightColor?: string | number;
scratchColor?: string | number;
rivetColor?: string | number;
};
}{
enable?: boolean;
src?: string; // image mode
renderMode?: "image" | "procedural_topdown";
style?: string; // built-in or custom style id
styles?: Record<string, object>; // optional style map by id
alpha?: number;
scale?: number;
scrollFactor?: number;
scrollSnapMode?: "screen" | "texture" | "none"; // default "none"; "texture" keeps legacy seam snapping
scrollRenderMode?: "auto" | "tiling"; // default "auto"; procedural_topdown uses seam-free redraw path unless forced to "tiling"
proceduralTopdown?: {
tileSizePx?: number;
ribStepPx?: number;
ribWidthPx?: number;
sideBandPx?: number;
sideCleatStepPx?: number;
sideCleatLengthPx?: number;
shadeAlpha?: number;
baseColor?: string | number;
shadeColor?: string | number;
ribColor?: string | number;
grooveColor?: string | number;
sideCleatColor?: string | number;
sideLineDarkColor?: string | number;
sideLineLightColor?: string | number;
scuffCount?: number;
patchCount?: number;
scuffColor?: string | number;
patchColor?: string | number;
};
brickContactBand?: {
enable?: boolean;
topInsetPx?: number;
topInsetRatio?: number;
bottomInsetPx?: number;
bottomInsetRatio?: number;
offsetYPx?: number;
};
}{
enable?: boolean;
style?: string; // default style id (e.g., "crate", "present", or custom)
styles?: Record<string, object>; // per-style overrides
pattern?: "wood_planks" | "gift_wrap" | "pizza" | "checkerboard" | "cardboard_block";
baseFillColor?: string | number;
baseFillAlpha?: number; // 0..1, can fully mask base brick color when 1
alpha?: number;
plankCount?: number;
seamWidthPx?: number;
grainCount?: number;
nailRadiusPx?: number;
insetPx?: number;
topSheenAlpha?: number;
seamColor?: string | number;
highlightColor?: string | number;
ribbonColor?: string | number; // gift_wrap
ribbonAlpha?: number; // gift_wrap
ribbonWidthRatio?: number; // gift_wrap
ribbonInsetPx?: number; // gift_wrap
bowBorderColor?: string | number; // gift_wrap
bowBorderAlpha?: number; // gift_wrap
bowBorderWidthPx?: number; // gift_wrap
paperPatternColor?: string | number; // gift_wrap
paperPatternAlpha?: number; // gift_wrap
paperDotStepPx?: number; // gift_wrap
checkerColorA?: string | number; // checkerboard
checkerColorB?: string | number; // checkerboard
checkerCellPx?: number; // checkerboard
fiberColor?: string | number; // cardboard_block
fiberAlpha?: number; // cardboard_block
fiberStepPx?: number; // cardboard_block
speckleColor?: string | number; // cardboard_block
speckleAlpha?: number; // cardboard_block
speckleCount?: number; // cardboard_block
tapeColor?: string | number; // cardboard_block
tapeAlpha?: number; // cardboard_block
tapeWidthRatio?: number; // cardboard_block
tapeInsetPx?: number; // cardboard_block
tapeOrientation?: "vertical" | "horizontal" | "cross"; // cardboard_block
sliceCount?: number; // pizza
toppingCount?: number; // pizza
crustColor?: string | number; // pizza
sauceColor?: string | number; // pizza
cheeseColor?: string | number; // pizza
toppingColor?: string | number; // pizza
sliceLineColor?: string | number; // pizza
labelPatch?: boolean; // wood_planks
labelPatchColor?: string | number; // wood_planks
labelPatchAlpha?: number; // wood_planks
labelPatchBorderColor?: string | number; // wood_planks
labelBarcodeColor?: string | number; // wood_planks
bandColor?: string | number; // wood_planks
bandAlpha?: number; // wood_planks
lockPlateColor?: string | number; // wood_planks
lockPlateAlpha?: number; // wood_planks
}{
enable?: boolean;
mode?: "none" | "fixed" | "random_per_trial";
id?: string; // fixed mode
fixedId?: string; // fixed mode
pool?: string[]; // random_per_trial mode
presetOverrides?: Record<string, object>; // per-preset deep override
}Built-in preset IDs:
warehouse_concrete_checkerindustrial_sleek_flatkraft_wood_packagingparcel_sorting_holidaycold_storage_blueprintnightshift_high_contrastlab_rivet_sorting_baywood_corrugation_packlinedamaged_salvage_linesalvage_shredder_bay
Texture-forward preset convention:
- New presets above include
display.brickTextureOverlay.styles.target,.other, and.neutral. - You can bind these directly via
bricks.textureCategories[*].textureStyle.
Built-in brick texture style IDs (global, usable without presets):
crateother_crateother_steel_casepresenttarget_presenttarget_teal_presentneutral_totepizzatarget_pizzaboxcheckerboardchecker_boardcrate_damagedparcel_labelparcel-labelparcel_damagedchest
Built-in warehouse floor procedural style IDs (display.backgroundTexture.style):
concrete_checkercold_blueprintlab_metal_rivetwood_corrugationdamaged_salvagesalvage_rivet
Built-in conveyor procedural style IDs (display.beltTexture.style):
industrial_ribbedcold_blueprint_beltlab_ribbedwood_corrugation_beltdamaged_patched_beltsalvage_shredder_belt
{
nConveyors: number;
lengthPx: number | number[] | SamplerSpec;
runtimeLengths?: number[]; // optional renderer override
speedPxPerSec: number | SamplerSpec;
dynamicSpeed?: {
enable?: boolean; // default false
intervalMs?: number | SamplerSpec; // ms between speed changes (sampled per conveyor)
speedPxPerSec?: number | SamplerSpec; // sampled speed for each change event
perConveyor?: Record<string, {
enable?: boolean;
intervalMs?: number | SamplerSpec;
speedPxPerSec?: number | SamplerSpec;
}>;
};
}conveyors.dynamicSpeed behavior:
- Off by default (
enable: false): conveyor speed is sampled once at trial init and reused; no runtime speed resampling occurs. - When enabled, each conveyor keeps its own independent timer and speed sampler.
- On each sampled interval boundary, the conveyor speed is resampled and applied immediately.
perConveyoroverrides are keyed by conveyor id ("c0","c1", …) or string index ("0","1", …).perConveyor[*].enablecan turn dynamic speed on/off for individual conveyors while globalenableremains unchanged.- If
speedPxPerSecis omitted insidedynamicSpeed, it falls back toconveyors.speedPxPerSec.
{
completionMode: "single_click" | "multi_click" | "hold_duration" | "hover_to_clear" | "hold_to_clear" | string;
completionParams?: {
clicks_required?: number; // multi_click
target_hold_ms?: number; // hold_duration
progress_per_perfect?: number; // hold_duration
progress_curve?: number; // hold_duration
overshoot_tolerance_ms?: number; // hold_duration
width_scaling?: boolean; // hold_duration
width_reference_px?: number; // hold_duration
width_scaling_exponent?: number; // hold_duration
hover_process_rate_px_s?: number;// hover_to_clear; if unset, uses runtime `brick.speed`
hold_process_rate_px_s?: number; // hold_to_clear; if unset, uses runtime `brick.speed`
};
maxBricksPerTrial?: number;
initialBricks?: number | { type: "fixed"; value: number };
spawn: {
ratePerSec?: number | SamplerSpec;
interSpawnDist?: number | SamplerSpec | null;
minSpacingPx?: number;
byConveyor?: boolean; // default true
maxActivePerConveyor?: number;
};
forcedSet?: Array<object>;
forcedSetPlan?: {
enable?: boolean;
count?: number | SamplerSpec;
n?: number | SamplerSpec;
defaults?: object;
fields?: Record<string, ForcedPlanFieldSpec>;
};
interaction?: {
targetingArea?: "brick" | "conveyor" | "spotlight";
conveyorWideHitArea?:
| boolean
| {
enable?: boolean;
};
spotlightWideHitArea?:
| boolean
| {
enable?: boolean;
};
};
colorCategories?: BrickCategorySpec[];
widthCategories?: BrickCategorySpec[];
borderColorCategories?: BrickCategorySpec[];
shapeCategories?: BrickCategorySpec[];
textureCategories?: BrickCategorySpec[];
}interaction targeting behavior:
- Default is direct brick hit-testing.
- Preferred selector:
targetingArea"brick": interact only on brick geometry (focused brick only when spotlight forced-order is active)."conveyor": interact anywhere on a conveyor lane; routes to front-most brick on that lane."spotlight": interact anywhere inside the current spotlight area; routes to currently spotlighted brick.
- Legacy booleans remain supported:
conveyorWideHitArea: true(same astargetingArea: "conveyor")spotlightWideHitArea: true(same astargetingArea: "spotlight")
- If
targetingAreais set, it takes precedence over legacy booleans.
hover_to_clear runtime behavior:
- Bricks continue moving while hovered (normal conveyor advection still applies).
- Processing depletes visible width from the right edge at
hover_process_rate_px_s. - Depleted segments are fully removed visually (no translucent ghost body), while interaction bounds remain the full brick object until clear/drop.
- If
hover_process_rate_px_sis not set, the runtime usesbrick.speed(the sampled conveyor/brick px/s rate), so depletion rate matches forward progress by default.
hold_to_clear runtime behavior:
- Bricks continue moving while held (normal conveyor advection still applies).
- Processing depletes visible width while the mouse button is held at
hold_process_rate_px_s. - Works with all targeting areas (
brick,conveyor,spotlight). - If
hold_process_rate_px_sis not set, the runtime usesbrick.speed.
Accepted keys include:
id,label- trait keys:
color/colourwidthborderColor/border_color/border_colourshapetextureStyle/texture_style/texturebrickLabel/brick_labelvalueworkDeadlineMs/work_deadline_mstargetHoldMs/target_hold_msprogressPerPerfect/progress_per_perfect
assignobject with same trait keys
Trait values can be direct values or SamplerSpec.
Supported keys (with aliases):
- position and conveyor:
conveyorIndex/conveyor_indexx, orxFraction/x_fractionrightEdge/right_edge, orrightEdgeFraction/right_edge_fraction
- category picks:
colorCategoryId/color_category_idwidthCategoryId/width_category_idborderColorCategoryId/border_color_category_idshapeCategoryId/shape_category_idtextureCategoryId/texture_category_idcategoryIds: { color, width, borderColor, shape, texture }
- direct traits:
color,colourwidthborderColor/border_color/border_colourshapelabelvalueisTarget/is_targetworkDeadlineMs/work_deadline_mstargetHoldMs/target_hold_msprogressPerPerfect/progress_per_perfect
Each field can be:
- direct value
- sampler object
- list spec:
{
values: unknown[];
draw?: "with_replacement" | "without_replacement" | "sequence";
mode?: same as draw;
weights?: number[]; // normalized if using weighted list sampling
without_replacement?: boolean;
shuffle?: boolean;
}{
enable?: boolean;
stim_type?: "visual" | "audio";
stim_file_audio?: string; // required for audio presentation
stim_visual_config?: {
shape?: string; // renderer interprets
color?: string | number;
size_px?: number;
x?: number;
y?: number;
};
key?: string; // supports "space"/"spacebar"/" "
isi_sampler?: SamplerSpec; // default uniform 3000..7000 ms
response_deadline_ms?: number; // default 1500
audio?: { volume?: number }; // 0..1
}{
seed?: number | string | "random" | "auto";
mode?: "fixed_time" | "max_bricks";
maxTimeSec?: number | null; // used in fixed_time
endDelayMs?: number; // global post-end delay (ms), default 0
brickQuotaEndDelayMs?: number; // post-end delay for "brick_quota_met" (ms), default 3000
stopDrtOnBrickQuotaMet?: boolean; // if true (trial-scoped DRT), stop DRT immediately when quota is met
endDrtOnBrickQuotaMet?: boolean; // alias of stopDrtOnBrickQuotaMet
holdDurationPractice?: {
requiredPresses?: number; // for hold-duration practice runner, forces max_bricks quota
fullWidthConveyor?: boolean; // default true in hold-duration practice runner
centerBrick?: boolean; // default true in hold-duration practice runner
hideHud?: boolean; // default true in hold-duration practice runner
replenishDelayMs?: number; // ms to keep post-release clear state before refilling (default 220)
trialTimeMs?: number; // alias of replenishDelayMs
nextTrialDelayMs?: number; // alias of replenishDelayMs
useSpotlightWindow?: boolean; // enable forced-order spotlight window during hold-duration practice
spotlightWindow?: boolean; // alias of useSpotlightWindow
};
forcedOrder?: {
enable?: boolean;
switchMode?: "on_clear" | "interval" | "interval_or_clear";
switchIntervalMs?: number; // default 8000
switchOnDrop?: boolean; // default true
sequence?: number[]; // index order into forced-set brick list
spotlightPadding?: number; // default 18
dimAlpha?: number; // default 0.45 (clamped 0..0.95)
coverStory?: {
enableAmmoCue?: boolean;
};
};
}{
startTrialsOn?: "space" | "click"; // preferred explicit trigger selector
startTrialsOnSpace?: boolean; // legacy alias for startTrialsOn: "space"
startTrialsOnClick?: boolean; // legacy alias for startTrialsOn: "click"
startOverlay?: {
text?: string;
padding?: string;
background?: string;
border?: string;
borderRadius?: string;
boxShadow?: string;
color?: string;
fontSize?: string;
buttonStyle?: {
padding?: string;
fontSize?: string;
fontWeight?: string | number;
border?: string;
borderRadius?: string;
color?: string;
background?: string;
minWidth?: string;
minHeight?: string;
outline?: string;
boxShadow?: string;
};
buttonAutoFocus?: boolean;
buttonPadding?: string; // click start mode: button padding
buttonFontSize?: string; // click start mode: button font-size
buttonFontWeight?: string | number;
buttonBorder?: string;
buttonBorderRadius?: string;
buttonColor?: string;
buttonBackground?: string;
buttonMinWidth?: string;
buttonMinHeight?: string;
};
showBetweenBlockSummary?: boolean; // currently not consumed in runtime
statsPresentation?: {
defaultScope?: "trial" | "block" | "experiment"; // default "trial"
scopeByMetric?: {
spawned?: "trial" | "block" | "experiment";
cleared?: "trial" | "block" | "experiment";
dropped?: "trial" | "block" | "experiment";
points?: "trial" | "block" | "experiment";
};
reset?: Array<{
at?: "block_start" | "block_end"; // default "block_end"
scope?: "block" | "experiment"; // default "experiment"
metrics?: Array<"spawned" | "cleared" | "dropped" | "points">; // default all metrics
when?: {
isPractice?: boolean;
phaseIn?: string[];
labelIn?: string[];
manipulationIdIn?: string[];
};
}>;
};
}Example: show cleared/dropped per trial while keeping points cumulative across experiment,
then clear only experiment points after practice blocks:
{
"experiment": {
"statsPresentation": {
"defaultScope": "trial",
"scopeByMetric": {
"points": "experiment"
},
"reset": [
{
"at": "block_end",
"scope": "experiment",
"metrics": ["points"],
"when": { "isPractice": true }
}
]
}
}
}{
pointerOverlay?: boolean;
pointerConsole?: boolean;
holdConsole?: boolean;
performanceConsole?: boolean; // if true, logs trial performance summary to console
performanceFrameBudgetMs?: number; // frame budget threshold used for overrun metrics (default 16.67)
saveTrialLog?: boolean;
printTrialLog?: boolean;
trialLogPrefix?: string;
}Used by trial difficulty estimator:
{
holdQualityMean?: number; // 0..1 (default 0.5)
hoverAcquireMs?: number; // default 120
hoverTrackingEfficiency?: number; // default 0.9
avgClickIntervalMs?: number; // default 240
clickAcquireMs?: number; // default 110
}selfReportis allowed and preserved in config snapshots.instructionsis consumed by Bricks UI flow:pages/introPages/intro/screens: task intro pagespreBlockPages/beforeBlockPages/beforeBlockScreens: shown before each block startspostBlockPages/afterBlockPages/afterBlockScreens: shown after each block endsendPages/outroPages/end/outro: shown after all blocksblockIntroTemplate: optional templated text shown on block intro cardsshowBlockLabel: optional bool (default true) for block intro/post headerspreBlockBeforeBlockIntro: optional bool (default false) to place pre-block pages before block intro card
- Instruction pages can be plain strings or objects like
{ title, text }/{ title, html }. - Page
title,text, andhtmlsupport{dot.path}interpolation against merged Bricks config values (and resolver-backed values), e.g.{bricks.completionParams.target_hold_ms}. - Post-trial survey presets (
surveys.postTrial) support display toggles:showQuestionNumbers?: boolean(setfalseto hideQ1/1.)showRequiredAsterisk?: boolean(setfalseto hide required*marker)questionBorder?: string(for example"none"to remove question frame border)questionBorderRadius?: string(for example"0"to square corners)submitButtonStyle?: { ... }(same style fields as core continue buttons)autoFocusSubmitButton?: boolean
Fields that accept sampler specs use this shape:
type SamplerSpec =
| { type: "fixed"; value: unknown }
| { type: "uniform"; min: number; max: number }
| { type: "normal"; mu: number; sd: number; min?: number; max?: number }
| { type: "truncnorm"; mu: number; sd: number; min?: number; max?: number }
| { type: "exponential"; lambda: number; min?: number; max?: number }
| { type: "poisson"; lambda: number; min?: number; max?: number }
| { type: "negative_binomial" | "negbin"; mu: number; k?: number; size?: number; dispersion?: number; r?: number; min?: number; max?: number }
| {
type: "list";
values: unknown[];
weights?: number[]; // must sum to 1
draw?: "with_replacement" | "without_replacement" | "sequence";
mode?: string;
without_replacement?: boolean;
withoutReplacement?: boolean;
shuffle?: boolean;
};{
"display": {
"canvasWidth": 1200,
"canvasHeight": 760,
"preset": {
"mode": "fixed",
"id": "parcel_sorting_holiday"
},
"ui": { "showHUD": true, "showTimer": true, "showDRT": true }
},
"conveyors": {
"nConveyors": 4,
"lengthPx": { "type": "uniform", "min": 860, "max": 1160 },
"speedPxPerSec": { "type": "fixed", "value": 42 }
},
"bricks": {
"completionMode": "hold_duration",
"completionParams": {
"target_hold_ms": 500,
"progress_per_perfect": 0.175
},
"colorCategories": [
{ "id": "NeutralA", "color": "#6b7280" },
{ "id": "NeutralB", "color": "#6b7280" }
],
"textureCategories": [
{ "id": "Target", "textureStyle": "target", "value": 15, "label": "High Value" },
{ "id": "Other", "textureStyle": "other", "value": 5, "label": "Distractor" }
],
"forcedSetPlan": {
"enable": true,
"count": 4,
"fields": {
"textureCategoryId": { "values": ["Target", "Other", "Other", "Other"] }
}
},
"maxBricksPerTrial": 4,
"spawn": {
"ratePerSec": { "type": "fixed", "value": 0 },
"byConveyor": true,
"maxActivePerConveyor": 1
}
},
"drt": {
"enable": true,
"stim_type": "visual",
"key": "space",
"isi_sampler": { "type": "uniform", "min": 3000, "max": 5000 },
"response_deadline_ms": 2500
},
"trial": {
"mode": "fixed_time",
"maxTimeSec": 40,
"forcedOrder": { "enable": true, "switchMode": "on_clear", "sequence": [1, 0, 2, 3] }
},
"blocks": [{ "label": "Block 1", "trials": 4, "manipulation": "m1" }],
"manipulations": [{ "id": "m1", "overrides": {} }]
}