Menu

D61. Branching dialogues: a block that holds its graph, one pure walk replayed from the choices, the first run scored (23 Oct 2026)

On this page

Authors come from Storyline, where a branching dialogue is seven slides and twenty triggers, and from Rise, whose scenario block replaced one bubble at a time until September 2026. The founder asked for dialogues that stack in one thread, can go back on a choice, score honestly and talk with the rest of the screen. Research came first (four notes: e-learning tools and pedagogy, narrative engines, presentation and characters, going back on a choice); the founder then decided: a structure of its own held in a block, events extended freely to link it to the screen, no « business rules » in the format (a decision or an option may name a plan objective), validation by the client through the Revue and an export of the script with its map. Authors and agents read the result in Dialogues.

Where the graph lives

Weighed:

  • Screens linked by events (the mock-up taken literally). Each screen starts the thread over, so bubbles cannot stack; a score, a return to a choice or « Jouer un chemin » would have to interpret arbitrary events. It is the problem Storyline has. Rejected.
  • Events extended with sequences, waits, resets and counters. They would become a small programming language that neither authors nor the core could read back. Rejected.
  • Embedding a narrative engine (Ink, Yarn Spinner) or its syntax. A second language to learn and translate, script inside JSON, no XLIFF units, conditions and effects the validator cannot see. Rejected; Yarn stays the import worth writing first if one is asked.
  • A dialogue block that holds its graph (nodes, options, endings, its own variables), with declarative effects. Chosen. It stays a block among others: it can follow a text, precede a quiz and, once ended, send to another screen by an event. The decision block stays for a choice on its own in a page.

The format

  • A line is a narration line (id, speaker, text, delivery, pronunciations) with expression, cues (gestures, expressions and presentation actions on words, as D28 anchors) and if, so voices, alignment, takes, pronunciations, stage directions and translation serve as they are. Line ids are unique in the screen across all its tracks. A line without speaker is a narration; a speaker marked narrator says it with its voice.
  • An option is always said by the learner (no speaker: the character can never say the learner’s line, Rise’s AI defect). Without next it joins the common way (Ink’s gather): a decision with a reaction and a note per option, then one thread, needs no wiring. retry, once and fallback cover hubs and retries without variables.
  • Quality before points: good, ok, bad on each option gives the default score, the debrief, SCORM results and the editor’s colours without writing a number (the vocabulary of Rise, Christy Tucker, Cathy Moore). Points stay possible, over the highest sum a path can reach, computed on the graph; an ending may carry the score.
  • Speakers take color (a theme token) and look (an SVG that follows the characters’ convention, or pictures by expression); the list of expressions is closed.

Rejected: a rule field linking an option to a business rule (the founder: no such concept in the format or the plan); several gauges (later, without closing the format); variable text inside a line (a variant is a whole line).

One walk, pure, replayed

The walk is written once, in @elabora/player dialogue/ (no DOM, no Node), in six steps: enter a node and play its lines, offer its options (greyed or hidden by their conditions; a fallback when none can be chosen; one option is never taken by itself), choose (effects before any later condition), leave (retry, the option’s way, the common way), end, and a condition that cannot be evaluated holds. The player plays it; the core imports it for elabora dialogue play and paths, the validation, the maximum of points and the debrief, so the app, the agent and the learner compute the same thing.

A dialogue’s state is the list of choices. Everything else is recomputed by replaying it: resuming a session, going back, a new version of the module (replay stops at the first choice that no longer exists) and « Jouer un chemin » share one engine. Nothing is random. A retry replaces its choice instead of adding a decision, and a path holds at most 60 decisions, so the suspend data has a worst case.

Going back, and what is scored

Four policies (none, retry right after a consequence, back to any choice after the first end, back at any time), set at once by three modes: practice (the default: retry, then back after the end; feedback now and in the debrief; not scored), assessment (no return, feedback in the debrief, scored by quality), exploration (back at any time, feedback now). A final decision cannot be revisited, nor anything before it; once the dialogue has ended, « Recommencer » still starts a new run (the scored run is kept). Revised on 24 Oct 2026: a final decision first closed starting over too, which left a practice dialogue with nothing to do after its end.

