diff --git a/.changeset/arrow-flanker-text-config.md b/.changeset/arrow-flanker-text-config.md new file mode 100644 index 00000000..d2008a8b --- /dev/null +++ b/.changeset/arrow-flanker-text-config.md @@ -0,0 +1,32 @@ +--- +"@jspsych-timelines/arrow-flanker": major +--- + +**Major refactor to use published @jspsych-contrib/plugin-flanker package** + +This release represents a comprehensive refactor of the arrow-flanker timeline to leverage the newly published `@jspsych-contrib/plugin-flanker` package, enabling more flexible stimulus types and improved performance. + +### Breaking Changes + +- Timeline implementation completely refactored to use the `@jspsych-contrib/plugin-flanker` package instead of custom trial logic +- Internal architecture changes may affect advanced users who were directly importing internal utilities + +### New Features + +- **Text Configuration System**: All user-facing text is now configurable via the `text_object` parameter to facilitate translation and customization +- **Improved Sequential Effects Tracking**: Now uses `jsPsych.data.get()` for more reliable tracking of previous trial data +- **Cleaner API**: Utilities are now namespaced under `.utils` export following jspsych-timelines conventions + +### Improvements + +- SOA handling refactored with cleaner `has_soa` flag pattern instead of try/catch +- Only user-facing utilities are exported; internal implementation details are no longer part of the public API +- Better separation of concerns between plugin (stimulus presentation) and timeline (trial ordering, blocks, configuration) + +### Migration Guide + +For most users, this update should be backward compatible. The plugin dependency is automatically installed, so no additional installation steps are required. + +However, if you were: +- Importing internal utilities: These are no longer exported. Use the public API via `utils.*` +- Relying on specific trial implementation details: The underlying plugin has changed, though the timeline API remains the same diff --git a/package-lock.json b/package-lock.json index a33d0de6..747c1dcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2394,6 +2394,15 @@ "jspsych": "^8.0.0" } }, + "node_modules/@jspsych-contrib/plugin-flanker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jspsych-contrib/plugin-flanker/-/plugin-flanker-1.0.0.tgz", + "integrity": "sha512-ZVHhV297O9TxSEjj8Hu7LgFsYJaCcNnnMJ6FEfKmvNwI/ext6mN5G9C0kFsvooHye+AxUmKm1o8TEKUucSz0hw==", + "license": "MIT", + "dependencies": { + "jspsych": "^8.2.1" + } + }, "node_modules/@jspsych-contrib/plugin-spatial-nback": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@jspsych-contrib/plugin-spatial-nback/-/plugin-spatial-nback-1.1.0.tgz", @@ -12038,9 +12047,10 @@ }, "packages/arrow-flanker": { "name": "@jspsych-timelines/arrow-flanker", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "dependencies": { + "@jspsych-contrib/plugin-flanker": "^1.0.0", "@jspsych/plugin-html-keyboard-response": "^2.0.0" }, "devDependencies": { diff --git a/packages/arrow-flanker/CHANGELOG.md b/packages/arrow-flanker/CHANGELOG.md index f53204c9..188b4ad2 100644 --- a/packages/arrow-flanker/CHANGELOG.md +++ b/packages/arrow-flanker/CHANGELOG.md @@ -1,5 +1,16 @@ # @jspsych-timelines/arrow-flanker +## 0.3.0 + +### Minor Changes + +- Refactored to use @jspsych-contrib/plugin-flanker for stimulus presentation +- Plugin now handles RAF-based SOA timing, response collection, and stimulus rendering +- Timeline package focuses on experiment orchestration (trial order, blocks, congruency ratios) +- Added peer dependency on @jspsych-contrib/plugin-flanker ^1.0.0 +- Removed internal stimulus generation code (now handled by plugin) +- Improved timing precision with requestAnimationFrame implementation + ## 0.2.0 ### Minor Changes diff --git a/packages/arrow-flanker/README.md b/packages/arrow-flanker/README.md index ce7ee295..d55001e2 100644 --- a/packages/arrow-flanker/README.md +++ b/packages/arrow-flanker/README.md @@ -2,12 +2,187 @@ ## Overview -This timeline shows a sequence of arrow flanker trials. Participants are supposed to respond to the arrow in the middle of the screen and ignore the flankers. Half of the trials will be congruent (flankers match the target) and half incongruent. +A comprehensive implementation of the Eriksen Flanker Task using arrow stimuli for jsPsych. Measures selective attention and response inhibition by requiring participants to respond to a central target arrow while ignoring flanking arrows. Supports extensive parameterization for research applications including temporal manipulation (SOA), spatial configuration, congruency ratio control, sequential effects tracking, and multiple block designs. -## Functions +## Loading -### `createTimeline` +### Via NPM -### `timelineUnits` +```bash +npm install @jspsych-timelines/arrow-flanker +``` -### `utils` \ No newline at end of file +```js +import { createTimeline } from '@jspsych-timelines/arrow-flanker' +``` + +### In browser + +```html + +``` + +## Compatibility + +`@jspsych-timelines/arrow-flanker` requires: +- jsPsych v8.0.0 or later +- `@jspsych-contrib/plugin-flanker` v1.0.0 or later (peer dependency) + +## Documentation + +### createTimeline + +#### jsPsychTimelineArrowFlankerTask.createTimeline(jsPsych, { *options* }) ⇒ timeline + +Creates a complete Arrow Flanker Task timeline with configurable parameters for research applications. + +**Basic usage:** +```javascript +const jsPsych = initJsPsych(); + +const timeline = jsPsychTimelineArrowFlankerTask.createTimeline(jsPsych, { + fixation_duration: 500, + num_trials: 24 +}); + +jsPsych.run(timeline.timeline); +``` + +**Advanced usage (SOA manipulation):** +```javascript +const timeline = jsPsychTimelineArrowFlankerTask.createTimeline(jsPsych, { + soa: [-200, -100, 0, 100, 200], // Temporal manipulation + stimulus_duration: 100, + congruency_ratio: { congruent: 30, incongruent: 70 }, + track_sequence_effects: true, + num_blocks: 4, + num_trials: 84 +}); +``` + +The following parameters can be specified in the **options** parameter. + +#### Temporal Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `soa` | number \| number[] \| {min, max} | `0` | Stimulus Onset Asynchrony (ms). Controls timing between flanker and target onset. Single value, array of values to sample, or range object. | +| `stimulus_duration` | number \| null | `null` | Stimulus display duration (ms). `null` = response-terminated | +| `fixation_duration` | number | `500` | Fixation cross duration (ms) | +| `iti_duration` | number | `0` | Inter-trial interval (ms) | +| `response_timeout` | number | `1500` | Maximum response time allowed (ms) | + +#### Spatial Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `stimulus_size` | string | `'48px'` | Size of individual arrow elements | +| `target_flanker_separation` | string | `'10px'` | Space between target and flankers | +| `fixation_size` | string | `'24px'` | Size of fixation cross | +| `stimulus_container_height` | string | `'100px'` | Container height to prevent layout shifts | +| `flanker_arrangement` | 'horizontal' \| 'vertical' | `'horizontal'` | Orientation of flanker array | +| `num_flankers` | 4 \| 6 | `4` | Number of flankers (creates 5 or 7-item arrays) | + +#### Design Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `include_neutral` | boolean | `false` | Include neutral trials with non-directional flankers | +| `neutral_stimulus` | string | (dash SVG) | Custom SVG for neutral flanker stimulus | +| `block_design` | 'mixed' \| 'blocked' | `'mixed'` | Trial presentation order (randomized or grouped) | +| `congruency_ratio` | object | `{congruent: 1, incongruent: 1}` | Relative proportions of trial types. E.g., `{congruent: 25, incongruent: 75}` | +| `track_sequence_effects` | boolean | `false` | Add previous trial information for CSE analysis | +| `num_blocks` | number | `1` | Number of experimental blocks | +| `num_trials` | number | `12` | Number of trials per block | +| `block_break_duration` | number \| null | `null` | Block break duration (ms). `null` shows continue button | + +#### Response Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `response_keys` | object | `{left: ['ArrowLeft'], right: ['ArrowRight']}` | Response key mapping for left/right | +| `data_labels` | object | `{}` | Custom data labels added to all trials | + +#### Legacy Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `n` | number | - | Alias for `num_trials` (backward compatibility) | + +### timelineUnits + +Building blocks for custom timeline construction: + +- **`createFixationTrial(options)`**: Creates a fixation cross trial +- **`createFlankerTrial(jsPsych, options)`**: Creates a flanker stimulus trial with response collection +- **`createITITrial(options)`**: Creates an inter-trial interval blank screen +- **`createBlockBreak(options)`**: Creates a block break screen + +**Example:** +```javascript +const fixation = jsPsychTimelineArrowFlankerTask.timelineUnits.createFixationTrial({ + duration: 500 +}); +``` + +### Exported utilities + +Additional functions for advanced customization: + +- **`generateTrialVariables(jsPsych, options)`**: Generates timeline variables for a block +- **`createFlankerStimulus(direction, congruency, options)`**: Creates HTML for a flanker stimulus array +- **`mergeConfig(userConfig, defaults)`**: Merges configurations + +## Data + +Each trial records the following data: + +| Name | Type | Description | +|------|------|-------------| +| `task` | string | Always 'flanker' | +| `phase` | string | 'response', 'fixation', 'iti', or 'block_break' | +| `direction` | string | Target direction: 'left' or 'right' | +| `congruency` | string | Trial type: 'congruent', 'incongruent', or 'neutral' | +| `soa` | number | SOA value for this trial (ms) | +| `block_number` | number | Current block number | +| `trial_number` | number | Trial number within block | +| `previous_congruency` | string | Previous trial congruency (if `track_sequence_effects: true`) | +| `previous_direction` | string | Previous trial direction (if `track_sequence_effects: true`) | +| `rt` | number | Reaction time (ms) | +| `response` | string | Key pressed | +| `correct` | boolean | Response accuracy | + +## Examples + +Complete working examples are available in the [examples directory](examples/): + +- **[Basic Usage](examples/index.html)** - Simple flanker task with default settings +- **[SOA Manipulation](examples/advanced-soa.html)** - Temporal dynamics research with multiple SOA values +- **[Congruency Ratio](examples/congruency-ratio.html)** - Global control manipulation (high vs low conflict) +- **[Neutral Trials](examples/neutral-trials.html)** - Separate facilitation from interference +- **[Sequential Effects](examples/sequential-effects.html)** - Congruency Sequence Effect (Gratton effect) + +See [examples/README.md](examples/README.md) for detailed descriptions and research applications. + +## Research Applications + +This package supports investigating: + +1. **Response Competition** - Use SOA manipulation to isolate response selection stage +2. **Perceptual Filtering** - Small separations + brief durations test visual processing +3. **Cognitive Control Adaptation** - Sequential effects tracking enables CSE analysis +4. **Global vs Local Control** - Congruency ratio manipulation tests proactive control +5. **Temporal Dynamics** - SOA arrays reveal time course of interference + +## Author / Citation + +**Author:** Josh de Leeuw +**GitHub:** [@jodeleeuw](https://github.com/jodeleeuw) + +If you use this package in your research, please cite: + +``` +Eriksen, B. A., & Eriksen, C. W. (1974). Effects of noise letters upon the +identification of a target letter in a nonsearch task. Perception & Psychophysics, +16(1), 143-149. +``` diff --git a/packages/arrow-flanker/examples/README.md b/packages/arrow-flanker/examples/README.md new file mode 100644 index 00000000..38ff8bee --- /dev/null +++ b/packages/arrow-flanker/examples/README.md @@ -0,0 +1,214 @@ +# Arrow Flanker Task - Examples + +This directory contains working examples demonstrating various features of the arrow-flanker package. + +## Available Examples + +### 1. **index.html** - Basic Flanker Task +A simple implementation with default settings. + +**Features demonstrated:** +- Basic configuration +- Fixation + stimulus + response sequence +- Multiple blocks with breaks +- Instructions + +**Configuration:** +```javascript +{ + fixation_duration: 500, + num_trials: 24, + num_blocks: 2 +} +``` + +**Use case:** Standard flanker task experiment + +--- + +### 2. **advanced-soa.html** - SOA Manipulation +Demonstrates temporal manipulation using Stimulus Onset Asynchrony (SOA). + +**Features demonstrated:** +- Array-based SOA sampling +- Brief stimulus duration +- Data analysis by SOA condition +- Sequential effects tracking + +**Configuration:** +```javascript +{ + soa: [-200, -100, 0, 100, 200], + stimulus_duration: 100, + track_sequence_effects: true, + num_trials: 40 +} +``` + +**Use case:** Research on temporal dynamics of response competition + +**Research application:** Isolate response selection vs perceptual filtering stages by varying when flankers appear relative to target + +--- + +### 3. **congruency-ratio.html** - Global Control Manipulation +Demonstrates manipulation of conflict expectation through trial proportions. + +**Features demonstrated:** +- Custom congruency ratios +- Multiple blocks with different contexts +- High-conflict vs low-conflict environments +- Block-specific data analysis + +**Configuration:** +```javascript +// Block 1: High-conflict +{ + congruency_ratio: { congruent: 25, incongruent: 75 } +} + +// Block 2: Low-conflict +{ + congruency_ratio: { congruent: 75, incongruent: 25 } +} +``` + +**Use case:** Study proactive vs reactive cognitive control + +**Research application:** Test predictions that high-conflict contexts reduce overall flanker effect due to sustained control + +--- + +### 4. **neutral-trials.html** - Neutral Trials +Demonstrates inclusion of neutral trials to separate facilitation from interference. + +**Features demonstrated:** +- Three trial types (congruent, incongruent, neutral) +- Equal proportions +- Facilitation vs interference analysis + +**Configuration:** +```javascript +{ + include_neutral: true, + congruency_ratio: { + congruent: 1, + incongruent: 1, + neutral: 1 + }, + num_trials: 36 +} +``` + +**Use case:** Distinguish response facilitation from response interference + +**Research application:** Determine whether flanker effects are driven by helpful congruent flankers, harmful incongruent flankers, or both + +--- + +### 5. **sequential-effects.html** - Sequential Effects (Gratton Effect) +Demonstrates trial-to-trial adaptations in cognitive control. + +**Features demonstrated:** +- Sequential effects tracking +- Congruency Sequence Effect (CSE) analysis +- Four transition types (cC, cI, iC, iI) +- Gratton effect calculation + +**Configuration:** +```javascript +{ + track_sequence_effects: true, + congruency_ratio: { congruent: 1, incongruent: 1 }, + num_trials: 64 +} +``` + +**Use case:** Examine dynamic adjustments in cognitive control + +**Research application:** Test whether experiencing conflict on trial n-1 reduces the flanker effect on trial n, supporting reactive control mechanisms + +--- + +## Running the Examples + +### Method 1: Local Build +1. Build the package: `npm run build` +2. Open any HTML file in a web browser +3. The examples load the built package from `../dist/index.global.js` + +### Method 2: From unpkg (see load-from-unpkg.html) +Load the published package directly from unpkg CDN: +```html + +``` + +## Understanding the Output + +All examples include data analysis in the browser console. Open Developer Tools (F12) to see: + +- **Basic example:** Simple completion message +- **SOA example:** RT and flanker effect by SOA condition +- **Congruency ratio:** Flanker effect by block (high vs low conflict) +- **Neutral trials:** Facilitation and interference components +- **Sequential effects:** CSE analysis with all four transition types + +## Customization Tips + +### Timing Parameters +```javascript +fixation_duration: 500, // Time before stimulus +stimulus_duration: 100, // How long stimulus shows (null = until response) +iti_duration: 200, // Blank time after response +response_timeout: 1500 // Max time to respond +``` + +### Spatial Parameters +```javascript +stimulus_size: '64px', // Make arrows bigger +target_flanker_separation: '20px', // More spacing +flanker_arrangement: 'vertical', // Stack vertically +num_flankers: 6 // 7-item array instead of 5 +``` + +### Response Keys +```javascript +response_keys: { + left: ['f', 'F'], + right: ['j', 'J'] +} +``` + +## Research Design Guidelines + +### Minimum Trial Counts +- **Basic flanker effect:** 20-40 trials (balanced congruent/incongruent) +- **SOA manipulation:** 80-140 trials (balanced across SOA × congruency) +- **Sequential effects:** 60-100 trials (need sufficient n for all transitions) +- **Between-subjects design:** Consider practice block of 10-20 trials + +### Block Structure +- Use breaks every 50-80 trials to maintain attention +- Consider counterbalancing block order for ratio manipulations +- First block often shows practice effects - consider exclusion or longer practice + +### Data Quality +- Monitor accuracy (typically >90% for valid data) +- Check for outlier RTs (common cutoffs: <200ms or >2000ms) +- Verify sufficient trials per condition after exclusions + +## Additional Resources + +- **README.md** - Package documentation and feature overview +- **API.md** - Complete API reference with all parameters +- **plan.md** - Research background and theoretical framework + +## Contributing + +Have an example demonstrating another use case? Feel free to contribute! + +Common requests: +- Blocked design example +- Custom stimulus sizing for visual angle control +- Integration with other jsPsych plugins +- Advanced data export and analysis diff --git a/packages/arrow-flanker/examples/advanced-soa.html b/packages/arrow-flanker/examples/advanced-soa.html new file mode 100644 index 00000000..2f8d2a8f --- /dev/null +++ b/packages/arrow-flanker/examples/advanced-soa.html @@ -0,0 +1,91 @@ + + + + + Arrow Flanker Task - SOA Manipulation Example + + + + + + + + + + + diff --git a/packages/arrow-flanker/examples/congruency-ratio.html b/packages/arrow-flanker/examples/congruency-ratio.html new file mode 100644 index 00000000..ba6110bf --- /dev/null +++ b/packages/arrow-flanker/examples/congruency-ratio.html @@ -0,0 +1,131 @@ + + + + + Arrow Flanker Task - Congruency Ratio Example + + + + + + + + + + + diff --git a/packages/arrow-flanker/examples/index.html b/packages/arrow-flanker/examples/index.html index 84cb5f2e..da8fa282 100644 --- a/packages/arrow-flanker/examples/index.html +++ b/packages/arrow-flanker/examples/index.html @@ -2,27 +2,53 @@ - - + Arrow Flanker Task - Basic Example + + - + + - \ No newline at end of file + diff --git a/packages/arrow-flanker/examples/neutral-trials.html b/packages/arrow-flanker/examples/neutral-trials.html new file mode 100644 index 00000000..3d75aba9 --- /dev/null +++ b/packages/arrow-flanker/examples/neutral-trials.html @@ -0,0 +1,100 @@ + + + + + Arrow Flanker Task - Neutral Trials Example + + + + + + + + + + + diff --git a/packages/arrow-flanker/examples/sequential-effects.html b/packages/arrow-flanker/examples/sequential-effects.html new file mode 100644 index 00000000..78cceed4 --- /dev/null +++ b/packages/arrow-flanker/examples/sequential-effects.html @@ -0,0 +1,125 @@ + + + + + Arrow Flanker Task - Sequential Effects Example + + + + + + + + + + + diff --git a/packages/arrow-flanker/package.json b/packages/arrow-flanker/package.json index a61bcad6..81801dd7 100644 --- a/packages/arrow-flanker/package.json +++ b/packages/arrow-flanker/package.json @@ -1,6 +1,6 @@ { "name": "@jspsych-timelines/arrow-flanker", - "version": "0.2.0", + "version": "0.3.0", "description": "Arrow flanker task for jsPsych", "type": "module", "main": "dist/index.mjs", @@ -29,10 +29,11 @@ "jspsych": "^8.0.1" }, "dependencies": { + "@jspsych-contrib/plugin-flanker": "^1.0.0", "@jspsych/plugin-html-keyboard-response": "^2.0.0" }, "devDependencies": { "tsup": "^8.0.1", "typescript": "^5.5.4" } -} \ No newline at end of file +} diff --git a/packages/arrow-flanker/plan.md b/packages/arrow-flanker/plan.md new file mode 100644 index 00000000..b644a2e4 --- /dev/null +++ b/packages/arrow-flanker/plan.md @@ -0,0 +1,197 @@ + + +# **The Parameterized Space of Cognitive Control: A Comprehensive Analysis and Taxonomy of the Eriksen Flanker Task Methodology** + +## **I. Conceptual and Historical Foundations of Interference Control** + +### **The Eriksen Flanker Task (EFT): Definition, Purpose, and Primary Behavioral Measure** + +The Eriksen Flanker Task (EFT) stands as a cornerstone paradigm within cognitive science, fundamentally designed to assess the crucial executive function of inhibitory control—the ability to selectively attend to a target stimulus while suppressing irrelevant, yet often compelling, flanking information.1 Developed by Barbara A. Eriksen and Charles W. Eriksen and first published in 1974, the task operationalizes the cognitive processes involved in resolving stimulus conflict and response competition.2 + +The original experimental implementation utilized symbolic **letter stimuli**.2 Participants were instructed to make a directional response (e.g., left or right) based on the central target letter. For example, letters H and K might be arbitrarily mapped to a right response, while S and C were mapped to a left response.2 The full display typically consisted of a set of seven letters, with the target situated centrally.2 + +The core measurement, known as the **Flanker Effect**, is derived from comparing performance across three primary stimulus conditions: + +1. **Congruent (Compatible) trials:** Flankers correspond to the same directional response as the target (e.g., HHHHH).2 +2. **Incongruent (Incompatible) trials:** Flanker items call for the opposite response of the target (e.g., SSHSS).2 +3. **Neutral trials:** Flanker items neither suggest the target response nor evoke a response conflict (e.g., using squares or non-mapped symbols).2 + +The Flanker Effect is quantified as the interference cost, characterized by prolonged **Reaction Time (RT)** and reduced **Percentage Error (PE)** accuracy on incongruent trials compared to congruent trials.1 The inclusion of neutral trials is essential for dissociating the impact of *response conflict* (Incongruent vs. Neutral) from general *visual distraction* or increased target recognition difficulty (Congruent vs. Neutral).2 + +### **The Locus of Conflict: From Perceptual Filtering to Response Competition Theory** + +A central theoretical debate surrounding the EFT concerns the precise stage of information processing at which the irrelevant flanker stimulus exerts its influence.5 Conflict paradigms generally suggest interference occurs either early, during perceptual filtering, or later, during response selection and competition.6 + +Experimental design parameters offer a methodology for systemically investigating this distinction. If interference were primarily due to early perceptual limitations, manipulation of **spatial parameters** (such as Target-Flanker Separation, Parameter P.1) should have the greatest impact on effect size. Conversely, if interference primarily arises during late-stage response competition, manipulation of **temporal parameters** (such as Stimulus Onset Asynchrony, Parameter P.2) and the complexity of the Stimulus-Response (S-R) mapping rules (Parameter S.2) should be the key modulators. For instance, robust interference observed even at large spatial separation suggests that the distracting information has bypassed initial attentional filtering and is operating directly on pre-activated response codes. By setting up experiments where these spatial and temporal parameters are systematically varied (e.g., short SOA coupled with wide separation, or vice versa), researchers can isolate and measure the sensitivity of the interference effect to specific cognitive stages, thereby providing an operational means of separating perceptual limitations from response inhibition mechanisms.6 + +### **Contextualizing the EFT: Similarities and Distinctions** + +The EFT is frequently categorized alongside other classic conflict paradigms, notably the Stroop task and the Simon task, as tools for investigating interference from irrelevant stimulus features.2 While all measure cognitive control, they differ in the source of conflict. The key distinction lies in the Simon task, which specifically manipulates *spatial compatibility* (whether the stimulus location matches the required response location, regardless of content). In contrast, the EFT primarily measures *feature-based* or *directional compatibility* (whether the content of the flanker matches the required response to the target).2 + +## **II. Stimulus and Response Mapping Parameters (The Input Space)** + +The definition of the EFT’s input space requires precise specification of the sensory information provided to the participant, determining the nature of the interference generated. + +### **Stimulus Content and Sensory Modality (Parameter Cluster S.1)** + +The type of stimulus material utilized dictates the complexity and domain of the conflict: + +* **Visual Directional Stimuli (Arrows):** This is the most prevalent contemporary version of the task. Arrows pointing left or right (e.g., $\>\>\>\<\>$) are used, which naturally align with directional motor responses, establishing a **Natural/Direct S-R Mapping**.2 +* **Visual Symbolic Stimuli (Letters/Numbers):** As featured in the original task, these stimuli require an **Arbitrary S-R Mapping**.2 +* **Feature-Based Stimuli (Colors/Shapes):** Flanker tasks can utilize non-symbolic visual features, such as colored discs.4 When color is used, advanced methodology dictates controlling the perceptual distance between target and flanker stimuli using established color space metrics (e.g., HSV or RGB). For example, target and flanker colors may be placed 180° apart in HSV color space to ensure maximum color conflict.4 +* **Auditory and Semantic Stimuli:** The EFT methodology extends beyond the visual domain. Auditory variations, such as the **Auditory Semantic Flanker**, use spoken words or category names, demanding rapid semantic classification and response.5 +* **Bimodal Design:** An important variation involves using stimuli across sensory boundaries, such as visual target letters paired with auditory flanker letters.6 + +### **Response Mapping Rules (Parameter Cluster S.2)** + +The complexity of the rule linking the perceived stimulus to the motor output is a powerful parameter for modulating cognitive control demands: + +* **Arbitrary Mapping (Indirect S-R):** The instructed mapping is non-intuitive (e.g., Letter ‘C’ $\\rightarrow$ Left response).2 Maintaining this arbitrary task set requires sustained, high-level executive control, which correlates with preparatory activity in regions such as the left Dorsolateral Prefrontal Cortex (DLPFC).10 +* **Natural Mapping (Direct S-R):** The stimulus feature naturally suggests the required response (e.g., Left Arrow $\\rightarrow$ Left Keypress).2 This direct relationship typically promotes stronger, more automatic activation of the competing motor pathways.11 + +### **Sensory Integration and Bimodal Parameters** + +The parameter of **Modality Integration** (unimodal vs. bimodal) serves as a critical tool for localizing the source of interference. In bimodal flanker tasks (e.g., auditory flanker, visual target), the conflict must necessarily involve cognitive processing deep enough to cross sensory modalities and activate competing response representations.6 + +Research utilizing bimodal paradigms has demonstrated that a flanker-like effect emerges only when the auditory flankers and visual targets share the *same semantic identity* (e.g., both are the letter name 'A'), but not when they are different letters merely mapped onto the *same motor response*.6 This key finding suggests that the auditory flankers influence the time required to recognize the visual targets perceptually or semantically, rather than directly activating the manual motor response codes. Therefore, the parameter of **Flanker Modality** is essential for dissociating interference occurring at the perceptual/semantic recognition stage from interference at the response selection stage. Furthermore, evidence indicates that the distractor modality constrains global behavioral adaptation effects, potentially due to the learning of modality-specific memory traces or the engagement of modality-specific cognitive control processes.12 + +## **III. Psychophysical and Temporal Parameters (Controlling Presentation)** + +To effectively isolate sensory processing and response timing, the physical presentation of the stimuli must be precisely controlled using quantitative parameters. + +### **Spatial Metrics (Parameter Cluster P.1)** + +Spatial parameters are crucial as they are typically measured in **degrees of visual angle (dva)** to standardize the retinal size and eccentricity across different participant viewing conditions. + +* **Parameter: Stimulus Size:** Defines the angular size of the elements. For example, individual target or flanker discs often have a diameter of $\\sim 1.5$ dva, while a central fixation cross might subtend only $\\sim 0.45$ dva.4 +* **Parameter: Target-Flanker Separation (TFS):** This parameter is the distance, measured in dva, between the central target and the nearest distracting flanker.13 Manipulating TFS is critical for testing the spatial extent of selective attention. Small separations (e.g., $\< 1.0$ dva) maximize perceptual overlap and interference, while large separations test the boundaries of inhibitory control.13 +* **Parameter: Flanker Arrangement:** Describes the configuration of the flankers relative to the target. The most common configuration is the **Horizontal Array** 2, but variations include the **Vertical Array** where flankers are positioned above and below the target.2 +* **Parameter: Number of Flankers:** The total quantity of irrelevant items presented. Standard implementations use 4 or 6 flankers, creating 5-item (e.g., $\<\>\<\<$) or 7-item arrays (as in the original 1974 study).2 + +### **Temporal Dynamics (Parameter Cluster P.2)** + +Temporal parameters govern the sequence and duration of exposure, enabling researchers to manipulate the availability of conflicting information relative to the target: + +* **Parameter: Stimulus Onset Asynchrony (SOA):** The time delay between the onset of the flanker stimuli and the onset of the central target. The SOA is arguably the most powerful temporal manipulation, allowing for the precise timing of conflict induction. SOA can be fixed or randomized, sampling a continuous range including negative values (flanker precedes target) and positive values (target precedes flanker). Documented sampled values in experimental designs include **\[-400, \-200, \-100, \-50, 0, \+30, \+50, \+100, \+200\] ms** relative to the target onset.14 + * By testing negative SOAs, researchers ensure that the flanker information is processed and has activated its associated (but incorrect) response pathway before the target is even visible. Positive SOAs test the system's capacity to rapidly suppress conflicting input that arrives slightly after the central target. Analyzing interference magnitude as a function of SOA creates a temporal profile of response activation, revealing the temporal window of maximal interference, often centered near 0 ms SOA. +* **Parameter: Stimulus Array Duration (Exposure):** The total time the complete stimulus display remains on screen. This can be a fixed, brief duration (e.g., 100 ms after the scheduled SOA) to minimize re-fixation and subsequent processing 14, or it can be **Response-Terminated** (remaining visible until the participant responds).1 + +### **Trial Flow Parameters (Parameter Cluster P.3)** + +* **Parameter: Inter-Trial Interval (ITI):** The temporal gap between the end of one trial and the beginning of the next, often encompassing a period of fixation (e.g., 950 ms fixation followed by a 50 ms gap).14 The ITI is a critical factor, particularly in neuroimaging studies, where several seconds may be required to allow the BOLD signal to return to baseline, and in behavioral studies, where ITI length influences the presence and magnitude of sequential effects. +* **Parameter: Response Window (Max RT):** The maximum time allowed for the participant to execute a response. Typical constraints aim for rapid responses, often setting the limit at **1.0 s** or less, or instructing participants to maintain an average RT under $1$ s.5 + +## **IV. Experimental Design and Contextual Parameters** + +These parameters define the overarching structure of the experiment, modulating the participant's readiness, expectation, and the dynamic regulation of cognitive control across trials. + +### **Block Structure (Parameter Cluster D.1)** + +The method of sequencing trials influences whether participants rely on a stable, sustained level of control or adapt rapidly on a trial-by-trial basis: + +* **Mixed Design (Randomized):** Congruent (C), Incongruent (I), and Neutral (N) trials are intermixed randomly throughout the block.4 This structure necessitates a consistent, high-level state of **Proactive Control** because conflict could occur on any given trial. +* **Blocked Design (Pure or Proportional):** Trials are grouped by congruency or feature blocks, or the overall proportion of conflict is manipulated by block. Cued paradigms, which introduce a preparation cue before the stimulus array, are also used within blocked designs to examine **Task-Set Maintenance**.10 Such cued designs have demonstrated that task preparation primes task-relevant brain areas and highlights the importance of the left DLPFC in top-down control during the cue period.10 + +### **Global Control Manipulation: Congruency Proportion (Parameter Cluster D.2)** + +The relative frequency of conflict trials within a block determines the predictability of the environment and serves as a powerful parameter for manipulating global control settings: + +* **Balanced Ratio (50:50):** The standard setting, minimizing bias in conflict expectation.4 +* **Biased Ratios (e.g., 75:25 or 25:75):** Used to manipulate the expectation of conflict. In blocks where $75\\%$ of trials are congruent, the task environment is generally safe, which encourages participants to lower their baseline preparatory effort (proactive control). This reduction in sustained control results in a significantly larger flanker effect when the rare $25\\%$ incongruent trials occur.15 Conversely, a block containing only $25\\%$ congruent trials forces continuous vigilance, thereby enhancing proactive control and typically *reducing* the overall magnitude of the flanker effect. Thus, manipulating the Incongruent-to-Congruent (I:C) ratio provides a crucial causal parameter for studying the resource allocation of global cognitive control and the learning of modality-specific adaptation effects.12 + +### **Sequential Trial Effects (Parameter Cluster D.3)** + +These parameters investigate local, dynamic adjustments in control, often referred to as the Congruency Sequence Effect (CSE) or Gratton effect: + +* **Parameter: Congruency Sequence Effect (CSE):** Defined as the observation that the flanker interference effect (I-C difference) is smaller when the current trial ($n$) follows an incongruent trial ($n-1$) than when it follows a congruent trial.5 The analysis of trial transitions (iI, cC, cI, iC) is necessary to measure the influence of the previous trial's outcome on current performance.16 +* **Interaction Parameters (Repetition Type):** To refine the understanding of the CSE, modern designs separate the contribution of: + * **Flanker Repetition:** Evidence suggests that repetition of the irrelevant flanker stimulus alone may be sufficient to generate the CSE, indicating that irrelevant information is bound to response representations regardless of target relevance.5 + * **Target Repetition:** Whether the target identity is repeated. + * **Response Repetition:** Whether the required motor response is repeated. + * The interpretation of the CSE often aligns with reactive control—an immediate, adaptive adjustment mechanism triggered by the recent conflict experience. This control adjustment is hypothesized to stem from the binding and updating of fleeting *event files* that associatively link the target, flanker, and response representations.5 This process is frequently conceptualized as conflict-modulated reinforcement learning, where experiencing conflict on trial $n-1$ reinforces the necessary control settings for trial $n$.5 + +## **V. Measurement and Output Parameters** + +The utility of the EFT paradigm is maximized through the comprehensive measurement of both overt behavior and underlying neural activity. + +### **Behavioral Measures (Parameter Cluster M.1)** + +Performance metrics provide the primary indices of inhibitory capacity: + +* **Reaction Time (RT):** The main index of processing speed. Typical reported values show incongruent trials (M $\\approx 821$ ms) are significantly longer than congruent trials (M $\\approx 780$ ms).4 +* **Percentage Error (PE):** The accuracy measure, where incompatible trials (M $\\approx 4.47\\%$) typically result in higher error rates than compatible trials (M $\\approx 3.34\\%$).17 +* **Effect Size:** Standardized metrics quantify the magnitude of the conflict effect. For the effect of trial type on RT, effect sizes can be very large, such as partial eta squared ($\\eta\_p^2$) values up to $0.759$.4 +* **Inverse Efficiency Scores (IES):** Calculated as RT divided by accuracy (1 \- PE), IES is a combined metric used to address and mitigate potential speed-accuracy trade-offs across different experimental groups or conditions.15 + +### **Response Output Modality (Parameter Cluster M.2)** + +The physical method used to register the response profoundly influences the type of data collected: + +* **Manual Keypress/Lever:** The traditional output, yielding discrete RT and PE data.3 +* **Vocal Response:** Measures voice onset latency, used to probe speech planning and articulation under conflict.18 +* **Gaze Shift Latencies:** Eye movements are tracked, providing an index of overt attention timing relative to response selection, particularly in dual-task scenarios.18 +* **Mouse Tracking/Continuous Response:** This method records the entire spatial trajectory of the response movement from trial onset to selection.4 This transforms the outcome from a discrete latency into a continuous kinematic measure, allowing for analysis of early cognitive processes, such as the maximum spatial deviation of the trajectory, which quantifies conflict during the real-time motor planning phase.4 + +### **Neurophysiological Measures (Parameter Cluster M.3)** + +Integrating neurophysiological data allows for a mechanistic understanding of conflict detection and resolution: + +* **Electroencephalography (EEG) Parameters:** + * **Sampling Rate:** Data acquisition often occurs at rates such as **512 Hz**.20 + * **Electrode Impedance:** Must be maintained below 5 kOhm to ensure high signal quality.20 + * **Event-Related Potential (ERP) Components:** + * The **N2/N200** component: A negative waveform peaking around $200$ ms post-stimulus at anterior scalp sites, commonly inferred to index conflict monitoring or inhibitory processes.21 + * The **Error Related Negativity (ERN or Ne)** component: A sharp negative wave that emerges immediately following an erroneous response, signaling the brain’s rapid detection of a conflict or error.11 +* **Functional Magnetic Resonance Imaging (fMRI) Parameters:** + * **Regions of Interest (ROIs):** Conflict processing heavily engages the network comprising the **Anterior Cingulate Cortex (ACC)** 23 (primary role in conflict detection) and the **Dorsolateral Prefrontal Cortex (DLPFC)** 10 (key role in top-down control and task-set maintenance). +* **Integration and Functional Interpretation:** Simultaneous EEG-fMRI allows for superior spatio-temporal resolution, although it presents technical challenges related to signal degradation.22 Critically, analysis of the ERN component transcends merely registering error frequency. Single-trial ERN amplitudes have been proven to predict task behavior in the *subsequent trial*.22 This demonstrates that the neural response to conflict is not merely an outcome metric but functions as an active, internal parameter that informs and modulates the control setting applied to future performance, linking neural activity directly to the behavioral CSE effect. + +## **VI. The Parameterized Space: Comprehensive Taxonomy and Implementation Guidelines** + +The exhaustive taxonomy provided here structures the EFT as a fully parameterized experimental paradigm, suitable for systematic modeling and variation. + +Table VI.1: Categorical Parameters Defining Flanker Task Variations + +| Parameter Domain | Parameter Name | Possible Values (Categorical/Type) | Exemplary Implementations | +| :---- | :---- | :---- | :---- | +| **Stimulus/Input** | Stimulus Content Type | Arrows, Letters (Symbolic), Numbers, Colors/Shapes/Discs, Pictures (Semantic) | Modern Arrow Flanker; Original Eriksen (Letters); Color Flanker 2 | +| **Stimulus/Input** | Response Mapping Type | Arbitrary (Indirect S-R), Natural/Direct (Spatially/Directionally Compatible) | Letters H/K $\\rightarrow$ Right key; Right Arrow $\\rightarrow$ Right key 2 | +| **Stimulus/Input** | Sensory Modality | Unimodal (Visual), Unimodal (Auditory), Bimodal (e.g., Auditory Flanker/Visual Target) | Standard Visual EFT; Auditory Letter Flanker 6 | +| **Trial Condition** | Congruency Status | Congruent, Incongruent, Neutral, High-Conflict vs. Low-Conflict | Example: $\<\<\<\<\<$ (C), $\<\<\>\<\<$ (I),\>\< (N) 2 | +| **Experimental Design** | Block Structure | Blocked (Pure), Mixed (Randomized), Cued (Inclusion of pre-trial preparation cue) | Used to study Global vs. Local adaptation; Cued design isolates DLPFC function 4 | +| **Measurement/Output** | Response Modality | Manual (Keypress/Lever), Vocal, Mouse Tracking, Gaze Shift | Standard RT collection; Mouse Tracking for continuous kinematic data 4 | +| **Measurement/Output** | Neural Measurement Type | Behavioral only, EEG/ERP, fMRI/BOLD, Simultaneous EEG-fMRI | Used to measure N2, ERN, ACC/PFC activity 11 | + +Table VI.2: Quantitative Parameters Defining Flanker Task Psychophysics and Design + +| Parameter Domain | Parameter Name | Unit of Measure | Typical Range / Discrete Values | +| :---- | :---- | :---- | :---- | +| **Temporal Control** | Stimulus Onset Asynchrony (SOA) | Milliseconds (ms) | Fixed or Randomized samples: **\[-400, \-200, \-50, 0, \+50, \+200\] ms** | +| **Temporal Control** | Stimulus Array Duration (Exposure) | Milliseconds (ms) | Fixed: 100 ms; Max limit: 1,500 ms; Unlimited (Response-terminated) 1 | +| **Temporal Control** | Inter-Trial Interval (ITI) | Milliseconds (ms) | Minimum $\\sim 1,000$ ms (e.g., 950 ms fixation \+ 50 ms gap) 14; several seconds for fMRI. | +| **Temporal Control** | Response Window (Max RT) | Seconds (s) | Typically $\\le 1.0$ s 5 | +| **Spatial Control** | Stimulus Size (Diameter/Height) | Degrees Visual Angle (dva) | Target/Flanker: $\\sim 1.5$ dva; Fixation: $\\sim 0.45$ dva 4 | +| **Spatial Control** | Target-Flanker Separation | Degrees Visual Angle (dva) | Continuous variable; manipulated to test visual field/eccentricity effects 13 | +| **Design/Trial Control** | Congruency Proportion (I:C) | Ratio (%) | Balanced (50:50); Biased (e.g., 75:25 or 25:75) 15 | +| **Design/Trial Control** | Total Trial Count (N) | Integer Count (N) | Minimum recommended: $\>100$ 7; Typical range: 400 \- 864 trials 4 | +| **Measurement/Sample** | Required Statistical Power N | Integer Count (N) | High power $N \\approx 36$ (for paired t-test, d=0.5, 80% power) 24 | +| **Measurement/Sample** | Physiological Sampling Rate | Hertz (Hz) | EEG: 512 Hz 20 | + +## **VII. Strategic Parameter Selection** + +The power of the parameterized approach lies in the strategic combination of settings to isolate specific cognitive mechanisms. + +To isolate *pure response competition*, the experimental design should minimize early perceptual filtering difficulties by utilizing easily discriminable stimuli and large visual sizes, while maximizing the simultaneous presentation of conflicting information (SOA \= 0 ms). This configuration promotes the immediate activation of competing response codes, focusing the measured interference effect on the selection stage. + +Conversely, studies aiming to target *perceptual filtering* limitations should utilize parameter combinations that stress the visual system's capacity for rapid selective processing. This involves employing a **Small Target-Flanker Separation** (P.1) combined with a **Very Brief Stimulus Duration** (P.2, e.g., $50$ ms fixed exposure). By limiting the viewing time and forcing perceptual overlap, researchers can measure interference before downstream response planning fully engages. + +For understanding the adaptive nature of cognitive control, researchers can compare performance between a high-conflict expectation context (e.g., a **50:50 Mixed Design**) and a low-conflict expectation context (e.g., a **75:25 Blocked Design**). A larger Congruency Sequence Effect (CSE) observed specifically within the low-conflict blocks indicates a greater reliance on reactive, trial-to-trial adjustment when the default setting for global proactive control is low.15 Furthermore, utilizing the **Mouse Tracking** response modality (M.2) in highly incongruent conditions allows for the quantification of conflict during the motor planning phase, as reflected by the spatial deviation of the cursor trajectory prior to committing to the final response.4 + +## **VIII. Conclusion** + +The Eriksen Flanker Task is a highly adaptable methodological tool, its utility fundamentally defined by a comprehensive parameter space encompassing stimulus features, psychophysics, temporal dynamics, and experimental context. By detailing the ranges and values of these parameters—from the specific SOA timings (e.g., $\\pm 200$ ms) to the use of biased congruency ratios (e.g., 75:25)—this report establishes a functional taxonomy for defining any possible variation of the EFT. + +The most critical analytical utility of this parameterized space lies in its ability to isolate cognitive mechanisms. By coupling manipulations in the input space (e.g., Flanker Modality, SOA) with advanced physiological output measures (e.g., ERN, fMRI ROI activity), researchers can dissect the temporal and functional locus of conflict resolution. The amplitude of single-trial neurophysiological responses, such as the ERN, serves not just as a measured outcome, but as an internal, functional parameter of the control system, directly modulating subsequent behavior.22 + +This synthesis provides the necessary methodological blueprint for computational modeling efforts that seek to simulate human executive function. Future research should leverage these strategic parameter settings, particularly the integration of continuous response measures and simultaneous neurophysiological recordings, to dynamically map the intricate interplay between proactive and reactive cognitive control across diverse environmental constraints. + diff --git a/packages/arrow-flanker/src/constants.ts b/packages/arrow-flanker/src/constants.ts new file mode 100644 index 00000000..2d366539 --- /dev/null +++ b/packages/arrow-flanker/src/constants.ts @@ -0,0 +1,66 @@ +/** + * Arrow Flanker Task - Constants + * + * SVG definitions and default parameter values + */ + +import { ArrowFlankerConfig, ResponseKeys } from './types'; + +/** + * Left-pointing arrow SVG (48x48) + */ +export const LEFT_ARROW = ``; + +/** + * Right-pointing arrow SVG (48x48) + */ +export const RIGHT_ARROW = ``; + +/** + * Neutral stimulus (horizontal line/dash) + */ +export const NEUTRAL_STIMULUS = ``; + +/** + * Default response keys + */ +export const DEFAULT_RESPONSE_KEYS: ResponseKeys = { + left: ['ArrowLeft'], + right: ['ArrowRight'] +}; + +/** + * Default configuration values + */ +export const DEFAULT_CONFIG: Required> = { + // Temporal parameters + soa: 0, // Simultaneous presentation (standard) + stimulus_duration: null, // Response-terminated + fixation_duration: 500, + iti_duration: 0, + response_timeout: 1500, + + // Spatial parameters + stimulus_size: '48px', + target_flanker_separation: '10px', + fixation_size: '24px', + stimulus_container_height: '100px', + flanker_arrangement: 'horizontal', + num_flankers: 4, + + // Design parameters + include_neutral: false, + block_design: 'mixed', + congruency_ratio: { + congruent: 1, + incongruent: 1, + neutral: 0 + }, + track_sequence_effects: false, + num_blocks: 1, + num_trials: 12, + block_break_duration: null, + + // Response parameters + response_mode: 'keyboard' +}; diff --git a/packages/arrow-flanker/src/index.spec.ts b/packages/arrow-flanker/src/index.spec.ts index 29debd29..a01a6a57 100644 --- a/packages/arrow-flanker/src/index.spec.ts +++ b/packages/arrow-flanker/src/index.spec.ts @@ -1,11 +1,160 @@ import { JsPsych, initJsPsych } from "jspsych"; import { createTimeline } from "."; -describe("createTimeline", () => { +describe("Arrow Flanker Task", () => { + let jsPsych: JsPsych; + + beforeEach(() => { + jsPsych = initJsPsych(); + }); + + describe("createTimeline", () => { it("should return a timeline", () => { - const jsPsych = initJsPsych(); + const timeline = createTimeline(jsPsych); + expect(timeline).toBeDefined(); + expect(timeline.timeline).toBeDefined(); + expect(Array.isArray(timeline.timeline)).toBe(true); + }); + + it("should handle backward compatibility with 'n' parameter", () => { + const timeline = createTimeline(jsPsych, { n: 8 }); + expect(timeline).toBeDefined(); + // Timeline should have trials + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should create timeline with default parameters", () => { + const timeline = createTimeline(jsPsych, { + fixation_duration: 500, + num_trials: 12 + }); + expect(timeline).toBeDefined(); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should support neutral trials", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + include_neutral: true, + congruency_ratio: { + congruent: 1, + incongruent: 1, + neutral: 1 + } + }); + expect(timeline).toBeDefined(); + }); + + it("should support custom congruency ratios", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + congruency_ratio: { + congruent: 25, + incongruent: 75 + } + }); + expect(timeline).toBeDefined(); + }); + + it("should support SOA configuration with array", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + soa: [-200, 0, 200] + }); + expect(timeline).toBeDefined(); + }); + + it("should support SOA configuration with range", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + soa: { min: -200, max: 200 } + }); + expect(timeline).toBeDefined(); + }); + + it("should support multiple blocks", () => { + const timeline = createTimeline(jsPsych, { + num_blocks: 3, + num_trials: 12 + }); + expect(timeline).toBeDefined(); + // Should have 3 block procedures + 2 block breaks + expect(timeline.timeline.length).toBe(5); + }); + + it("should support sequential effects tracking", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 24, + track_sequence_effects: true + }); + expect(timeline).toBeDefined(); + }); + + it("should support vertical arrangement", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + flanker_arrangement: 'vertical' + }); + expect(timeline).toBeDefined(); + }); + + it("should support 7-item arrays", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + num_flankers: 6 + }); + expect(timeline).toBeDefined(); + }); + + it("should support custom spatial parameters", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + stimulus_size: '64px', + target_flanker_separation: '20px', + fixation_size: '32px' + }); + expect(timeline).toBeDefined(); + }); + + it("should support custom temporal parameters", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + fixation_duration: 1000, + stimulus_duration: 200, + iti_duration: 500, + response_timeout: 2000 + }); + expect(timeline).toBeDefined(); + }); + + it("should support blocked design", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + block_design: 'blocked' + }); + expect(timeline).toBeDefined(); + }); + + it("should support custom response keys", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + response_keys: { + left: ['f'], + right: ['j'] + } + }); + expect(timeline).toBeDefined(); + }); - const timeline = createTimeline(jsPsych); - expect(timeline).toBeDefined(); + it("should support custom data labels", () => { + const timeline = createTimeline(jsPsych, { + num_trials: 12, + data_labels: { + task: 'arrow-flanker', + condition: 'experiment-1' + } + }); + expect(timeline).toBeDefined(); }); -}); \ No newline at end of file + }); +}); diff --git a/packages/arrow-flanker/src/index.ts b/packages/arrow-flanker/src/index.ts index 120bc103..2da89cc5 100644 --- a/packages/arrow-flanker/src/index.ts +++ b/packages/arrow-flanker/src/index.ts @@ -1,65 +1,165 @@ -import { JsPsych } from "jspsych"; -import jsPsychHtmlKeyboardResponse from '@jspsych/plugin-html-keyboard-response' - -const left_arrow = ``; -const right_arrow = ``; - -function createFlankerStim(direction, congruent) { - let html = `
`; - if (congruent) { - if (direction === "left") { - html += ` ${left_arrow} ${left_arrow} ${left_arrow} ${left_arrow} ${left_arrow} `; - } else { - html += ` ${right_arrow} ${right_arrow} ${right_arrow} ${right_arrow} ${right_arrow} `; - } - } else { - if (direction === "left") { - html += ` ${right_arrow} ${right_arrow} ${left_arrow} ${right_arrow} ${right_arrow} `; - } else { - html += ` ${left_arrow} ${left_arrow} ${right_arrow} ${left_arrow} ${left_arrow} `; - } - } - html += `
`; - return html; -} +/** + * Arrow Flanker Task for jsPsych + * + * A comprehensive implementation of the Eriksen Flanker Task using arrow stimuli. + * Supports extensive parameterization for research applications including: + * - Temporal manipulation (SOA) + * - Spatial configuration + * - Congruency ratio control + * - Sequential effects tracking + * - Multiple block designs + * + * @module @jspsych-timelines/arrow-flanker + */ -export function createTimeline(jsPsych: JsPsych, { - fixation_duration = 500, - n = 12, -} : { - fixation_duration?: number, - n?: number, -} = {}){ - - const timeline_variables = [ - {direction: 'left', congruent: true}, - {direction: 'left', congruent: false}, - {direction: 'right', congruent: true}, - {direction: 'right', congruent: false}, - ] - - const trials = jsPsych.randomization.repeat(timeline_variables, Math.floor(n/4)).concat(jsPsych.randomization.sampleWithoutReplacement(timeline_variables, n%4)); - - const fixation = { - type: jsPsychHtmlKeyboardResponse, - stimulus: '+', - choices: "NONE", - trial_duration: fixation_duration, - } +import { JsPsych } from 'jspsych'; +import { ArrowFlankerConfig } from './types'; +import { DEFAULT_CONFIG, DEFAULT_RESPONSE_KEYS } from './constants'; +import { generateTrialVariables, mergeConfig } from './utils'; +import { + createFixationTrial, + createFlankerTrial, + createITITrial, + createBlockBreak +} from './trials'; +import { trial_text } from './text'; - const flanker = { - type: jsPsychHtmlKeyboardResponse, - stimulus: ()=>{ - return createFlankerStim(jsPsych.timelineVariable('direction'), jsPsych.timelineVariable('congruent')) - }, - choices: ['ArrowLeft', 'ArrowRight'], +/** + * Create a complete Arrow Flanker Task timeline + * + * @param jsPsych - JsPsych instance + * @param config - Configuration options (optional) + * @returns Timeline object ready for jsPsych.run() + * + * @example + * // Basic usage (backward compatible) + * const timeline = createTimeline(jsPsych, { + * fixation_duration: 500, + * num_trials: 12 + * }); + * + * @example + * // Advanced usage with SOA and congruency manipulation + * const timeline = createTimeline(jsPsych, { + * soa: [-200, 0, 200], + * stimulus_duration: 100, + * congruency_ratio: { congruent: 25, incongruent: 75 }, + * track_sequence_effects: true, + * num_blocks: 4, + * num_trials: 48 + * }); + */ +export function createTimeline( + jsPsych: JsPsych, + config: ArrowFlankerConfig = {} +): any { + // Handle backward compatibility with 'n' parameter + if (config.n !== undefined && config.num_trials === undefined) { + config.num_trials = config.n; } - const flanker_task = { - timeline: [fixation, flanker], - timeline_variables: trials, - randomize_order: true + // Merge with defaults + const fullConfig = mergeConfig(config, DEFAULT_CONFIG); + + // Setup response keys + const response_keys = config.response_keys || DEFAULT_RESPONSE_KEYS; + + // Merge text configuration + const text = { ...trial_text, ...config.text_object }; + + // Create main timeline + const timeline: any[] = []; + + // Generate blocks + for (let block = 1; block <= fullConfig.num_blocks; block++) { + // Generate trial variables for this block + const trial_variables = generateTrialVariables(jsPsych, { + num_trials: fullConfig.num_trials, + congruency_ratio: fullConfig.congruency_ratio, + include_neutral: fullConfig.include_neutral, + soa_config: config.soa, + track_sequence_effects: fullConfig.track_sequence_effects, + control_repetitions: config.control_repetitions, + block_number: block + }); + + // Create trial sequence: fixation -> stimulus -> ITI + const trial_sequence: any[] = []; + + // Fixation + trial_sequence.push( + createFixationTrial({ + duration: fullConfig.fixation_duration, + fixation_size: fullConfig.fixation_size, + container_height: fullConfig.stimulus_container_height + }) + ); + + // Flanker stimulus + trial_sequence.push( + createFlankerTrial(jsPsych, { + response_keys, + response_timeout: fullConfig.response_timeout, + stimulus_duration: fullConfig.stimulus_duration, + response_mode: fullConfig.response_mode === 'keyboard' ? 'keyboard' : 'buttons', + has_soa: config.soa !== undefined, + data_labels: config.data_labels, + num_flankers: fullConfig.num_flankers, + flanker_arrangement: fullConfig.flanker_arrangement, + stimulus_size: fullConfig.stimulus_size, + target_flanker_separation: fullConfig.target_flanker_separation, + container_height: fullConfig.stimulus_container_height + }) + ); + + // ITI + const iti_trial = createITITrial({ + duration: fullConfig.iti_duration, + container_height: fullConfig.stimulus_container_height + }); + if (iti_trial !== null) { + trial_sequence.push(iti_trial); + } + + // Create block procedure + const block_procedure = { + timeline: trial_sequence, + timeline_variables: trial_variables, + randomize_order: fullConfig.block_design === 'mixed' + }; + + timeline.push(block_procedure); + + // Add block break between blocks (except after last block) + if (block < fullConfig.num_blocks) { + timeline.push( + createBlockBreak({ + block_number: block, + total_blocks: fullConfig.num_blocks, + duration: fullConfig.block_break_duration, + text + }) + ); + } } - return flanker_task; -} \ No newline at end of file + return { + timeline + }; +} + +/** + * Exported utilities for advanced customization + * + * Most users won't need these - they're for building custom timelines + * using the individual trial components. + */ +export const utils = { + createFixationTrial, + createFlankerTrial, + createITITrial, + createBlockBreak +}; + +// Re-export types for TypeScript users +export type { ArrowFlankerConfig } from './types'; diff --git a/packages/arrow-flanker/src/stimuli.ts b/packages/arrow-flanker/src/stimuli.ts new file mode 100644 index 00000000..d6b60151 --- /dev/null +++ b/packages/arrow-flanker/src/stimuli.ts @@ -0,0 +1,126 @@ +/** + * Arrow Flanker Task - Stimulus Generation + * + * Functions for creating flanker stimulus arrays + */ + +import { CongruencyType, Direction, FlankerArrangement } from './types'; +import { LEFT_ARROW, RIGHT_ARROW, NEUTRAL_STIMULUS } from './constants'; + +/** + * Create a flanker stimulus array + * + * @param direction - Target arrow direction + * @param congruency - Trial congruency type + * @param options - Configuration options + * @returns HTML string for the flanker stimulus + */ +export function createFlankerStimulus( + direction: Direction, + congruency: CongruencyType, + options: { + num_flankers?: 4 | 6; + arrangement?: FlankerArrangement; + stimulus_size?: string; + target_flanker_separation?: string; + neutral_stimulus?: string; + container_height?: string; + } = {} +): string { + const { + num_flankers = 4, + arrangement = 'horizontal', + stimulus_size = '48px', + target_flanker_separation = '10px', + neutral_stimulus = NEUTRAL_STIMULUS, + container_height = '100px' + } = options; + + // Select target arrow + const target = direction === 'left' ? LEFT_ARROW : RIGHT_ARROW; + + // Select flanker arrows based on congruency + let flanker: string; + if (congruency === 'congruent') { + flanker = target; + } else if (congruency === 'incongruent') { + flanker = direction === 'left' ? RIGHT_ARROW : LEFT_ARROW; + } else { + // neutral + flanker = neutral_stimulus; + } + + // Build array + const flankers_per_side = num_flankers / 2; + const items: string[] = []; + + for (let i = 0; i < flankers_per_side; i++) { + items.push(flanker); + } + items.push(target); + for (let i = 0; i < flankers_per_side; i++) { + items.push(flanker); + } + + // Apply spacing and arrangement + const spacing = target_flanker_separation; + const flexDirection = arrangement === 'horizontal' ? 'row' : 'column'; + + const itemsHtml = items + .map((item, index) => { + const isTarget = index === flankers_per_side; + const marginStyle = getMarginStyle(index, items.length, spacing, arrangement); + return `
${item}
`; + }) + .join(''); + + return `
${itemsHtml}
`; +} + +/** + * Calculate margin style for spacing between items + */ +function getMarginStyle( + index: number, + total: number, + spacing: string, + arrangement: FlankerArrangement +): string { + const isLast = index === total - 1; + if (isLast) return ''; + + if (arrangement === 'horizontal') { + return `margin-right: ${spacing};`; + } else { + return `margin-bottom: ${spacing};`; + } +} + +/** + * Create a fixation cross + * + * @param options - Configuration options + * @returns HTML string for fixation cross + */ +export function createFixation(options: { + size?: string; + container_height?: string; +} = {}): string { + const { size = '24px', container_height = '100px' } = options; + + return `
+
`; +} + +/** + * Create a blank screen (for ITI or ISI) + * + * @param options - Configuration options + * @returns HTML string for blank screen + */ +export function createBlank(options: { + container_height?: string; +} = {}): string { + const { container_height = '100px' } = options; + + return `
`; +} diff --git a/packages/arrow-flanker/src/text.ts b/packages/arrow-flanker/src/text.ts new file mode 100644 index 00000000..9e093a02 --- /dev/null +++ b/packages/arrow-flanker/src/text.ts @@ -0,0 +1,21 @@ +/** + * Arrow Flanker Task - Text Configuration + * + * All user-facing text strings for internationalization and customization + */ + +export const trial_text = { + /** + * Block break message + * @param block_number - Current block number + * @param total_blocks - Total number of blocks + * @param duration - Duration of break (null for key press to continue) + */ + block_break: (block_number: number, total_blocks: number, duration: number | null) => { + return `Block ${block_number} of ${total_blocks} complete. ${ + duration === null ? 'Press any key to continue.' : 'Take a short break...' + }`; + } +}; + +export type TrialText = typeof trial_text; diff --git a/packages/arrow-flanker/src/trials.ts b/packages/arrow-flanker/src/trials.ts new file mode 100644 index 00000000..c10bcb3a --- /dev/null +++ b/packages/arrow-flanker/src/trials.ts @@ -0,0 +1,173 @@ +/** + * Arrow Flanker Task - Trial Components + * + * Functions for creating individual trial components + */ + +import { JsPsych } from 'jspsych'; +import jsPsychFlanker from '@jspsych-contrib/plugin-flanker'; +import jsPsychHtmlKeyboardResponse from '@jspsych/plugin-html-keyboard-response'; +import { createFixation as createFixationStimulus, createBlank } from './stimuli'; +import { ResponseKeys } from './types'; +import type { TrialText } from './text'; + +/** + * Create a fixation trial + * + * @param options - Configuration options + * @returns jsPsych trial object + */ +export function createFixationTrial(options: { + duration: number; + fixation_size?: string; + container_height?: string; +}) { + const { duration, fixation_size, container_height } = options; + + return { + type: jsPsychHtmlKeyboardResponse, + stimulus: createFixationStimulus({ size: fixation_size, container_height }), + choices: 'NO_KEYS', + trial_duration: duration, + data: { + task: 'flanker', + phase: 'fixation' + } + }; +} + +/** + * Create a flanker trial using the plugin-flanker + * + * @param jsPsych - JsPsych instance + * @param options - Configuration options + * @returns jsPsych trial object + */ +export function createFlankerTrial( + jsPsych: JsPsych, + options: { + response_keys: ResponseKeys; + response_timeout: number; + stimulus_duration?: number | null; + response_mode?: 'keyboard' | 'buttons'; + button_label_left?: string; + button_label_right?: string; + has_soa?: boolean; // Whether timeline variables include SOA + data_labels?: any; + num_flankers?: 4 | 6; + flanker_arrangement?: 'horizontal' | 'vertical'; + stimulus_size?: string; + target_flanker_separation?: string; + container_height?: string; + } +) { + const { + response_keys, + response_timeout, + stimulus_duration, + response_mode = 'keyboard', + button_label_left, + button_label_right, + has_soa = false, + data_labels = {}, + num_flankers, + flanker_arrangement, + stimulus_size, + target_flanker_separation, + container_height + } = options; + + return { + type: jsPsychFlanker, + target_direction: () => jsPsych.timelineVariable('direction'), + congruency: () => jsPsych.timelineVariable('congruency'), + soa: has_soa ? () => jsPsych.timelineVariable('soa') : 0, + response_timeout, + stimulus_duration: stimulus_duration !== undefined ? stimulus_duration : null, + response_mode, + response_keys_left: response_keys.left, + response_keys_right: response_keys.right, + button_label_left, + button_label_right, + num_flankers, + flanker_arrangement, + stimulus_size, + target_flanker_separation, + container_height, + data: { + task: 'flanker', + phase: 'response', + block_number: () => jsPsych.timelineVariable('block_number'), + trial_number: () => jsPsych.timelineVariable('trial_number'), + ...(has_soa ? { soa: () => jsPsych.timelineVariable('soa') } : {}), + ...data_labels + }, + on_finish: (data: any) => { + // Record sequential effects by looking at previous trial data + const previousTrials = jsPsych.data.get().filter({ task: 'flanker', phase: 'response' }); + if (previousTrials.count() > 0) { + const lastTrial = previousTrials.last(1).values()[0]; + data.previous_congruency = lastTrial.congruency; + data.previous_correct = lastTrial.correct; + } + } + }; +} + +/** + * Create an ITI (inter-trial interval) blank screen + * + * @param options - Configuration options + * @returns jsPsych trial object + */ +export function createITITrial(options: { + duration: number; + container_height?: string; +}) { + const { duration, container_height } = options; + + if (duration === 0) { + return null; // Skip ITI if duration is 0 + } + + return { + type: jsPsychHtmlKeyboardResponse, + stimulus: createBlank({ container_height }), + choices: 'NO_KEYS', + trial_duration: duration, + data: { + task: 'flanker', + phase: 'iti' + } + }; +} + +/** + * Create a block break screen + * + * @param options - Configuration options + * @returns jsPsych trial object + */ +export function createBlockBreak(options: { + block_number: number; + total_blocks: number; + duration?: number | null; + text: TrialText; +}) { + const { block_number, total_blocks, duration = null, text } = options; + + const message = text.block_break(block_number, total_blocks, duration); + + return { + type: jsPsychHtmlKeyboardResponse, + stimulus: `

