Skip to content

The payload, field by field

Every type in runtime.json as the engine reads it — the traversal half, the project tables, and the asymmetries the format encodes on purpose.

All documentation — Concepts
On this page

runtime.json is the file a game plays from. EnginePayload in C# and FTaleShardPayload in C++ are that file as the engine reads it — the same members under each language’s naming — and this page is every type in it.

Every node in it has already been resolved by the exporter, so nothing on this side re-derives what a node type means. That is the whole point of the format — see node meaning is resolved once.

Reading one

EnginePayloadReader is the one parse of that file: both the code generator and the dialogue engine come through it.

Member
const string FileNameruntime.json
const int SupportedFormatVersion1
bool Exists(string directory)
EnginePayload ReadDirectory(string directory)Throws on a missing file, a malformed body, or a version this build does not understand

Every member tolerates absence — a missing array reads as empty — but the payload as a whole does not tolerate a wrong version. Parse once and share the result.

The top level

Member
FormatVersion
ProjectId · ProjectName
StartBoardId · StartNodeId
DefaultLanguageThe master language code. Empty for a project that never named one.
Languages
Boards
NodeTypes · EntityDefinitionsThe declarations
Entities · Variables · Contexts · EnumsThe project’s own data
MediaMedia id to where the bytes are
MediaFocalMedia id to the picture’s focal point, for the pictures that have a face marked. Absent when none has
MediaKindMedia id to what kind of file it is: image · video · audio · document · other. Absent when no file names one
TagsTag id to label
ExpressionsExpression id to its parsed tree
DiagnosticsWhat the exporter could not resolve

Media is keyed by both the media item’s id and its storage id, because a stored reference may hold either. MediaFocal is keyed the same two ways and covers only the pictures with a face marked; it is what a reference minted from a bare id — an entity’s portrait, an entity’s asset field — answers its focal point from, the way such a reference answers its url from Media. MediaKind is keyed the same two ways; the engine reads it to decide which media fields are sound effects — a field is an sfx clip on its step only when its file’s kind is audio.

Tags omits a tag that has no label — the id is never a fallback for one.

Surface Diagnostics rather than discarding them. A field the exporter could not read is not a type error on this side, so nothing else will ever report it. EngineSession replays them into its own diagnostics channel for exactly that reason.

The graph

EngineBoard

Id · Name · StartNodeId · Nodes · Edges.

When StartNodeId is absent or names a node that no longer exists, the entry is derived: a root with an outgoing edge — not merely a root, since a board is usually full of isolated strays and every one of them is a root.

EngineNode

Member
Id · BoardId
TypeThe type id: a seeded pack id, or custom:<uuid> for a type authored in the editor. Never the type’s code.
TypeCodeWhat names the generated type, or null when the type is unsupported
SupportedFalse when the exporter could not resolve the node’s type
Fields · Scripts · Exits · Requirements
Jump
TakenByWhich one of the node’s track rows plays, chosen by a number variable the game advances — see EngineTakenBy. Null when the node’s type picks no variable to take its track by, or draws no lane as a track
QuizWhat this node is as a question, or null when its type is not one
ChoiceTextKeysWhich of this node’s fields say the option text of a choice that leads here
PresentsWhether this node’s type says anything to the player at all
EntryPoint · EntryKind

Scripts run on entering the node, in document order, before any step is emitted.

ChoiceTextKeys are already selected by the exporter, in document order. Use them rather than re-deriving them — though when they are used is the engine’s decision: a contentless exit borrows them only when it is one of several silent, ungated, underived ways out.

Presents being false silences every one of the node’s fields, which no single field can say for itself. It does not silence a choice’s label.

EntryPoint is what the author’s node type declares. A board whose author never used a declaring type still has ways in — they are found structurally.

EngineField

