Skip to content
EcoSimLab
DeutschEnglish
Docs navigation
Tool docs

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.

Part of: Cockpit-Display-Replay

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 jspsych as 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 engines field, 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.json of main pins the library at v1.0.1. The README still names v0.5.0 in the same place. The package.json counts. The newest tag of the library itself is v1.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.ts writes sectorType as well, although the data block 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.js and the types. The second entry adds dist/layout.js, dist/layout.cjs and dist/layout.d.ts. The folder dist/ is in the repository.
  • src/index.ts holds citations: "__CITATIONS__". The build fills the placeholder from CITATION.cff.
  • The tests cover the trial, the boxes and the stream: src/index.spec.ts, src/layout.spec.ts, src/app/layout.spec.ts and src/app/stream.spec.ts. jest.setup.cjs stubs ResizeObserver, 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.0 in the text, v1.0.1 in package.json) and a third spelling of the import variable (jsPsychVideoSimlabcockpitReplay). The repository has to settle both.
  • main is one commit ahead of the tag 2.1.0. That commit only lifts the pin of the library to v1.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 over dist/index.browser.min.js comes 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.