SAGAT-Freeze
Das jsPsych-Plugin für SAGAT-Freezes installieren, einen Freeze mit Items konfigurieren und die Antwortdaten auslesen.
Diese Seite ist die Anleitung zum Plugin-Paket
@ecosimlab/jspsych-plugin-videosimlab-sagat: Installation, Trial-Konfiguration,
Item-Formate, Datenausgabe. Ein Trial dieses Typs entspricht einem SAGAT-Freeze — die
Items werden nacheinander abgefragt, danach endet der Trial und die Timeline läuft weiter.
Alles hier ist aus dem Repo videosimlab-sagat belegt, Stand Tag 1.0.1 (Commit
e64c9a1, zugleich main), Paketversion 1.0.1. Die mitgelieferte Repo-Doku
(docs/plugin-videosimlab-sagat.md) ist stellenweise noch das unausgefüllte
jsPsych-Template und weicht an mehreren Stellen vom Code ab — wo das so ist, steht unten
der Code-Stand, und die Abweichung ist markiert.
Voraussetzungen
- jsPsych ≥ 8.0.0. Im Repo als reguläre
dependencydeklariert, nicht alspeerDependency— in einer bestehenden Studie also darauf achten, dass nicht zwei jsPsych-Instanzen im Bundle landen. - Node/npm für Installation und Build. Der Unter-Build
src/sagat-itemsist ein Vite-/Solid-Projekt; sein README nennt pnpm, der Build-Script ruftnpm installauf. - Browser-Kontext. Das Plugin rendert in das jsPsych-
display_elementund injiziert sein eigenes CSS. Kein Videoplayer und keine Datenquelle nötig — das Ausblenden von Video und Display ist Sache der umgebenden Timeline.
Installation
Das Paket heißt @ecosimlab/jspsych-plugin-videosimlab-sagat. Es ist nicht in der
npm-Registry veröffentlicht — der Kommentar in examples/index.html („Once this plugin
package is published, you can load it from a CDN instead“) bestätigt das. Die Installation
läuft über das öffentliche GitLab-Repo, festgelegt auf einen Tag:
npm install https://gitlab.com/ecosimlab/videosimlab-sagat#1.0.1
Der README nennt an dieser Stelle noch
#1.0.0. Der neueste Tag ist1.0.1, und diepackage.jsonsteht ebenfalls auf1.0.1. Es gilt der Tag1.0.1.
Das Paket liefert seinen gebauten dist/-Ordner mit. Die Installation baut also nichts
nach.
Import. README und package.json nennen denselben Paketnamen:
import jsPsychVideosimlabSagat from "@ecosimlab/jspsych-plugin-videosimlab-sagat";
Der README schreibt die Variable als jsPsychVideoSimLabSagat. Der Name der Variablen ist
frei wählbar; maßgeblich für das Browser-Global ist jsPsychVideosimlabSagat aus
rollup.config.mjs.
Im Browser ohne Bundler. Das README lässt diesen Abschnitt leer, der Build liefert die
Datei aber: dist/index.browser.min.js (auch als unpkg-Feld eingetragen) setzt das
globale Symbol jsPsychVideosimlabSagat — so macht es auch examples/index.html.
Einbindung / Quickstart
examples/index.html bindet jsPsych und das gebaute Browser-Bundle direkt ein:
<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" />
Das Beispiel-Trial im Repo ist allerdings nicht lauffähig — es definiert nur
type: jsPsychVideosimlabSagat ohne items, und die Item-Komponente greift beim Rendern
sofort auf items[0] zu. Minimal lauffähig ist erst ein Trial mit mindestens einem Item:
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]);
Der Ablauf ist fest verdrahtet: Überschrift „Fragen zur Fahrt“, Fortschrittsanzeige
n / gesamt, ein Item pro Seite, Button „Weiter“. Ohne Eingabe erscheint statt des
Weitersprungs der Hinweis „Eingabe erforderlich“; nach dem letzten Item endet der Trial.
Alle Beschriftungen des Plugins sind deutsch.
Freezes und Fragen konfigurieren
Ein Freeze = ein Trial = ein items-Array. Jedes Item hat vier Felder:
| Feld | Typ | Bedeutung |
|---|---|---|
id |
String | Item-Kennung, Konvention laut Code-Kommentar: level-number-type — level1/level2/level3 (SA-Ebene), fortlaufende Nummer, optional p für Power bzw. c für Consumption |
itemType |
String | Item-Template, siehe unten |
stimulus |
String | Die angezeigte Frage |
options |
Objekt[] | Antwortkonfiguration; die Felder hängen vom itemType ab |
Achtung itemType-Namen. Plugin-info, README und Repo-Doku nennen die vier Typen
radio, select, range, bar. Der tatsächlich ausgewertete Satz (utils.ts,
App.tsx) ist:
radio · selection · range · bar · pedal · dragdrop
select trifft im Code keinen Zweig — Mehrfachauswahl heißt selection. pedal und
dragdrop sind implementiert, aber nirgends dokumentiert.
radio und selection — Auswahl
Optionen: { text, suboptions }, suboptions ist ein String-Array. text ist die
Gruppenüberschrift, jede Suboption wird als Zeile mit Radio-Button (radio) bzw. Checkbox
(selection) gerendert. Zwei Sonderfälle sind im Code fest verdrahtet:
- Ist
texteiner vonpickup/Pickup,pkw/Pkw/PKW,bus/Bus, wird statt der Überschrift ein Fahrzeugbild gezeigt. - Suboptionen
N,D,R,Pbekommen eine Fahrmodus-Darstellung.
range — horizontaler Slider
Optionen: { text, start, min, max, unit }. Mehrere Slider pro Item sind möglich. Je nach
Inhalt wird zusätzlich eine Anzeige über dem Slider eingeblendet — die Auswahl erfolgt per
String-Vergleich:
| Bedingung | Zusatzanzeige |
|---|---|
unit === "km/h" |
Tacho |
text enthält "SOC" |
Batterie-Symbol in Prozent |
text enthält "Reichweite" |
Reichweite in km |
text enthält "Durchschnittlicher Verbrauch" |
Verbrauch in kWh/100km |
Einschränkung: min und max werden für alle Slider eines Items aus options[0]
gelesen — abweichende Grenzen pro Slider wirken nicht.
bar — vertikaler Slider (Power / Rekuperation)
Optionen: { text, textTop, textBottom, start, unit }. textTop/textBottom
beschriften die beiden Enden der Säule; text erscheint nur als Überschrift, wenn das Item
mehrere Bars enthält. Der rohe Sliderbereich ist fest -149 … 300 (negativ =
Rekuperation). Der daraus berechnete Wert wird über unit skaliert: bei unit === "kW"
auf 39 (negativ) bzw. 92 (positiv), sonst auf 1 bzw. 2 — für Verbrauchsangaben also
effektiv ein Bereich von −1 bis 2.
pedal — Energie- und Bremspedal
Optionen: { text, start }. Rendert zwei vertikale Slider („Energiepedal“,
„Bremspedal“), jeweils roh 0 … 300, angezeigt und gespeichert als Prozentwert 0–100.
dragdrop — Fahrzeug-Relativposition
Optionen: { text } je Fahrzeug. Gerendert wird eine Fahrspur mit vier Ablagefeldern
pos1–pos4 (von unten nach oben, vor dem Ego-Fahrzeug) und darunter eine Startreihe mit
den Fahrzeugen aus options. Das Ego-Fahrzeug (zoe) steht fest und ist nicht ziehbar.
text bestimmt zugleich das Fahrzeugbild (pickup, pkw, bus, sonst zoe).
Nicht deklarierte Felder: Die
options-Struktur in der Plugin-infokennt nurtext,textTop,textBottomundstart.suboptions,min,maxundunitwerden von den Komponenten gelesen, sind in derinfoaber nicht deklariert — sie erscheinen deshalb nicht in der generierten Parameter-Doku und bekommen keine jsPsych-Defaults.
API-Referenz
Default-Export: die Plugin-Klasse VideosimlabSagatPlugin (info.name:
plugin-videosimlab-sagat). Browser-Global und Rollup-Name: jsPsychVideosimlabSagat.
Trial-Parameter
| Parameter | Typ | Default | Beschreibung |
|---|---|---|---|
sectorId |
STRING | undefined |
Kennung des Sektors |
replayType |
STRING | undefined |
Effizienz-Typ des Replays; im Code-Kommentar: efficient / inefficient |
items |
COMPLEX | undefined |
Array der Items dieses Freezes (siehe oben) |
Dazu kommen die Standardparameter aller jsPsych-Plugins.
TypeScript-Typen (src/utils.ts)
Item, ItemType, Answer, DataItem, SagatItem sowie die optionsspezifischen Typen
SelectionItemOptions, RangeItemOptions, BarItemOptions, PedalItemOptions,
DragDropItemOptions. Die gebauten Deklarationen liegen in dist/index.d.ts
(typings-Feld).
Events und Styling
Es gibt keine eigenen Events oder Callbacks — der einzige Ausgang ist
jsPsych.finishTrial() am Ende des letzten Items; für Reaktionen auf den Freeze also
on_finish am Trial nutzen.
Die kompilierten Tailwind-Styles injiziert das Plugin beim ersten Trial einmalig als
<style id="jspsych-videosimlab-sagat-styles">. Alle Utilities tragen den Präfix tw:,
der Tailwind-Preflight ist bewusst weggelassen — die Host-Seite wird nicht überschrieben.
Trial-Daten
Der Trial schreibt ein Objekt mit drei Feldern in die jsPsych-Daten. Die Feldnamen im
Code weichen von der Repo-Doku ab — maßgeblich ist der Code (src/index.ts):
| Feld im Code | laut Repo-Doku | Inhalt |
|---|---|---|
sectroId |
sectorId |
durchgereichtes sectorId — der Tippfehler steckt im Quellcode |
replayType |
replayType |
durchgereichtes replayType |
answers |
sagat |
Array der beantworteten Items |
Jeder Eintrag in answers ist ein DataItem und spiegelt die Item-Konfiguration
zurück, ergänzt um die Antworten:
{
"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 }]
}
Was answers[].text und answers[].answer bedeuten, hängt am Item-Typ:
| Typ | text |
answer |
|---|---|---|
radio / selection |
"<Gruppe>-<Suboption>" |
"on" bei Auswahl, sonst "" — es werden alle Suboptionen ausgegeben, nicht nur die gewählten |
range |
options[i].text |
Zahlwert des Sliders |
bar |
options[i].textTop |
skalierter Zahlwert (siehe bar oben) |
pedal |
options[i].text |
String "energyPedal:<0–100>-brakePedal:<0–100>" |
dragdrop |
options[i].text |
ID des Ablagefeldes (pos1–pos4) bzw. null, solange nicht abgelegt |
Es gibt keine Zeitstempel pro Item — nur die von jsPsych selbst erfassten Trial-Zeiten
(rt, time_elapsed) — und keine Ground Truth im Plugin: Der Code-Kommentar „the
correct one is marked“ trifft nicht zu, es wird nichts als richtig markiert. Der Abgleich
mit den Fahrdaten passiert nachgelagert in der Auswertung.
Entwicklung & Tests
npm install
npm run build # baut erst src/sagat-items, dann rollup --config
npm run build:watch
npm run tsc
npm test # jest
npm run test:watch
- Zweistufiger Build.
build:sagat-itemswechselt nachsrc/sagat-items, installiert dort und baut das Solid-/Vite-Teilprojekt zusrc/sagat-items/dist/sagat-items.js. Erst danach bündelt Rollup (makeRollupConfig("jsPsychVideosimlabSagat")aus@jspsych/config) das Hauptpaket nachdist/(ESM, CJS, Browser, minifiziert). - CSS-Einbettung. Das Vite-Plugin
scripts/export-css.tshängt die kompilierte CSS alsexport const __css__ = "…"an den Entry an und löscht die separate.css-Datei — deshalb braucht das fertige Paket keine extra Stylesheet-Einbindung. - Item-Templates isoliert entwickeln. In
src/sagat-itemsstartetnpm run deveinen Vite-Dev-Server auf Port 3001 mit den Item-Komponenten ohne jsPsych drumherum. - Tests.
jest.config.cjsnutzt@jspsych/config/jest.src/index.spec.tsist allerdings noch der unveränderte jsPsych-Template-Test: er übergibt erfundene Parameter (parameter_name,parameter_name2) und prüft nur, dass der Trial endet. Es gibt faktisch keine inhaltliche Testabdeckung.
Lizenz & Zitation
MIT-Lizenz, Copyright 2026 Leonardt Wagner (LICENSE.txt, bestätigt durch
package.json).
Die CITATION.cff nennt: Leonardt Wagner (ORCID
0009-0009-1759-9674), Titel
jsPsychVideosimlabSagat, Version 1.0.0, Release-Datum 2026-02-07, kein DOI. Beim Build
ersetzt @jspsych/config den Platzhalter citations: '__CITATIONS__' in der Plugin-info
durch die daraus generierten Angaben — abrufbar über die jsPsych-Zitationsfunktion.
Zwei Fehler in der CITATION.cff nicht ungeprüft übernehmen: url zeigt auf
videosimlab-cockpit-replay statt auf dieses Repo (Copy-Paste), und Version 1.0.0
widerspricht der Paketversion 1.0.1.
Troubleshooting
Cannot find module "@jspsych-plugin-videosimlab-sagat"— der Paketname trägt den Scope@ecosimlab/. Aus@ecosimlab/jspsych-plugin-videosimlab-sagatimportieren (siehe Installation).- Trial bricht sofort ab / weiße Seite —
itemsfehlt oder ist leer. Das Plugin greift ungeprüft auf das erste Item zu; mindestens ein Item ist Pflicht. - Item wird gar nicht gerendert —
itemTypeprüfen.selectist kein gültiger Wert, Mehrfachauswahl heißtselection. Bei unbekanntem Typ bleibt der Bereich schlicht leer, ohne Fehlermeldung. - Auswertung findet
sagatnicht — das Datenfeld heißt im Codeanswers, die Sektorkennung landet untersectroId. - Layout ohne Styles — der Unter-Build fehlt. Das Hauptpaket importiert
src/sagat-items/dist/sagat-items.js(liefert Markup und__css__); ohnenpm run build:sagat-itemsschlägt der Rollup-Build fehl. - Fahrzeugbilder fehlen (
selection,dragdrop) — die Bilder werden über relative Pfade wie./cars/zoe.pnggeladen und nicht ins Bundle eingebettet. Die Dateien aussrc/sagat-items/public/cars/müssen von der Host-Seite unter diesem Pfad ausgeliefert werden. range-Slider ignoriertmin/max— beide werden für alle Slider eines Items ausoptions[0]gelesen. Slider mit abweichenden Grenzen auf eigene Items aufteilen.
Offene Punkte
- Versionsangaben im Repo. README (
#1.0.0) undCITATION.cff(1.0.0) hängen hinter Tag und Paketversion (1.0.1) zurück. DieurlderCITATION.cffzeigt auf das falsche Repo.- Feldnamen der Datenausgabe.
sectroId/answers(Code) vs.sectorId/sagat(Doku) — ob der Code korrigiert oder die Doku angepasst wird, ist eine offene Entscheidung des Teams. Die Anleitung hier wird danach angepasst.selectvs.selection— dieselbe Frage für die Typbezeichnung.- Beispiel-Konfigurationen. Im Repo liegt kein vollständiges, echtes Item-Set (z. B. das der VideoSimLab-Studie). Die Beispiele hier sind aus den Komponenten rekonstruiert, nicht aus einer produktiv genutzten Konfiguration.
- Ungenutzte Assets. Unter
src/sagat-items/public/road-signs/liegen 14 Verkehrszeichen, die von keiner Komponente referenziert werden — vermutlich für ein geplantes Level-1-Item; ungeklärt.