Standalone HTML player
Export → Standalone HTML player produces a single .html file with the
scenario JSON, a player runtime and its UI all inlined. No server, no
external requests, no assets; it runs from file://, from an email
attachment, from any static host. It records the path taken and shows a
summary at the end.
example-scenario-player.html is the example project as a player — open it and view source.
What it is for
Section titled “What it is for”- Playtest builds before the engine work exists.
- Review links — send a stakeholder one file.
- QA harness — a deterministic reference for “is it the content or the integration?” (see Building a custom runtime).
- The base of the SCORM and xAPI packages.
What it supports
Section titled “What it supports”Everything in the runtime semantics: group
nodes, priority suppression, passthrough, [f]-forced menus,
DelaySimStatus, | sentence splits, [var=…] substitution, [?Var]
input prompts (rendered as a text field), conditions and scripts in the
Lua subset (see Lua),
cross-conversation links, and localised text for the language chosen at
export.
Deliberately not included:
- Media. No audio, video or pictures — they would make the file large and break offline portability. The player is text.
- Full Lua. A WebAssembly Lua would be ~400 KB of base64 and would not
load from
file://on some browsers. Scripts outside the subset are reported in the export’s warnings and are no-ops in the player. - Persistence. No
localStorage: a training file that leaves state on a shared machine is a support ticket, and SCORM owns persistence anyway. Reloading the page restarts the run.
Conformance
Section titled “Conformance”The player runtime is a port of the editor’s simulator, and it is kept faithful by a test rather than by shared source: every commit runs a battery of scenarios through both and asserts byte-identical transcripts and final variable state. If the editor plays it one way, the file plays it the same way.
The JavaScript API
Section titled “The JavaScript API”The page exposes window.chatmapperPlayer. A wrapper that loads before
the player (a <script> in <head>, which is how the SCORM wrapper is
injected) may pre-create the object; the runtime merges into it rather than
replacing it.
interface ChatMapperPlayer { version: 1; scenario: Scenario; // the embedded canonical JSON session: Session; // the live traversal summary: Summary | null; // null until the run ends
onComplete: ((s: Summary) => void) | null; // assignable; called once when the run ends onChoice: ((c: Choice) => void) | null; // assignable; called as each choice is taken whenComplete(fn: (s: Summary) => void): void; // like onComplete, but fires immediately if already ended beforeComplete?: (s: Summary, done: () => void) => void; // optional gate: call done() to release onComplete restart(): void; // new session; summary back to null}
interface Session { start(): State; choose(index: number): State; // index into state.choices submitInput(values: Record<string, string | number>): State; // answer a [?Var] prompt summary(): Summary; state: State | null;}
interface Summary { completed: boolean; // the run reached an end of conversation nodesVisited: number; // unique playable nodes entered totalNodes: number; // playable nodes in the whole project visited: { convId: number; nodeId: number }[]; endings: { convId: number; nodeId: number }[]; // nodes that had no successor endingsReached: number; choices: { convId: number; nodeId: number; text: string }[]; // in the order taken variables: Record<string, unknown>; // final state, keyed by cleaned Lua name}Examples
Section titled “Examples”Log the path a tester took:
<script> window.chatmapperPlayer = window.chatmapperPlayer || {}; window.chatmapperPlayer.onChoice = c => console.log('chose', c.text, 'at', c.convId + ':' + c.nodeId);</script>Post the result somewhere when the run ends:
window.chatmapperPlayer.whenComplete(summary => { fetch('/playtests', { method: 'POST', body: JSON.stringify(summary) });});Drive the player headlessly (no UI) — the runtime and the UI are separate scripts, and the runtime touches no DOM:
const s = window.chatmapperPlayer.session;let st = s.start();while (!st.ended) { if (st.pendingInput) st = s.submitInput({ PlayerName: 'Ines' }); else st = s.choose(0); // always the first option}console.log(s.summary());Building it programmatically
Section titled “Building it programmatically”The export has two options beyond the defaults: the starting conversation (defaults to the first) and the display language. The SCORM and xAPI packages add head/tail scripts around the same file.