Member
KeyThe exact storage key
Code · Label · Type · Storage
RepeaterWhich block instance this belongs to, or null
Presentable
ValueA scalar, a list of scalars, or null. Never an object. Null on an itemCounts field
TextLanguage code to rendered text
RefsAbsent, rather than empty, when the field is not a reference type — and absent on an itemCounts field
ItemsAn itemCounts field’s entries, each an EngineItemCount — the entity Ref, the Count, and the Slot when the entry has a cell — in the author’s order, duplicates kept. Present on every itemCounts field (empty when it holds nothing) and on no other; an empty list in the reader when the key is absent
ExpressionIdWhen it holds code or a condition
SpeakerKeyWho says this line: the Key of the field on the same node that names its speaker — a conversation row’s own entity field, not one on its beat after the pause; a row with none asks its outer row, then the node; an exit row is never the node’s. Present only on a spoken field whose node names a speaker. A voice clip’s VoiceCode is that character’s

A shape a consumer could not draw arrives as Value: null rather than as a nested object — so a reader never has to defend against one. An itemCounts field carries its entries in one shape, the same one everywhere the value sits: Items here, Items on an exit that hands them over, and FieldItems on an entity — each entry an unresolved entity reference, a count, and its cell when it has one. The payload view looks each entry up and hands the resolved field its references, its counts as the value, and its cells, so ResolvedField.Items() reads the author’s pairs; an entry naming an entity the payload does not carry is dropped whole, its count and its cell with it, and reported as item-missing — the same rule for an exit’s hand-over and an entity’s field. The cells are already laid out by the editor: with a capacity on the field (maxItems, the grid), every entry the editor shows in a cell carries that cell, so a chest reaches the game as the author sees it; without one only the entries she placed herself carry one. The field’s declaration — on the entity definition or the node type’s field template — carries maxItems and, when the grid has a fixed width, columns, and the payload view hands both to the resolved field as ResolvedField.Capacity and ResolvedField.Columns, so a game that draws the grid reads its size off the step’s field and places a cell at row slot / Columns, column slot % Columns, never walking back to the type.

EngineRichText is Html and Text — the same content rendered twice, at export time.

EngineRef

A reference the exporter has already looked up, so a consumer performs no lookup of its own.

Member
Kindentity · media · tag · command · node · board · variable
Id · MediaId
UrlWhere a media reference’s bytes are: a relative path under the sync output, or a presigned URL for a share link. Never an id.
FocalWhere the picture’s face is: { x, y }, each a fraction 0..1 of the picture’s box from its top-left corner. Present only when a face is marked. A presenter that crops the picture crops toward it, and crops about the centre when it is absent
Label
CodeA command’s code — what a dispatcher expects. Not its id.
ArgsA command call’s arguments, positionally: one per parameter the command declares, in its declared order. An unfilled parameter is null, and so is one bound to a variable
ArgVariablesA command call’s variable bindings, aligned index for index with Args: a variable reference (Code, ContextCode) where the author bound the parameter to a project variable, null everywhere else. Absent when no argument is bound. The payload view reads the variable’s value into the argument when it resolves the call, so a resolved step’s command already holds it
NodeId · BoardId

EngineRepeaterRef is RepeaterId · RepeaterCode · InstanceId · Index · Ported · Gate · GateExpressionId · Plays · Weight · Once · Parent. Parent is present only on a row of a list declared INSIDE another list’s block: it is the outer row’s own ref, and its InstanceId is what you pass to read just that row’s inner rows. The code names the generated list member. Ported is present, and true, when the repeater’s block carries a source handle: its rows are the node’s exits, each row one of the step’s choices — a generic presenter reads such a row as a choice, never as content, and its fields also ship with Presentable false. Gate is a read row’s own condition, the source — present when the block declares one and the repeater is not ported — and GateExpressionId is the id of its parsed expression, null when the gate is null or blank. The engine has already applied it by the time a step exists: a row whose gate evaluated false is dropped whole, none of its fields reaching the step, and a row whose gate is blank is kept, since the author has written no test yet. A ported row carries neither; its condition is its exit’s.

