Cockpit-Display-Replay
How to install the replay plugin, add it to a jsPsych timeline, set its parameters and layout, and read the trial data.
This page is the guide to the jsPsych plugin
@ecosimlab/jspsych-plugin-videosimlab-cockpit-replay. It covers the
installation, the trial parameters, the data stream and the trial data. What the
plugin is for stands on the tool page.
Every statement here comes from the repository videosimlab-cockpit-replay,
branch main (commit 65f8d37), package version 2.1.0, newest tag 2.1.0. Where
the README and the code disagree, the code counts, and this page names the
difference.
Prerequisites
- jsPsych 8.0.0 or later. The plugin declares
jspsychas a normal dependency, not as a peer dependency. Take care that a study does not bundle two jsPsych instances. - A browser and a jsPsych experiment. The plugin is a trial type. It brings no player of its own.
- Node and npm for the installation. The repository declares no
enginesfield, so it names no minimum Node version. - Your own media: one browser-compatible video file and the data stream that belongs to it. The plugin records neither.
Installation
The package is not in the npm registry. Install it from its public GitLab repository, pinned to a tag:
npm install gitlab:ecosimlab/videosimlab-cockpit-replay#2.1.0
The display library @ecosimlab/ecodisplays comes with it. The plugin pins the
library the same way in its package.json, and both repositories ship their
built dist/ folder. You need no token and no .npmrc, and the installation
runs no build.
import jsPsychVideosimlabCockpitReplay from "@ecosimlab/jspsych-plugin-videosimlab-cockpit-replay";
The
package.jsonofmainpins the library atv1.0.1. The README still namesv0.5.0in the same place. Thepackage.jsoncounts. The newest tag of the library itself isv1.0.2.
The package has a second entry point,
@ecosimlab/jspsych-plugin-videosimlab-cockpit-replay/layout. It exports the
box rules: resolveDisplayBoxes, resolveVideoSize, defaultBoxSize and
DEFAULT_VIDEO_SIZE. A screen that must show the boxes before the replay runs
reads them from there instead of a copy.
In a browser without a bundler. The README leaves this section as a TODO.
The build does deliver the file: dist/index.browser.min.js is in the
repository and sets the global jsPsychVideosimlabCockpitReplay. The example
page examples/index.html loads it that way.
The plugin injects the CSS of the library itself. The displays live in a shadow
root, and the @font-face blocks go into the head of the document once per
page. Do not add a stylesheet of the library to the page.
Quickstart
The smallest trial that runs needs three parameters and a stop value:
const jsPsych = initJsPsych();
const trial = {
type: jsPsychVideosimlabCockpitReplay,
display: "AP6DefaultDisplay",
cockpitVideo: "media/sector-1.mp4",
displayStream: sectorOneStream,
start: 0,
stop: 62.5,
sectorId: 1,
replayType: "efficient",
};
jsPsych.run([trial]);
cockpitVideo is a ParameterType.VIDEO. The plugin reads the file through
jsPsych.pluginAPI.getVideoBuffer() and falls back to the path itself. Preload
the video with the preload plugin if the value comes from a timeline variable
or from a function.
examples/index.html in the repository declares a trial without parameters. It
draws nothing and is a stub, not an example.
Displays
display takes the name that @ecosimlab/ecodisplays exports a display with.
An unknown name throws at the start of the trial, and the message lists every
name there is.
The plugin reaches 20 displays (src/app/displays.ts):
| Family | Displays |
|---|---|
| AP4 | AP4ConsumptionDisplay, AP4VelocityDisplay |
| AP5 | AP5BaselineDisplay, AP5BoxDisplay, AP5ComperatorDisplay, AP5DecisionDisplay, AP5DecisionSpeedDisplay, AP5InputDisplay, AP5SpeedometerDisplay |
| AP6 | AP6DefaultDisplay, AP6StatusQuoDisplay, AP6VariationDisplay |
| Energy score | EnergyScoreBarDisplay, EnergyScoreGlyphDisplay, MinimalGaugeDisplay |
| LEM | LEMDecisionDisplay, LEMDisplay, LEMTimeDisplay, LEMVelocityTimeDisplay |
| Input transparency | InputTransparenzDisplay |
Version 2.0.0 removed the six ids of the older studies. The README maps them:
default to AP6DefaultDisplay, status-quo-power to AP6StatusQuoDisplay,
status-quo-variation to AP6VariationDisplay, default-minimal to
MinimalGaugeDisplay, glyphe to EnergyScoreGlyphDisplay, and gamification
to EnergyScoreBarDisplay.
API reference
A default value of undefined means that the parameter is required.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
display |
STRING |
undefined |
The export name of the display. |
displays |
COMPLEX (array) |
[] |
One box per display. See “The layout”. |
displayStream |
COMPLEX (array) |
undefined |
The recorded display behaviour. See “The data stream”. |
cockpitVideo |
VIDEO |
undefined |
The video of the cockpit view. |
video |
COMPLEX |
{ width: 1400, height: 787.5 } |
The size the video is drawn in. |
start |
FLOAT |
0 |
The second of the video at which the replay starts. |
stop |
FLOAT |
0 |
The second at which the replay stops and the trial ends. |
sectorType |
STRING |
"other" |
"incoming", "outgoing" or "other". It decides the blend. |
sectorId |
INT |
null |
The sector, for the assignment of the trial. |
replayType |
STRING |
null |
The efficiency type, for the assignment of the trial. |
A trial needs either display or one entry in displays. Without both, the
plugin throws. An unknown sectorType throws as well
(src/app/sector.ts).
The layout
displays holds one entry per box. Every field but the name has a default:
| Field | Default | Meaning |
|---|---|---|
display |
the parameter display |
the export name of the display |
left, top |
280, 560 | the corner of the box, from the top left corner of the video |
width, height |
the default box, see below | the size of the box |
fit |
"contain" |
"contain" fits the whole design into the box, "width" fits the width |
The coordinates count in the pixels of video. Two entries draw two displays
side by side, which the removed parameter hasDefaultGauge did before:
displays: [
{ display: "AP6StatusQuoDisplay", left: 720, top: 560, width: 440, height: 390 },
{ display: "MinimalGaugeDisplay", left: 280, top: 560, width: 440, height: 390 },
],
Without width and height a box follows the design size of its display
(src/app/layout.ts). The energy score family gets its design size: 632 x 390
for EnergyScoreBarDisplay and EnergyScoreGlyphDisplay, 402 x 254 for
MinimalGaugeDisplay. Every other display gets 640 pixels of width in the ratio
of its design size.
All boxes hang in one layer over the video, so the blend takes them together.
The data stream
displayStream is a list of DisplayInput points. DisplayInput is the input
contract of the library, and it is importable on its own from
@ecosimlab/ecodisplays/input. The plugin declares no field list of its own.
A point can name only the fields the recording has. The plugin fills the rest
from defaultDisplayInput, so a missing field shows its default and not the
value of the point before.
timeS counts seconds from the start of the video, and it must keep counting
up. A point holds until the next one starts, and the last point holds until the
end of the video. Before the first point the displays show the default input.
The name of a field carries its unit: Percent is 0 to 100, Fraction is 0 to
1, S seconds, M metres, Mps metres per second, Kmh kilometres per hour,
and W, Kw and Kwh watt, kilowatt and kilowatt hours.
const displayStream = rows.map((row) => ({
hasSimulationData: true,
hasEnergyData: true,
timeS: row.t,
wheelspeedMps: row.v,
gear: "D",
batteryEnergyKwh: row.energy,
batterySocPercent: row.soc,
pBatteryKw: row.pBattery,
optimalSpeedKmh: row.optimalSpeed,
optimalPowerW: row.optimalPower,
referenceSpeedLine: [],
}));
Set hasSimulationData to true for every point of a recording. The README of
the library lists which display reads which fields.
The replay polls the stream every 40 milliseconds, so 25 times per second
(src/app/App.ts).
Trial data
The plugin writes these fields for each trial, next to the default data of jsPsych:
| Field | Type | Content |
|---|---|---|
sectorId |
INT | the parameter sectorId |
replayType |
STRING | the parameter replayType |
display |
STRING | the parameter display |
video |
STRING | the value of cockpitVideo as a string |
start |
FLOAT | the parameter start |
stop |
FLOAT | the parameter stop |
src/index.tswritessectorTypeas well, although thedatablock of the plugin does not declare the field. Do not rely on it.
The plugin fires no events and offers no callbacks. Use on_finish of the
trial.
Development and tests
npm install
npm run build # rollup --config
npm test # jest
npm run tsc # type check only
- The build writes
dist/index.js,dist/index.cjs,dist/index.browser.js,dist/index.browser.min.jsand the types. The second entry addsdist/layout.js,dist/layout.cjsanddist/layout.d.ts. The folderdist/is in the repository. src/index.tsholdscitations: "__CITATIONS__". The build fills the placeholder fromCITATION.cff.- The tests cover the trial, the boxes and the stream:
src/index.spec.ts,src/layout.spec.ts,src/app/layout.spec.tsandsrc/app/stream.spec.ts.jest.setup.cjsstubsResizeObserver, which jsdom does not have.
License and citation
MIT (LICENSE.txt, copyright 2026 Leonardt Wagner; the same in
package.json).
CITATION.cff names:
- Author: Leonardt Wagner (ORCID 0009-0009-1759-9674)
- Title:
jsPsychVideosimlabCockpitReplay, version 2.1.0, released 2026-09-10 - URL:
https://gitlab.com/ecosimlab/videosimlab-cockpit-replay - The DOI line is a comment, so the plugin has no DOI.
The VideoSimLab paper covers the toolchain as a whole. The tool page names it.
Troubleshooting
The trial ends at once. stop has the default 0, and the replay ends as
soon as the time of the video reaches stop. Always set stop.
An error message lists every display. The name in display, or in an entry
of displays, is not an export of the library. Take a name from the table
above.
The replay throws before it draws. A trial needs display or one entry in
displays, and sectorType must be "incoming", "outgoing" or "other". A
wrong value used to leave the whole trial invisible; it throws now.
Old recordings show nothing sensible. Version 2.0.0 replaced the type
DisplayStreamDataPoint with DisplayInput. Convert the recordings.
There is no video although the path is right. The plugin reads the buffer of
jsPsych first. Preload the file with the preload plugin.
The browser blocks the autoplay. The plugin starts the video unmuted. A browser can refuse that without an interaction before it. Put a trial with a click in front of the replay.
Open questions
- The README names two versions of the library (
v0.5.0in the text,v1.0.1inpackage.json) and a third spelling of the import variable (jsPsychVideoSimlabcockpitReplay). The repository has to settle both.mainis one commit ahead of the tag 2.1.0. That commit only lifts the pin of the library tov1.0.1. The install command above takes the tag.- The installation in a browser is undocumented. README and repository documentation hold a
TODO. The way overdist/index.browser.min.jscomes from the example page.- The repository has no example pages any more. They never showed a display. The combination with a SAGAT-Freeze is written down nowhere.