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
Start here
Concepts
Authoring for a game
Playing a story
The generated code
The plugin and sync
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.
| For | Carries | |
|---|---|---|
ErrorRaised — an event | Your game, deciding what the player sees | A message, a board id and a node id |
IEngineDiagnostics — a sink | Your developer, deciding what to fix | A 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
| Member | Notes |
|---|---|
Level | Info · Warning · Error |
Code | Match on this, never on the message. Messages are written for a human and change. |
Message | |
BoardId · NodeId · FieldKey | All nullable |
ToString() renders the whole thing on one line with the ids in parentheses, which is what you want
in a log.
EngineDiagnosticLevel:
| Level | Means |
|---|---|
Info | Something worth knowing |
Warning | The engine carried on, but an author would want to know |
Error | The engine could not do what the data asked. Playback continues. |
The sinks
| Type | Notes |
|---|---|
CollectingDiagnostics | Keeps everything in memory. The default when a host builds a session without naming one. Entries returns a snapshot; Clear() empties it. |
DelegateDiagnostics | Forwards each entry to a callback |
NullDiagnostics | Drops everything. NullDiagnostics.Instance. |
TaleShardPlayer.UnityConsole | Routes 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
| Code | Level | |
|---|---|---|
no-start-board | Error | The project names no start board |
no-entry-point | Error | A board with no way in |
node-missing | Error | An edge pointing at a node that is not there |
unsupported-node-type | Error | The 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-limit | Error | A loop in the graph, or a chain deeper than the cap |
engine-busy | Warning | A 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-superseded | Warning | A 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-superseded | Warning | The 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
| Code | Level | |
|---|---|---|
script-unparsed | Error | The expression did not parse at sync time. Recorded, never a crash and never a silent success. |
script-threw | Error | |
script-failed | Error | The runtime reported a failure |
A command
| Code | Level | |
|---|---|---|
command-failed | Error | The handler called Fail. An awaited command your game has not overridden fails this way, naming the method to override |
command-not-implemented | Warning | A 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-limit | Error | One pump followed a playthrough restarted from inside a command handler too many times |
command-result-unread | Warning | It answered, and no result variable was declared to put the answer in |
command-threw-after-completing | Warning | The handler threw after settling. The answer stands. |
command-completed-off-thread | Info | Refused. The story is still waiting. |
command-completed-late | Info | It settled after the playthrough moved on. Nothing was changed. |
command-completed-twice | Info | Only the first answer counted. |
A save
| Code | Level | |
|---|---|---|
save-node-missing | Error | The save stands on a node that no longer exists |
runtime-overlay-failed | Warning | |
variables-read-failed | Warning | |
entity-read-failed | Warning |
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
| Code | Level | |
|---|---|---|
duplicate-entity-code | Warning | two entities share a code. The first wins; the other is unreachable by code |
duplicate-field-code | Warning | two fields of one node type share a code |
unknown-field-type | Warning | a field type this build cannot name. The raw type name survives beside it |
unknown-field-storage | Warning | |
unknown-ref-kind | Warning | a reference kind this build cannot name |
exit-content-key-missing | Warning | an exit names a content field its own node does not carry |
choice-text-key-missing | Warning | a node lends choice text under a key it does not carry — resolved against the destination, unlike the one above |
quiz-feedback-key-missing | Warning | a question’s answer whose feedback field is not on its node |
item-missing | Warning | an 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
| Code | Level | |
|---|---|---|
policy-unknown-node-type | Error | A declared node type id matches nothing in this project |
policy-unknown-field-code | Error | A 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.