Plays says which of a read list’s rows are presented, after the gate: "first" keeps the holding row with the lowest Index, "random" keeps one holding row drawn by Weight (zero never fires; one when the block has no weight number), "leastRecent" keeps the holding row said longest ago — a row never said beats every row that was, and the lowest Index wins among equals; while any holding row carries a condition (a non-blank Gate), only those rows compete, so an “any time” filler waits for a moment none of them fits — and absent keeps every holding row. The engine has already applied it too, so the step carries only the rows that play. The random row is drawn from the playthrough’s seeded generator — the same one a random branch and a script’s random() draw from — and the number is consumed the moment the step is presented, and again whenever a rollback or a restore presents that step again, so a save, a rollback and a redraw all show the same row and nothing drawn while the row is on screen — a NextLine, a script’s roll() — receives its number. A ported row carries neither key.

Once is present, and true, on a read row whose block’s once flag the author ticked. Such a row plays once per playthrough: once it has been said, the engine skips it exactly as it skips a row whose gate failed, under every Plays. A row is said when a step presents it or NextLine returns it; the engine keeps that memory — for a "leastRecent" list’s rows and for a Once row, and for nothing else — in the save as SaidRows, so a load does not forget it, and each moment of the rollback window carries the memory as it stood when that moment was drawn. A ported row never carries Once.

EngineScript is Key · Code · Source · ExpressionId.

Exits, edges and gates

EngineExit

One exit the node’s type declares.

Member
PortIdA handle id, and not an identity
Gate · GateExpressionId
GateChecksThe gate’s checks, decomposed and named
ContentKeysWhich of the node’s fields carry this exit’s option text
Repeater
ExitKindoffer · branch · continue · display, or null when derived
WhenBlockedshow draws it locked, hide drops it. Never null.
LabelThe exit handle’s own label, or null when it has none: the option’s words when neither ContentKeys nor the edge’s label give any. Read only for an exit that is already offered; it never makes an exit an offer. Not translated
WeightThis exit’s weight in a draw among the passing flow exits, already read: a finite number of zero or more. Present only when the exit’s row declares a weight field — a random branch; null everywhere else
ScriptsThe scripts that run when this exit fires — the script cells of the exit’s own row, each in the shape of an EngineNode.Scripts entry and never also listed there. Absent in the JSON for an exit with none; an empty list here
ItemsWhat this exit hands over when it fires: every entry of every itemCounts field on the exit’s own row, each an EngineItemCount — an entity Ref, a Count and, when the entry has a cell, a Slot — in the author’s order with duplicates kept; the same shape a node field’s Items and an entity’s FieldItems carry. Absent in the JSON for an exit whose row declares no such field; an empty list here
QuizPresent when this exit is an answer of a question: Correct and FeedbackKeys. Null on every other exit

An absent gate passes. A present but blank gate fails on an exit taken automatically, since a blank gate there is an unfilled one, and is no condition on an exit the player is offered (ExitRules.BlankGateFails). The same holds for each of ExtraGates.

The ExitKind derivation, when the author left it unset: content makes it an offer, otherwise a gate makes it a branch, otherwise it continues.

Empty ContentKeys means there is nothing to show, so the exit is a branch rather than a choice. The runtime adds a second source — the edge’s own label. An exit that is offered all the same (a declared offer) and has neither reads its Label as a third source.

An exit’s scripts and items belong to the moment it fires. The engine runs Scripts after the node’s own scripts and before the target node is processed, and takes Items at the same moment; both ride to the next presented step — the runs on its Trace.ScriptsRun, the items on its Trace.Received — so a Loot roll’s drawn outcome reaches the player through that one outcome and no other. The items are taken as ids and resolved when that step is composed, after the exit’s scripts and the target’s own have run, so a script on the exit that writes the item’s field is seen on the entry. The engine keeps no inventory and merges nothing.

