The play loop
EngineSession, DialogueEngine, PlayStep and the enums around them — every member you call, and the handful that exist for the engine rather than for you.
All documentation — Reference
Start here
Concepts
Authoring for a game
Playing a story
The generated code
The plugin and sync
On this page
- EngineSession
- DialogueEngine
- What it does at each node
- Constants
- Telling the engine what your host draws
- Events
- Properties
- Driving it
- PlayStep
- What you read
- Finding a field
- What is there for the engine, not for you
- StepNode
- The enums
- PlayVerb
- ArrivedBy
- ChoiceKind
- StepAwaiting
- EngineState
- What is not deprecated
- Before your first attempt
Three types carry a playthrough: a session holds everything, an engine walks the graph, and a step is one moment the player sees. This page is their public surface.
What a step contains — fields, choices, entities — is a step.
EngineSession
TaleShard.Runtime.Engine.EngineSession — sealed.
Everything the engine needs, built from one payload: a graph source, a script runtime and a view context.
Its constructor is private. There are two ways in.
| Member | What it does |
|---|---|
static EngineSession Create(EnginePayload payload, IScriptRuntime runtime, string? language = null, IEngineDiagnostics? diagnostics = null, bool offerBoardEntries = false) | Build a session on a payload you already have |
static EngineSession FromDirectory(string directory, IScriptRuntime runtime, string? language = null, IEngineDiagnostics? diagnostics = null) | Read the payload from a sync output directory and build a session on it |
offerBoardEntries decides what happens when a path ends on a board that has other ways in.
Off by default, so a path that ends, ends.
| Property | Type |
|---|---|
Payload | EnginePayload |
Graph | IGraphSource |
View | INodeViewContext |
Runtime | IScriptRuntime |
Engine | DialogueEngine |
Diagnostics | IEngineDiagnostics |
StartBoardId | string? — where a playthrough begins, or null when the project names no start board |
StartBoardId being null is a real answer, not a failure: the author has not set a game start, and
the engine will not guess. Guessing the first board in the file would be a plausible wrong answer,
which is why it refuses to.
void Start() begins the project’s own start board, at the author’s own start node when they
named one — reading only the board would open a different scene from the one the editor shows.
The payload’s own diagnostics are replayed into the session’s channel when it is constructed, so a problem found at export time reaches you at startup rather than never.
DialogueEngine
TaleShard.Runtime.Engine.DialogueEngine — sealed.
What it does at each node
- Run every script slot, in document order.
- Present a step, if the node has content.
- Resolve the exits.
- A
jumpTargetfield overrides all of it.
Every behaviour is derived from the node’s declared shape, never from its type name.
Constants
| Constant | Value | Why it exists |
|---|---|---|
MaxAutoAdvances | 1000 | Breaks a loop in the graph. Also caps call depth and the terminal look-ahead. |
MaxRestartsFromCommand | 100 | How many times one pump may follow a playthrough a command handler began from inside its own callback. Past this you get command-restart-limit and an error finish. |
BoardEntryPort | "@entry:" | The synthetic port prefix for a board-entry choice. This string reaches a saved playthrough’s recorded decisions — changing it invalidates existing saves. |
Telling the engine what your host draws
Three settable properties. Each throws on null, and each re-checks your declarations when set.
| Property | Type | What it declares |
|---|---|---|
SilentNodeTypes | ISet<string> | Node types this host never shows |
AlwaysPresentNodeTypes | ISet<string> | Node types this host always shows, even with no text |
SilentFields | NodeFieldSet | Individual fields this host never shows |
AlwaysPresentNodeTypes is for a node your game draws as a beat rather than as prose: an image, a
reward the player dismisses.
Events
| Event | Raised when |
|---|---|
Action<PlayStep>? StepPresented | A moment the player sees |
Action<EngineErrorEvent>? ErrorRaised | The engine could not do what the data asked, and carried on. Not an exception. |
Action<string>? Finished | The story reached the end of a path. Not raised for Stop or for an error state. |
Action<EngineState, EngineState>? StateChanged | State changed, from the first argument to the second |
Properties
| Property | Type | Notes |
|---|---|---|
Diagnostics | IEngineDiagnostics | |
State | EngineState | |
History | IReadOnlyList<PlayStep> | Every step, not the save’s capped window |
Graph | IGraphSource | |
CallStack | IReadOnlyList<CallFrame> | Outermost first. Part of a save, so a playthrough saved inside a sub-board resumes into its caller. |
CanForward | bool | |
Language | string | The storage key, which for the master language is the default key rather than the project’s code for it. Map it back through INodeViewContext.LanguageOptions before showing it. |
Driving it
| Method | What it does |
|---|---|
Start(string boardId, string? nodeId = null) | Begin on a board, at a node or at the board’s entry. Clears history, moments, the roll-forward stack, the call stack, taken ports and said lines — but not the runtime’s values. |
Advance() | Move past a presented step. Ignored unless presenting. |
SelectChoice(int index) | Take one of PlayStep.Choices. Ignored for an index that is out of range, disabled, or has no target. |
Lapse() | A countdown ran out and the player picked nothing. Takes the first flow exit that is not an offered option — never the first option. Records nothing, so a roll-forward will not replay it. |
Back() | One step back, restoring the full state as it was. Refused while a command is in flight. |
Forward() | Re-advance after a Back, replaying what the player did rather than re-running it |
SetLanguage(string language) | Re-draws the whole history and the roll-forward stack immediately |
Redraw() | Re-draw against current state without re-traversing |
NextLine(string boardId, string nodeId) | A node’s next line without moving the story — no step, no script, no visit — remembered as said. Null while the engine is drawing — a step, or the history again. See a line without a step. |
Stop() | End the playthrough. Always works, including mid-command — it is the abort. |
SavePlaythrough() | Capture everything. Throws if there is nothing to save. |
RestorePlaythrough(PlaythroughSave payload) | Put one back. Legal at any time; anything pending is abandoned. |
Redraw() is for late arrivals. Call it when something a drawn step depends on has resolved
since — a media URL, typically, which a step drawn before it arrived would otherwise carry as null
forever.
PlayStep
TaleShard.Runtime.Engine.PlayStep — sealed.
One moment of a playthrough — the contract between the engine and the game, and the only thing that crosses it.
What you read
| Member | Type | Notes |
|---|---|---|
Verb | PlayVerb | What this moment asks of the player. Switch on this. |
ChoiceKind | ChoiceKind | What kind of choices these are. Switch on this rather than inspecting a choice’s PortId. |
Presented | IReadOnlyList<ResolvedField> | The fields a player actually reads, in order. Computed once. |
Choices | IReadOnlyList<ResolvedChoice> | Already gate-filtered. Empty unless Verb is Choose. |
Fields | IReadOnlyList<ResolvedField> | Everything, including what is not text |
Node | StepNode | Its id, its board, its type |
ArrivedBy | ArrivedBy | How the player got here — a different question from Verb |
Cast | IReadOnlyList<ResolvedActor> | |
Audio | IReadOnlyList<ResolvedAudio> | Per channel: sfx and voice. Switch on Channel. |
Flags | StepFlags | IsLatest, IsTerminal, Awaiting |
Id | string |
Finding a field
Three verbs, because there are three questions.
| Method | Answers |
|---|---|
ResolvedField? Field(string code) | A field by its author-facing code. Non-repeater fields only. |
ResolvedField? FieldByKey(string key) | A field by its exact storage key, including a repeater instance’s |
IReadOnlyList<IReadOnlyList<ResolvedField>> Blocks(string repeaterCode, string? withinInstanceId = null) | A repeater’s fields, one list per block, in index order. For a repeater declared inside another’s block, withinInstanceId — an outer row’s instance id — keeps that row’s own inner rows; without it, every outer row’s, outer row by outer row |
A repeater field’s key embeds an instance id, so a code inside a repeater has one answer per row —
which is why Field cannot answer it and Blocks exists.
What is there for the engine, not for you
Public, and you should not normally touch it:
- The 12-argument constructor. Only the engine builds a
PlayStep. Traceand everything under it. Its own summary says for diagnostics.Variables— a debug view. The live source isIScriptRuntime.GetAllVariables().
StepNode
| Member | Type |
|---|---|
Id | string |
BoardId | string |
TypeId | string |
The enums
PlayVerb
What this moment asks of the player, derived from the node’s content and exits rather than its type.
| Member | |
|---|---|
Choose | The player picks |
Present | The player reads and continues |
ArrivedBy
How the player got here. Start, Jump and Return are arrivals — the player was placed
somewhere. Walk is not: they followed an exit.
| Member | |
|---|---|
Start | |
Walk | |
Jump | |
Return |
A transcript that starts a new page at each arrival is written against this. A board-id change cannot stand in for it, because a jump can happen within one board.
This says how control reached a node. Why is Trace.EnteredVia.
ChoiceKind
| Member | |
|---|---|
Exit | The ordinary case: the ways out of the node on screen |
BoardEntry | The path ended, and these are the board’s remaining ways in |
StepAwaiting
None · Advance · Choice · Timer
EngineState
Its constructor is private; every instance comes from a static.
| Member | Type | Notes |
|---|---|---|
Kind | EngineStateKind | |
Step | PlayStep? | |
Reason | string? | Why it finished: end-of-path, stopped or error |
Error | string? | |
PendingCommand | CommandCompletion? | The command being waited on, or null unless suspended |
EngineStateKind is Idle · Presenting · AwaitingChoice · Suspended · Finished.
Suspended means a command is running and the story is waiting. The engine does not decide whether
anything stays on screen — it publishes the pending command and the host decides.
What is not deprecated
Nothing here. There is no [Obsolete] member anywhere in this surface, so there is no legacy path to
avoid and no newer replacement to prefer.
One thing that is conditional rather than obsolete: AddressablesMediaSource exists only in a
project that has the Addressables package.
Before your first attempt
Several types here have an ordering constraint that is easy to get wrong once and never again. They are collected in what to get right first.