The first run to an end is the only one scored, whatever the mode, and in each decision the first choice counts (a retried option, or a choice replaced by a return before the first end). It is the one attempt made without knowing the consequences, what a manager reads in the LMS; the score is sent once, the status never regresses, one interaction per decision is never rewritten; and the value of wrong paths stays: once the stakes are past, the learner sees what would have happened. The tree view « ‹ 1/2 › » of exploration comes later.

The dialogue and its screen

The dialogue tells its screen through triggers (dialogue.started, .node, .line, .chosen, .ended), and the module reads its scored run through conditions (ending, chosen, score, done). The screen drives it with start-dialogue, goto-dialogue and restart-dialogue. From inside, options and cues run presentation actions only (show and hide blocks and layers, play media, set an object’s props, reveal a step): replaying sets the last visibility and props of the blocks touched and opens no layer. A dialogue never writes the course’s variables: that happens on dialogue.ended, once, which is what makes going back safe (H5P’s trap of effects replayed).

Saved state

One compact string per dialogue: the current path (each option by a permanent key of elabora.lock.json, about 2 characters a decision), the scored run when it differs, and bit fields of the options explored and the endings found, each option and ending at a place given by its key, never reused. The compiler measures the worst case with the same encoder.

Tools and validation

elabora dialogue show | play | paths | export and their agent operations; set_dialogue_node writes one node whole, the only targeted operation (adding an option to a long dialogue with update_block meant writing every node again). The map is laid out by the core (dialogue/layout.ts: columns by distance from the start, barycentre ordering, dashed edges going back): the export and the app’s Carte share it, and no position is stored. The client validates through the Revue and the export of the script with its map, nothing in the plan. The validation checks targets, ids, speakers, conditions, effects, actions, cues, dead ends, what no path reaches, loops without a way out, the writing (options, lines, the data the score needs, endings that cover every score, trees that never join) and the decisions cap, each with a code and its fix. Rise’s scenario block is imported as a dialogue.

Voices and characters

A dialogue’s lines are voice lines of its screen (D62’s sources: one more, dialogueLineSource): a line with a speaker, and with learnerVoice the learner’s answers, named <node>.<option> since an option has no id of its own in the screen (a dot is never in an id). Their audio, takes, alignment, recordings, transcription, translation markers and checks are the narration’s; missing audio is told once per dialogue, a voice of several speakers says a node’s lines (or an option’s line and its reaction) as one exchange. A package ships one file per line at its media quality, narration/<screen>.<block>/<line>.m4a, never a track: a dialogue’s lines are said when the learner gets there, in an order no timeline knows.

The mouth follows the sound, v1: it speaks, the voice sounds, its opening comes from the envelope of the audio (30 values a second quantized in four shapes, thresholds from the line’s own spoken frames so that a whisper moves it as a loud voice does), closed in silences and on m, b, p. The node writes it next to the audio when it writes the line (the sidecar’s mouth), the build measures it when it is missing: the player never decodes audio. Visemes are set aside: they need phonemes per language, and the lips of flat characters read well enough with four shapes.

Characters are SVG files that describe themselves (the expressions, gestures and their durations, the face’s box), the state set by the player on one group and the parts shown by matching attributes, so that a character works without CSS and an agent draws a new one by copying a sample. Rejected: Rive (a runtime and a binary format, the founder’s choice), one SVG per expression (gestures, mouths and blink would multiply files), ids and gradients (a page shows a character twice).

Revisit if: authors need several gauges, visemes (lips that read in close-ups), a node that hosts an activity block, saved paths as tests, the tree view of exploration, a text syntax to write dialogues faster (Yarn-like), or to validate a dialogue as a versioned part of the plan; or a course’s dialogues no longer fit the suspend data (then a key space per dialogue).