A weight changes how a silent node leaves. With no weight anywhere, the first passing flow exit is taken. When any passing flow exit carries one, the engine draws one number from the playthrough’s seeded generator and picks among them by weight — an exit with no weight counts as one, and a weight of zero never fires. When every weight is zero nothing is drawn and the path ends there, exactly as it ends at a node with no exits: a waiting call frame resumes its caller. The draw is replay-safe for the same reason a script’s roll is — the moment’s recorded generator state puts the generator back, so backing up and advancing again draws the same exit.

EngineExitAnswer, the Quiz on an exit, is Correct and FeedbackKeys — the row’s boolean, and the keys of its feedback fields into the node’s Fields, empty when the block declares no feedback field. The keys are structural, like PictureKey: a blank row still carries its key, and the engine skips the feedback step on the resolved text instead. The engine offers such an exit as a choice whatever its own port carries; what happens after the pick is the node’s Quiz, below.

EngineItemCount is one entry of entities-with-counts, wherever the value sits — an exit’s Items, a node field’s Items, an entity’s FieldItems: Ref (an entity reference, not looked up — the engine resolves it when it reads the entry), Count (a whole number of one or more), and Slot (the cell of the inventory grid the entry sits in; absent in the JSON, null here, when it has none). One shape for the three, so a reader parses it once. An entry naming an entity the payload does not carry is dropped, its count and its cell with it, and reported as item-missing — against the exit, the node field or the entity field it sat on.

EngineEdge

Member
Id · BoardId · Source · Target
SourceHandle · TargetHandle · Type
OrderThe author’s ordering. Absent sorts last, then by id — never by insertion order.
LabelThe edge’s own label by language code: a second source of choice content
Condition · ConditionExpressionId · ConditionChecks

Both the edge’s condition and the exit’s own gate must pass.

EngineRequirement

A property of the node itself, not a field its type declares. Any node may carry any number, and they compose by AND.

Member
Id · TagIdLook the tag’s label up in the payload’s Tags
ExpressionThe source. For diagnostics; never show it to a player.
ExpressionId
Policyshow draws a blocked choice disabled and carrying the row; hide does not draw it
ReasonThe author’s own sentence. Absent is the normal case.
Checks

A deleted tag still ships its id here. Resolve the label to null rather than discarding the row, which is real and editable.

EngineRequirementCheck

The same shape for a requirement, a gate and an edge condition — so a blocked option can name what the player needs rather than show source.

Member
NameThe left side of a comparison, or the whole leaf otherwise. Never the right side, which is the threshold rather than the thing the player lacks.
LeafExpressionIdEvaluate it to ask whether this part failed
HaveExpressionId · WantExpressionIdThe two sides, for their values. Absent when the leaf is not a comparison.

Names are computed at export time, since there is no parser on this side. Which checks failed is a runtime question — evaluate each leaf and draw only the failures.

GateChecks is empty when the gate is absent, blank, unparsed, or a bare literal.

EngineJump

FieldKey — which of the node’s fields carries the target — and ReturnAfter, which pushes a call frame so the path returns here when the jump ends.

EngineTakenBy

Which one of a node’s track rows plays, chosen by a number variable the game advances — an hour, a turn, a day, an alert level. A routine that follows the clock, a week that follows the day.

Member
VariableThe variable’s context-qualified name, read through the runtime’s GetValue. Null when the node picks none, or one the project no longer has — then only a row with no start can be taken
WrapThe length a span wraps round, or null when spans do not wrap: a clock ruler’s (24 unless the node’s length field says otherwise) or a wrapping ruler’s — its length field, else the ruler the card draws: the furthest row end, a repeated row’s last one, with a little headroom so the ruler ends on a round number (60, until a row ends past nine tenths of it, when the type has a length field left empty). A value past the last row therefore wraps exactly where the author sees the ruler end
RowsEvery row the rule chooses among, in stored order, each an EngineTakenRow — the rows of the lanes drawn as a track only. A type whose lanes are drawn any other way (a list, a table, exit rows drawn as reply chips) ships no TakenBy, and those rows play as they would with no variable

