Skip to content

Diagnostics

The two channels the engine reports on, the sinks you can plug into them, the thread-safety contract, and every code the runtime emits.

All documentation — Reference
On this page

The engine does not throw when the project data is wrong. It carries on, and it says so — on two channels, for two different readers.

ForCarries
ErrorRaised — an eventYour game, deciding what the player seesA message, a board id and a node id
IEngineDiagnostics — a sinkYour developer, deciding what to fixA level, a stable machine code, a message, and a field key as well

Every error reaches both. The diagnostics channel additionally carries warnings and information that raise no event at all.

EngineErrorEvent

Message · BoardId · NodeId. Both ids are nullable — a policy mistake is about the whole session and names no node.

Raised on DialogueEngine.ErrorRaised, and forwarded by TaleShardPlayer and its component.

EngineDiagnosticEvent

MemberNotes
LevelInfo · Warning · Error
CodeMatch on this, never on the message. Messages are written for a human and change.
Message
BoardId · NodeId · FieldKeyAll nullable

ToString() renders the whole thing on one line with the ids in parentheses, which is what you want in a log.

EngineDiagnosticLevel:

LevelMeans
InfoSomething worth knowing
WarningThe engine carried on, but an author would want to know
ErrorThe engine could not do what the data asked. Playback continues.

The sinks

TypeNotes
CollectingDiagnosticsKeeps everything in memory. The default when a host builds a session without naming one. Entries returns a snapshot; Clear() empties it.
DelegateDiagnosticsForwards each entry to a callback
NullDiagnosticsDrops everything. NullDiagnostics.Instance.
TaleShardPlayer.UnityConsoleRoutes the channel to Debug.LogError / LogWarning / Log. Stateless and shareable.

Silence has to be asked for. The default collects rather than drops, so a session built in a hurry still has its diagnostics somewhere to be found.

EngineDiagnosticsExtensions adds Info, Warn and Error shorthands over Report, plus ReportPayload(payload) — which replays a payload’s own export-time diagnostics into the channel. EngineSession calls it for you when it is constructed, so a problem found at sync time reaches you at startup rather than never.

Every code the runtime emits

Grouped by what went wrong. Match on the code; the message is prose.

The graph could not be walked

CodeLevel
no-start-boardErrorThe project names no start board
no-entry-pointErrorA board with no way in
node-missingErrorAn edge pointing at a node that is not there
unsupported-node-typeErrorThe view cannot read this node’s type at all. Playback takes the first exit and says how many branches it discarded — refusing to move would strand the player mid-story.
auto-advance-limitErrorA loop in the graph, or a chain deeper than the cap
engine-busyWarningA verb arrived while the engine was mid-transition: Back or Forward while a command is in flight, or NextLine from a diagnostics handler while the engine is drawing — a live step, before it is recorded and presented, or the history again (SetLanguage, Redraw, RestorePlaythrough). A NextLine from a step handler is answered
redraw-supersededWarningA diagnostics handler called Start, RestorePlaythrough or Stop while SetLanguage or Redraw was re-drawing the history. The re-draw is abandoned: a new playthrough is left exactly as the handler started it, and a stopped one keeps its live state
restore-supersededWarningThe same, while RestorePlaythrough was drawing its window. The restore is abandoned and nothing is presented; its SaveLoadReport describes the save it would have loaded, not the story the player is on

A script or a gate

CodeLevel
script-unparsedErrorThe expression did not parse at sync time. Recorded, never a crash and never a silent success.
script-threwError
script-failedErrorThe runtime reported a failure

A command

CodeLevel
command-failedErrorThe handler called Fail. An awaited command your game has not overridden fails this way, naming the method to override
command-not-implementedWarningA plain command ran whose hook your game has not overridden. Once per command, naming the method to override. Reported by the generated CommandHandler, not the engine
command-restart-limitErrorOne pump followed a playthrough restarted from inside a command handler too many times
command-result-unreadWarningIt answered, and no result variable was declared to put the answer in
command-threw-after-completingWarningThe handler threw after settling. The answer stands.
command-completed-off-threadInfoRefused. The story is still waiting.
command-completed-lateInfoIt settled after the playthrough moved on. Nothing was changed.
command-completed-twiceInfoOnly the first answer counted.

A save

CodeLevel
save-node-missingErrorThe save stands on a node that no longer exists
runtime-overlay-failedWarning
variables-read-failedWarning
entity-read-failedWarning

What a successful load had to change is not a diagnostic at all — it is the SaveLoadReport, because it is something to show a player.

The payload

CodeLevel
duplicate-entity-codeWarningtwo entities share a code. The first wins; the other is unreachable by code
duplicate-field-codeWarningtwo fields of one node type share a code
unknown-field-typeWarninga field type this build cannot name. The raw type name survives beside it
unknown-field-storageWarning
unknown-ref-kindWarninga reference kind this build cannot name
exit-content-key-missingWarningan exit names a content field its own node does not carry
choice-text-key-missingWarninga node lends choice text under a key it does not carry — resolved against the destination, unlike the one above
quiz-feedback-key-missingWarninga question’s answer whose feedback field is not on its node
item-missingWarningan entities-with-counts entry — on an exit’s hand-over, a node field or an entity field — naming an entity the payload does not carry. The entry is dropped, its count and its cell with it; the entries beside it keep their places. A hand-over’s drop is also raised on ErrorRaised as DialogueEngine.ItemMissingPlayerMessage, a sentence with no id in it that you can show a player, as the browser player raises it

A newer sync reaching an older plugin is what the three unknown-* codes exist for: the field or reference is kept, nameable, and skipped by the typed reads rather than crashing anything.

Your own declarations

CodeLevel
policy-unknown-node-typeErrorA declared node type id matches nothing in this project
policy-unknown-field-codeErrorA declared field matches nothing

Both are inert mistakes — the declaration silences nothing — which is why they are reported rather than thrown. Both messages print the project’s actual inventory, capped at twelve names, so the fix is usually in the message itself.

Deduplicated for the engine’s lifetime, so a restart does not re-report the same typo.

Written for
Unity developers · Advanced integrators
Last updated