Skip to content
EcoSimLab
DeutschEnglish
Docs navigation
Tool docs

SAGAT-Freeze

How to install the SAGAT plugin for jsPsych, configure a freeze with items, and read the response data.

Part of: SAGAT-freeze

This page is the guide to the plugin package @ecosimlab/jspsych-plugin-videosimlab-sagat: installation, trial configuration, item formats, data output. One trial of this type is one SAGAT freeze. The plugin asks the items one after the other, then the trial ends and the timeline goes on.

Every statement here comes from the repository videosimlab-sagat, tag 1.0.1 (commit e64c9a1, which is also main), package version 1.0.1. The repository documentation (docs/plugin-videosimlab-sagat.md) is in part still the empty jsPsych template, and it disagrees with the code in several places. Where it does, this page gives the state of the code and names the difference.

Prerequisites

  • jsPsych 8.0.0 or later. The repository declares jspsych as a normal dependency, not as a peer dependency. Take care that a study does not bundle two jsPsych instances.
  • Node and npm for the installation and the build. The sub-build src/sagat-items is a Vite and Solid project. Its README names pnpm, but the build script calls npm install.
  • A browser. The plugin draws into the display_element of jsPsych and injects its own CSS. It needs no video player and no data source. The timeline around it hides the video and the display.

Installation

The package is named @ecosimlab/jspsych-plugin-videosimlab-sagat. It is not in the npm registry. The comment in examples/index.html (“Once this plugin package is published, you can load it from a CDN instead”) confirms that. Install it from the public GitLab repository, pinned to a tag:

npm install https://gitlab.com/ecosimlab/videosimlab-sagat#1.0.1

The README still names #1.0.0 in this place. The newest tag is 1.0.1, and the package.json is on 1.0.1 as well. The tag 1.0.1 counts.

The package ships its built dist/ folder, so the installation runs no build.

Import. README and package.json name the same package:

import jsPsychVideosimlabSagat from "@ecosimlab/jspsych-plugin-videosimlab-sagat";

The README writes the variable as jsPsychVideoSimLabSagat. The name of the variable is free. For the browser global, jsPsychVideosimlabSagat from rollup.config.mjs counts.

In a browser without a bundler. The README leaves this section empty, but the build delivers the file: dist/index.browser.min.js (also the unpkg field) sets the global jsPsychVideosimlabSagat. examples/index.html loads it that way.

Quickstart

examples/index.html loads jsPsych and the built browser bundle directly:

<script src="https://unpkg.com/jspsych"></script>
<script src="../dist/index.browser.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/jspsych/css/jspsych.css" />

The example trial in the repository does not run. It declares only type: jsPsychVideosimlabSagat without items, and the item component reads items[0] as soon as it draws. A trial runs from one item on:

const jsPsych = initJsPsych();

const trial = {
  type: jsPsychVideosimlabSagat,
  sectorId: "sector-01",
  replayType: "efficient",
  items: [
    {
      id: "level1-1",
      itemType: "range",
      stimulus: "Wie schnell sind Sie gerade gefahren?",
      options: [
        { text: "Geschwindigkeit", start: 0, min: 0, max: 130, unit: "km/h" },
      ],
    },
  ],
};

jsPsych.run([trial]);

The sequence is fixed: the heading “Fragen zur Fahrt”, the progress n / total, one item per page, and the button “Weiter”. Without an input the page shows the message “Eingabe erforderlich” instead of the next item. After the last item the trial ends. The plugin draws these strings in German only.

Freezes and items

One freeze is one trial and one items array. Each item has four fields:

Field Type Meaning
id string The item. The comment in the code gives the convention level-number-type: level1, level2 or level3 for the SA level, a running number, and p for power or c for consumption.
itemType string The item template, see below.
stimulus string The question on the page.
options object[] The answer configuration. The fields depend on itemType.

Take care with the names of the item types. The plugin info, the README and the repository documentation name four types: radio, select, range and bar. The code reads six (src/utils.ts, src/sagat-items/src/App.tsx):

radio · selection · range · bar · pedal · dragdrop

select hits no branch of the code. Multiple choice is selection. pedal and dragdrop work, but no document names them.