EngineTakenRow is RepeaterId · InstanceId — the same address every field and exit of the row carries in its Repeater — · Ported · Start · Duration · Condition · ConditionExpressionId. Start is the row’s place on the ruler, null when unset; Duration its length, null for a mark. Condition is a read row’s gate or a ported row’s exit gate, null when it has none; a blank one is none here.

The rule, in Rows order:

  1. the first row with no start whose condition holds — a row with no start and no condition is never taken, because it says nothing about when;
  2. else, when the variable’s value is a finite number, the first row with a start whose span holds the value and whose condition, if it has one, holds. A value that is not a number takes no span.

A span runs from its start, included, to start plus duration, excluded, so 6 for 6 and 12 for 6 never both hold 12; a row with no duration holds exactly its start. With Wrap set, the value and the start are both read round the ruler (((x % wrap) + wrap) % wrap), so 22 for 4 on a 24-hour clock holds 23, 0 and 1, and a duration of the whole ruler or more holds every value.

The engine has already applied it by the time a step exists. A taken read row is the one track row the step presents; the others are dropped, after the row gate and before Plays. A taken ported row has its exits tried first, as flow exits whose own gate the rule already answered — followed, never offered as a choice — every other row’s exits are removed, and the node’s own exits follow. No row taken means no track row plays and the node’s own exits apply.

EngineQuiz

A question, resolved: RepeaterId · CorrectPortId · WrongPortId · Score. Its answer rows are the node’s exits carrying Quiz, and the two ports are the type’s first two node-level source handles.

The engine offers the answers as the step’s choices; after a pick it presents the picked row’s feedback as a step of its own, skipped when the feedback is empty; then it takes the correct port’s edge when the row is correct and the wrong port’s otherwise. WrongPortId is null when the type declares one source handle only — a wrong pick then ends the path. Score is a script, the increment of the score variable by the question’s worth, run on the correct path exactly as a script slot is, so the next step’s script list names it as quiz:score. It is null when the type names no score variable or the instance leaves it unset; such a question counts nothing, and the payload’s own Diagnostics say so.

The feedback step is recognised by how its moment was entered — from the question itself, through the answer’s port — and every way a step is re-drawn rather than walked to reads the same fact: a save taken on the feedback restores as the feedback, and a language switch keeps it the feedback.

The project tables

EngineNodeType

Type · Code · ClassName · Label · Fields · Repeaters · Presents · EntryPoint · TrackLengthField. The last is the code of the node-level number field that is the length of the type’s track — what a handler waits for before leaving — present only when the type names one; a type whose lanes have no declared length runs until its last row ends.

EngineFieldTemplate

Member
Id · Code · Label · Type · Storage
MemberNameSanitised and unique within its namespace. Generate from this.
RepeaterId · RepeaterCode
Multiple · Presentable
DecimalThree states. True is decimal, false is whole-number, and null is not declared — a different claim, so a generator need not pick one numeric type for every number field.
EnumRefThe enum a select or bitmask draws its values from
PictureRefFor a position or area field, the code of the asset field on the same type whose picture the point or rectangle is placed on. Present only when the author chose one; a generator emits it beside the field’s own code so a handler finds the picture without spelling its code
MaxItems · ColumnsFor an itemCounts field, the inventory grid’s capacity and how many cells wide it is — each a whole number, each present only when declared. Cells count row by row, so slot / Columns is a cell’s row and slot % Columns its column. The payload view carries them onto the resolved field as ResolvedField.Capacity and ResolvedField.Columns; the engine lays out nothing

Codes are unique per namespace rather than per type, so a type can carry the same code both at its top level and inside a repeater. Qualify by RepeaterCode when addressing one.