${message}

`, + choices: duration === null ? 'ALL_KEYS' : 'NO_KEYS', + trial_duration: duration, + data: { + task: 'flanker', + phase: 'block_break', + block_number + } + }; +} + diff --git a/packages/arrow-flanker/src/types.ts b/packages/arrow-flanker/src/types.ts new file mode 100644 index 00000000..d0a49b0f --- /dev/null +++ b/packages/arrow-flanker/src/types.ts @@ -0,0 +1,174 @@ +/** + * Arrow Flanker Task - Type Definitions + * + * Comprehensive parameter interface based on Eriksen Flanker Task methodology + */ + +import type { TrialText } from './text'; + +/** + * Trial congruency status + */ +export type CongruencyType = 'congruent' | 'incongruent' | 'neutral'; + +/** + * Direction of target arrow + */ +export type Direction = 'left' | 'right'; + +/** + * Flanker arrangement configuration + */ +export type FlankerArrangement = 'horizontal' | 'vertical'; + +/** + * Block design structure + */ +export type BlockDesign = 'mixed' | 'blocked'; + +/** + * Response mode + */ +export type ResponseMode = 'keyboard' | 'mouse-tracking'; + +/** + * Stimulus Onset Asynchrony configuration + * - number: fixed SOA in milliseconds + * - number[]: randomly sample from these SOA values + * - { min, max }: continuous random sampling range + */ +export type SOAConfig = number | number[] | { min: number; max: number }; + +/** + * Congruency ratio configuration for global control manipulation + */ +export interface CongruencyRatio { + congruent: number; + incongruent: number; + neutral?: number; +} + +/** + * Repetition control for sequential effects + */ +export interface RepetitionControl { + flanker?: boolean; // Control flanker identity repetition + target?: boolean; // Control target direction repetition + response?: boolean; // Control response repetition +} + +/** + * Response key configuration + */ +export interface ResponseKeys { + left: string[]; + right: string[]; +} + +/** + * Comprehensive configuration for Arrow Flanker Task + */ +export interface ArrowFlankerConfig { + // === Temporal Parameters (P.2) === + /** Stimulus Onset Asynchrony: time delay between flanker onset and target onset (ms) */ + soa?: SOAConfig; + + /** Duration to display the complete stimulus array (ms). null = response-terminated */ + stimulus_duration?: number | null; + + /** Duration to display fixation cross (ms) */ + fixation_duration?: number; + + /** Inter-trial interval: time between trials (ms) */ + iti_duration?: number; + + /** Maximum time allowed for response (ms) */ + response_timeout?: number; + + // === Spatial Parameters (P.1) === + /** Size of individual stimulus elements (e.g., "48px", "1.5dva") */ + stimulus_size?: string; + + /** Distance between target and nearest flanker (e.g., "10px", "1.0dva") */ + target_flanker_separation?: string; + + /** Size of fixation cross (e.g., "24px", "0.45dva") */ + fixation_size?: string; + + /** Height of stimulus container to prevent layout shifts */ + stimulus_container_height?: string; + + /** Arrangement of flankers relative to target */ + flanker_arrangement?: FlankerArrangement; + + /** Number of flanker items (4 creates 5-item array, 6 creates 7-item array) */ + num_flankers?: 4 | 6; + + // === Design Parameters (D.1, D.2, D.3) === + /** Include neutral trials (flankers are non-directional) */ + include_neutral?: boolean; + + /** Custom SVG for neutral flanker stimulus */ + neutral_stimulus?: string; + + /** Block structure: mixed (randomized) or blocked (grouped by condition) */ + block_design?: BlockDesign; + + /** Congruency proportion for global control manipulation (values are relative weights) */ + congruency_ratio?: CongruencyRatio; + + /** Enable tracking of sequential effects (Congruency Sequence Effect / Gratton effect) */ + track_sequence_effects?: boolean; + + /** Control stimulus repetitions for sequential effects analysis */ + control_repetitions?: RepetitionControl; + + /** Number of experimental blocks */ + num_blocks?: number; + + /** Number of trials per block */ + num_trials?: number; + + /** Duration of block breaks (ms). null = button to continue */ + block_break_duration?: number | null; + + // === Response Parameters (M.2) === + /** Response mode */ + response_mode?: ResponseMode; + + /** Custom response key mapping */ + response_keys?: ResponseKeys; + + /** Collect mouse trajectory data (requires response_mode: 'mouse-tracking') */ + collect_trajectory?: boolean; + + // === Data Parameters === + /** Custom data labels added to all trials */ + data_labels?: { + task?: string; + condition?: string; + [key: string]: any; + }; + + // === Text Configuration === + /** Custom text object for internationalization and customization */ + text_object?: Partial; + + // === Backward Compatibility === + /** Legacy parameter: total number of trials (use num_trials instead) */ + n?: number; +} + +/** + * Timeline variable for a single trial + */ +export interface FlankerTrialVariable { + direction: Direction; + congruency: CongruencyType; + soa?: number; + block_number?: number; + trial_number?: number; + previous_congruency?: CongruencyType; + previous_direction?: Direction; + previous_response?: string; +} diff --git a/packages/arrow-flanker/src/utils.ts b/packages/arrow-flanker/src/utils.ts new file mode 100644 index 00000000..5904a10b --- /dev/null +++ b/packages/arrow-flanker/src/utils.ts @@ -0,0 +1,196 @@ +/** + * Arrow Flanker Task - Utility Functions + * + * Helper functions for timeline generation and configuration + */ + +import { JsPsych } from 'jspsych'; +import { + CongruencyType, + Direction, + FlankerTrialVariable, + CongruencyRatio, + SOAConfig, + RepetitionControl +} from './types'; + +/** + * Generate timeline variables for a block of trials + * + * @param jsPsych - JsPsych instance for randomization + * @param options - Configuration options + * @returns Array of timeline variables + */ +export function generateTrialVariables( + jsPsych: JsPsych, + options: { + num_trials: number; + congruency_ratio: CongruencyRatio; + include_neutral: boolean; + soa_config?: SOAConfig; + track_sequence_effects?: boolean; + control_repetitions?: RepetitionControl; + block_number?: number; + } +): FlankerTrialVariable[] { + const { + num_trials, + congruency_ratio, + include_neutral, + soa_config, + track_sequence_effects = false, + control_repetitions, + block_number = 1 + } = options; + + // Calculate trial counts based on ratios + const trialCounts = calculateTrialCounts(num_trials, congruency_ratio, include_neutral); + + // Generate base trial pool + const trials: FlankerTrialVariable[] = []; + + // Congruent trials + for (let i = 0; i < trialCounts.congruent; i++) { + const direction: Direction = i % 2 === 0 ? 'left' : 'right'; + trials.push({ + direction, + congruency: 'congruent', + block_number + }); + } + + // Incongruent trials + for (let i = 0; i < trialCounts.incongruent; i++) { + const direction: Direction = i % 2 === 0 ? 'left' : 'right'; + trials.push({ + direction, + congruency: 'incongruent', + block_number + }); + } + + // Neutral trials + if (include_neutral && trialCounts.neutral > 0) { + for (let i = 0; i < trialCounts.neutral; i++) { + const direction: Direction = i % 2 === 0 ? 'left' : 'right'; + trials.push({ + direction, + congruency: 'neutral', + block_number + }); + } + } + + // Shuffle trials + let shuffledTrials = jsPsych.randomization.shuffle(trials); + + // Apply repetition controls if specified + if (control_repetitions) { + shuffledTrials = applyRepetitionControl(jsPsych, shuffledTrials, control_repetitions); + } + + // Assign SOA values + if (soa_config !== undefined) { + shuffledTrials = assignSOAValues(jsPsych, shuffledTrials, soa_config); + } + + // Add trial numbers + shuffledTrials.forEach((trial, index) => { + trial.trial_number = index + 1; + }); + + return shuffledTrials; +} + +/** + * Calculate trial counts based on congruency ratios + */ +function calculateTrialCounts( + num_trials: number, + ratio: CongruencyRatio, + include_neutral: boolean +): { congruent: number; incongruent: number; neutral: number } { + const total_weight = + ratio.congruent + ratio.incongruent + (include_neutral ? ratio.neutral || 0 : 0); + + const congruent = Math.round((ratio.congruent / total_weight) * num_trials); + const incongruent = Math.round((ratio.incongruent / total_weight) * num_trials); + const neutral = include_neutral && ratio.neutral + ? num_trials - congruent - incongruent + : 0; + + return { congruent, incongruent, neutral }; +} + +/** + * Assign SOA values to trials + */ +function assignSOAValues( + jsPsych: JsPsych, + trials: FlankerTrialVariable[], + soa_config: SOAConfig +): FlankerTrialVariable[] { + if (typeof soa_config === 'number') { + // Fixed SOA + return trials.map(trial => ({ ...trial, soa: soa_config })); + } else if (Array.isArray(soa_config)) { + // Sample from array + return trials.map(trial => ({ + ...trial, + soa: jsPsych.randomization.sampleWithReplacement(soa_config, 1)[0] + })); + } else { + // Random range + return trials.map(trial => ({ + ...trial, + soa: Math.floor(Math.random() * (soa_config.max - soa_config.min + 1)) + soa_config.min + })); + } +} + +/** + * Apply repetition control to trial sequence + * + * This is a simplified implementation - can be extended for more sophisticated control + */ +function applyRepetitionControl( + jsPsych: JsPsych, + trials: FlankerTrialVariable[], + control: RepetitionControl +): FlankerTrialVariable[] { + // For now, just shuffle - more sophisticated algorithms can be implemented + // to avoid/ensure specific repetition patterns + return jsPsych.randomization.shuffle(trials); +} + +/** + * Sample SOA value based on configuration + * + * @param jsPsych - JsPsych instance + * @param soa_config - SOA configuration + * @returns Sampled SOA value in milliseconds + */ +export function sampleSOA(jsPsych: JsPsych, soa_config: SOAConfig): number { + if (typeof soa_config === 'number') { + return soa_config; + } else if (Array.isArray(soa_config)) { + return jsPsych.randomization.sampleWithReplacement(soa_config, 1)[0]; + } else { + return Math.floor(Math.random() * (soa_config.max - soa_config.min + 1)) + soa_config.min; + } +} + +/** + * Merge user configuration with defaults + * + * @param userConfig - User-provided configuration + * @param defaults - Default configuration + * @returns Merged configuration + */ +export function mergeConfig>( + userConfig: Partial, + defaults: T +): T { + return { ...defaults, ...userConfig }; +} +