radio and selection — a choice

Options: { text, suboptions }, where suboptions is an array of strings. text is the heading of the group. Each suboption becomes a line with a radio button (radio) or a check box (selection). The code holds two special cases:

  • If text is one of pickup/Pickup, pkw/Pkw/PKW or bus/Bus, the page shows a picture of the vehicle instead of the heading.
  • The suboptions N, D, R and P get a drive mode picture.

range — a horizontal slider

Options: { text, start, min, max, unit }. An item can hold more than one slider. The component adds a second display above the slider. It selects that display by a comparison of strings:

Condition Additional display
unit === "km/h" a speedometer
text holds "SOC" a battery symbol in percent
text holds "Reichweite" the range in km
text holds "Durchschnittlicher Verbrauch" the consumption in kWh/100km

Limit: the component reads min and max for all sliders of an item from options[0]. Different limits per slider have no effect.

bar — a vertical slider for power and regenerative braking

Options: { text, textTop, textBottom, start, unit }. textTop and textBottom label the two ends of the bar. text is a heading and appears only if the item holds more than one bar. The raw range of the slider is -149 to 300, where a negative value is regenerative braking. unit scales the value: with unit === "kW" to 39 (negative) and 92 (positive), otherwise to 1 and 2. For a consumption item the range is therefore −1 to 2.

pedal — the energy pedal and the brake pedal

Options: { text, start }. The component draws two vertical sliders (“Energiepedal”, “Bremspedal”). Each one is raw 0 to 300 and is shown and stored as a percentage from 0 to 100.

dragdrop — the relative position of a vehicle

Options: { text } per vehicle. The component draws a lane with four drop fields, pos1 to pos4, from the bottom up, in front of the ego vehicle. Below it stands a start row with the vehicles from options. The ego vehicle (zoe) is fixed and cannot be dragged. text also selects the picture of the vehicle (pickup, pkw, bus, else zoe).

Fields that are not declared: the options structure of the plugin info knows only text, textTop, textBottom and start. The components read suboptions, min, max and unit as well. Those four fields are missing from the generated parameter documentation, and jsPsych gives them no defaults.

API reference

Default export: the plugin class VideosimlabSagatPlugin (info.name: plugin-videosimlab-sagat). The browser global and the rollup name are jsPsychVideosimlabSagat.

Trial parameters

Parameter Type Default Description
sectorId STRING undefined The sector.
replayType STRING undefined The efficiency type of the replay. The comment in the code gives efficient and inefficient.
items COMPLEX undefined The items of this freeze, see above.

The parameters of all jsPsych plugins come on top of these.

TypeScript types (src/utils.ts)

Item, ItemType, Answer, DataItem and SagatItem, plus the types of the options: SelectionItemOptions, RangeItemOptions, BarItemOptions, PedalItemOptions and DragDropItemOptions. The built declarations are in dist/index.d.ts (the typings field).

Events and styles

The plugin has no events and no callbacks of its own. Its only exit is jsPsych.finishTrial() after the last item. Use on_finish of the trial to react to a freeze.

The plugin injects the compiled Tailwind styles once, at the first trial, as <style id="jspsych-videosimlab-sagat-styles">. Every utility carries the prefix tw:, and the Tailwind preflight is left out on purpose. The styles of the host page stay as they are.

Trial data

The trial writes an object with three fields into the data of jsPsych. The names in the code differ from the repository documentation. The code counts (src/index.ts):

Field in the code In the repository documentation Content
sectroId sectorId the parameter sectorId — the typing error is in the source code
replayType replayType the parameter replayType
answers sagat the array of the answered items

Each entry of answers is a DataItem. It repeats the configuration of the item and adds the answers:

{
  "id": "level1-1",
  "itemType": "range",
  "stimulus": "Wie schnell sind Sie gerade gefahren?",
  "options": [{ "text": "Geschwindigkeit", "start": 0, "min": 0, "max": 130, "unit": "km/h" }],
  "answers": [{ "text": "Geschwindigkeit", "answer": 62 }]
}

What answers[].text and answers[].answer hold depends on the item type:

Type text answer
radio / selection "<group>-<suboption>" "on" for a selected option, else "" — the data holds every suboption, not only the selected ones
range options[i].text the number of the slider
bar options[i].textTop the scaled number, see bar above
pedal options[i].text the string "energyPedal:<0–100>-brakePedal:<0–100>"
dragdrop options[i].text the drop field (pos1 to pos4), or null while the vehicle is not dropped

There are no time stamps per item. Only the trial times of jsPsych (rt, time_elapsed) are there. There is no ground truth in the plugin either: the comment in the code, “the correct one is marked”, does not hold, because nothing is marked as correct. The comparison with the driving data happens afterwards, in the analysis.

Development and tests

npm install
npm run build        # builds src/sagat-items first, then rollup --config
npm run build:watch
npm run tsc
npm test             # jest
npm run test:watch
  • The build has two steps. build:sagat-items goes into src/sagat-items, installs there, and builds the Solid and Vite sub-project to src/sagat-items/dist/sagat-items.js. Only then does rollup bundle the main package into dist/ (ESM, CJS, browser, minified). It uses makeRollupConfig("jsPsychVideosimlabSagat") of @jspsych/config.
  • The CSS travels in the bundle. The Vite plugin scripts/export-css.ts appends the compiled CSS to the entry as export const __css__ = "…" and deletes the separate .css file. The finished package therefore needs no extra stylesheet.
  • Item templates on their own. In src/sagat-items, npm run dev starts a Vite development server on port 3001 with the item components alone, without jsPsych.
  • Tests. jest.config.cjs uses @jspsych/config/jest. src/index.spec.ts is still the unchanged jsPsych template test: it hands over invented parameters (parameter_name, parameter_name2) and checks only that the trial ends. There is no test coverage of the plugin itself.

License and citation

MIT (LICENSE.txt, copyright 2026 Leonardt Wagner; the same in package.json).

CITATION.cff names Leonardt Wagner (ORCID 0009-0009-1759-9674), the title jsPsychVideosimlabSagat, version 1.0.0, the release date 2026-02-07, and no DOI. At build time @jspsych/config replaces the placeholder citations: '__CITATIONS__' in the plugin info with the data from that file. The citation function of jsPsych reads it.

Do not take two errors of the CITATION.cff over unchecked: url points to videosimlab-cockpit-replay instead of this repository, and version 1.0.0 disagrees with the package version 1.0.1.

Troubleshooting

  • Cannot find module "@jspsych-plugin-videosimlab-sagat" — the package carries the scope @ecosimlab/. Import from @ecosimlab/jspsych-plugin-videosimlab-sagat (see Installation).
  • The trial ends at once, or the page stays whiteitems is missing or empty. The plugin reads the first item without a check. A trial needs at least one item.
  • An item does not appear — check itemType. select is not a valid value; multiple choice is selection. With an unknown type the area stays empty and no message appears.
  • The analysis does not find sagat — the field is named answers in the code, and the sector is stored under sectroId.
  • The layout has no styles — the sub-build is missing. The main package imports src/sagat-items/dist/sagat-items.js, which holds the markup and __css__. Without npm run build:sagat-items the rollup build fails.
  • The pictures of the vehicles are missing (selection, dragdrop) — the components load the pictures over relative paths such as ./cars/zoe.png, and the bundle does not hold them. Serve the files of src/sagat-items/public/cars/ from the host page under that path.
  • A range slider ignores min and max — the component reads both for all sliders of an item from options[0]. Put sliders with different limits into items of their own.

Open questions

  • The version numbers in the repository. The README (#1.0.0) and CITATION.cff (1.0.0) are behind the tag and the package version (1.0.1). The url of CITATION.cff points to the wrong repository.
  • The names of the data fields. sectroId and answers (code) against sectorId and sagat (documentation). Whether the code or the documentation changes is an open decision. This page follows that decision afterwards.
  • select against selection — the same question for the name of the type.
  • Example configurations. The repository holds no complete item set of a real study. The examples here come from the components, not from a configuration in use.
  • Assets that nothing uses. src/sagat-items/public/road-signs/ holds 14 traffic signs that no component reads. They may belong to a planned level 1 item. That is open.