EnumRef is present only for an enum-backed field. A select with inline options carries no enum and no integer values, so it can only produce a plain string — and this being absent is how you know.

EngineRepeaterTemplate is Id · Code · ClassName · Label · Min · Max · StartField · DurationField · PauseField · EveryField · RepeatField · Ported · GateField · Plays · ParentRepeaterId. Plays is the list’s "first", "random" or "leastRecent" (see EngineRepeaterRef.Plays), absent when every holding row plays. ParentRepeaterId is present only on a list declared inside another list’s block, and names the outer one. StartField is the code of the block’s number field that places a row in time — present only when the block marks one; a repeater with no start is an ordinary list, not a lane. DurationField is the code of a row’s duration in the same unit, never present without a start. PauseField is the code of the number field a conversation waits after each row — present only for a repeater drawn as a conversation (bubbles or screenplay). Ported is the block’s own copy of the flag above: its rows are the node’s exits. GateField is the code of the block’s condition field a read row is gated by — present only on a repeater that is not ported and whose block declares one, so the generated constant names exactly the field the engine skips rows by.

EngineEntity

Member
Id · EntityDefinitionId · Name · Code
PictureId · Color
FieldDataScalar field values, keyed by field id. An itemCounts field has no entry here, and a commandCall field’s entry is null
FieldCommandsThe call every commandCall field holds, keyed by field id: a command reference in the shape a node field’s call arrives in, ArgVariables included. A field whose call is empty, or names a command the project no longer has, has no key; absent for an entity that holds no call; an empty map in the reader. The payload view answers the entity’s field from it, so Command() reads a device’s call exactly as it reads a node’s
FieldItemsThe entries of every itemCounts field the entity holds, keyed by field id: each an EngineItemCount — the entity Ref, the Count, and the Slot when the entry has a cell — in the author’s order, duplicates kept. Every declared itemCounts field has a key (empty when it holds nothing); absent for an entity whose definition declares none; an empty map in the reader
CodeFieldsText, code and condition values — a separate bag, also keyed by field id
CodeExpressionsThe parsed script of every non-blank code field and the parsed condition of every non-blank condition field, keyed by field id: an id into Expressions (a script for a code field, an expression for a condition field). The source text stays in CodeFields; one that did not parse is still here and its expression carries the error. Entities holding the same text share one expression. Absent for an entity that holds no script; an empty map in the reader. Run one through the entity — an entity’s script — never with ExecuteScript, which binds no self
LocalizationsField id to language code to text. Translations only.
RichTextField id to language code to rendered text

The definition names its fields by name, so resolve a name to an id before looking one up.

An itemCounts field on an entity travels in the one shape it has everywhere: FieldItems[fieldId] is the entry list, each entry exactly what a node field’s Items and an exit’s Items carry. The payload view resolves them into the resolved entity’s field, so ResolvedField.Items() reads a recipe’s ingredients, or a chest’s contents, exactly as it reads a loot row’s; an entry naming an entity the payload does not carry is dropped with its count and its cell and reported as item-missing.

Two synthetic keys, which are not field ids: __name__ in Localizations, and __description__ in RichText — the description every item has and no definition declares.

An entity carries only the translations an author can edit: its name’s when its definition allows translated names, its description’s when it allows translated descriptions, and a field’s when the field is marked localizable. Anything else falls back to the master value.

EngineEntityDefinition is Id, InheritanceChain, CustomFields and MentionLook. InheritanceChain (inheritanceChain on the wire) is the ids of the definitions it inherits from, nearest first; the key is omitted when it inherits from nothing, and reads as an empty list. It is what makes an entity of this definition the right kind for a script role typed by any of them. CustomFields is every field the definition has, in this order: each ancestor’s (its traits’ fields, then its own), then the definition’s traits’ fields, then its own. A trait field’s Id is the key its value sits under in FieldData (<instance>__<field>, through a nested trait <outer>__<inner>__<field>) and its Name carries the trait’s prefix (stats.armor). MentionLook may name any of them. EngineEntityField is Id · Name · Type · Multiple · MaxItems · Columns · Computed · Roles — Multiple nullable, so a payload that predates the key reads as false; MaxItems and Columns the same grid declaration EngineFieldTemplate carries, present only on an itemCounts field that declares them, and reaching the resolved entity field as ResolvedField.Capacity and ResolvedField.Columns. Computed is true on a formula field and absent otherwise: its value in FieldData or CodeFields was worked out from the item’s other fields when the payload was built. It is fixed for the playthrough — a script that writes it is refused, and a saved value on it is dropped on load. Roles (roles on the wire) is present only on a code or condition field whose script receives more than self: each an EngineScriptRole, Name (target) and DefinitionId (the entity definition its value belongs to). A run must pass a value for every role and for nothing else, each an entity of that definition or of one whose InheritanceChain names it; the key is omitted when there are none, and reads as an empty list.

MentionLook (mentionLook on the wire) says how an @-mention of one of the definition’s records draws in rich text. Absent means its name, and the key is omitted rather than written empty, so a project that declares no such definition keeps the payload it had. The one look today is a keycap:

"mentionLook": { "as": "keycap", "keyField": "keyName", "padField": "padControl", "touchField": "touchGesture" }
Member
As"keycap" — the records are input actions, and each record’s code is what a game maps to its own action
KeyFieldThe custom field whose value is the key on a keyboard — Ctrl
PadFieldThe custom field whose value is a gamepad control by position: south · east · west · north · lb · rb · lt · rt · ls · rs · lsClick · rsClick · dpad · dpadUp · dpadDown · dpadLeft · dpadRight · start · select — the options of the Input actions pack’s Pad controls enum, which the project may edit
TouchFieldThe custom field whose value is a touch gesture: tap · doubleTap · touchAndHold · swipe · drag · pinch · rotate · onScreenButton · onScreenStick

The three field slots name custom fields by name, each optional, and each a preview only — what the editor draws on the canvas and in play mode. A game draws the player’s current binding, which it resolves itself, and reads these only as a last-resort fallback. An as this reader does not know reads as absent rather than failing the load, so a payload from a newer editor draws such a mention as the record’s name; a blank slot reads as null. The payload view hands the look to every entity of the definition as ResolvedEntity.MentionAs, and the entity display marks a mention of one as an action — see an input action’s glyph.

EngineVariable, EngineContext

Id · Code · Type · DefaultValue, plus an expression id for a condition-typed variable — a named expression rather than a value, whose authored source is its default value.

The runtime resolves a variable by code, never by id.

EngineContext is a Code.

EngineEnum

Id · Code · Kind (enum or bitmask) · Options.

An enum is named by its code — what an author types before the dot — and a member by an option’s value, which is also what a stored select field holds. Both sides of a comparison are therefore the same string, which is what makes state == GameState.Hostile resolvable.

EngineEnumOption is Value · IntValue. The integer is what lets a generator emit an integer-backed enum, and it is absent for a select with inline options. An option carries no label here; the generated enum takes its labels from codegen.json.

Expressions

Expressions maps an id to one authored expression, parsed once at export time. An engine ships an evaluator and no parser, so this tree is the only form an expression can be evaluated from — see expressions ship as trees.

Member
SourceFor diagnostics and traces. Never re-parse it.
Modeexpression · script · condition. Only a pure mode may be used as a gate.
TreeAbsent when Error is set, since a half-tree is worse than none
ErrorWhy it did not parse

An expression that did not parse is refused at evaluation time — a gate reads false and is reported, never true.

EngineDiagnostic

Severity · Code · Message · BoardId · NodeId · FieldKey · NodeType.

The same shape as the runtime’s own diagnostics, which is what lets ReportPayload replay them into one channel without translation.

Written for
Unity developers · Advanced integrators
Last updated