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
Start here
Concepts
Authoring for a game
Playing a story
The generated code
The plugin and sync
On this page
- Reading one
- The top level
- The graph
- EngineBoard
- EngineNode
- EngineField
- EngineRef
- Exits, edges and gates
- EngineExit
- EngineEdge
- EngineRequirement
- EngineRequirementCheck
- EngineJump
- EngineTakenBy
- EngineQuiz
- The project tables
- EngineNodeType
- EngineFieldTemplate
- EngineEntity
- EngineVariable, EngineContext
- EngineEnum
- Expressions
- EngineDiagnostic
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 FileName | runtime.json |
const int SupportedFormatVersion | 1 |
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 | |
DefaultLanguage | The master language code. Empty for a project that never named one. |
Languages | |
Boards | |
NodeTypes · EntityDefinitions | The declarations |
Entities · Variables · Contexts · Enums | The project’s own data |
Media | Media id to where the bytes are |
MediaFocal | Media id to the picture’s focal point, for the pictures that have a face marked. Absent when none has |
MediaKind | Media id to what kind of file it is: image · video · audio · document · other. Absent when no file names one |
Tags | Tag id to label |
Expressions | Expression id to its parsed tree |
Diagnostics | What 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 | |
Type | The type id: a seeded pack id, or custom:<uuid> for a type authored in the editor. Never the type’s code. |
TypeCode | What names the generated type, or null when the type is unsupported |
Supported | False when the exporter could not resolve the node’s type |
Fields · Scripts · Exits · Requirements | |
Jump | |
TakenBy | Which 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 |
Quiz | What this node is as a question, or null when its type is not one |
ChoiceTextKeys | Which of this node’s fields say the option text of a choice that leads here |
Presents | Whether 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 | |
|---|---|
Key | The exact storage key |
Code · Label · Type · Storage | |
Repeater | Which block instance this belongs to, or null |
Presentable | |
Value | A scalar, a list of scalars, or null. Never an object. Null on an itemCounts field |
Text | Language code to rendered text |
Refs | Absent, rather than empty, when the field is not a reference type — and absent on an itemCounts field |
Items | An 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 |
ExpressionId | When it holds code or a condition |
SpeakerKey | Who 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 | |
|---|---|
Kind | entity · media · tag · command · node · board · variable |
Id · MediaId | |
Url | Where a media reference’s bytes are: a relative path under the sync output, or a presigned URL for a share link. Never an id. |
Focal | Where 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 | |
Code | A command’s code — what a dispatcher expects. Not its id. |
Args | A 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 |
ArgVariables | A 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 | |
|---|---|
PortId | A handle id, and not an identity |
Gate · GateExpressionId | |
GateChecks | The gate’s checks, decomposed and named |
ContentKeys | Which of the node’s fields carry this exit’s option text |
Repeater | |
ExitKind | offer · branch · continue · display, or null when derived |
WhenBlocked | show draws it locked, hide drops it. Never null. |
Label | The 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 |
Weight | This 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 |
Scripts | The 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 |
Items | What 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 |
Quiz | Present 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 | |
Order | The author’s ordering. Absent sorts last, then by id — never by insertion order. |
Label | The 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 · TagId | Look the tag’s label up in the payload’s Tags |
Expression | The source. For diagnostics; never show it to a player. |
ExpressionId | |
Policy | show draws a blocked choice disabled and carrying the row; hide does not draw it |
Reason | The 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 | |
|---|---|
Name | The 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. |
LeafExpressionId | Evaluate it to ask whether this part failed |
HaveExpressionId · WantExpressionId | The 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 | |
|---|---|
Variable | The 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 |
Wrap | The 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 |
Rows | Every 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:
- 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;
- 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 | |
MemberName | Sanitised and unique within its namespace. Generate from this. |
RepeaterId · RepeaterCode | |
Multiple · Presentable | |
Decimal | Three 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. |
EnumRef | The enum a select or bitmask draws its values from |
PictureRef | For 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 · Columns | For 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 | |
FieldData | Scalar field values, keyed by field id. An itemCounts field has no entry here, and a commandCall field’s entry is null |
FieldCommands | The 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 |
FieldItems | The 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 |
CodeFields | Text, code and condition values — a separate bag, also keyed by field id |
CodeExpressions | The 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 |
Localizations | Field id to language code to text. Translations only. |
RichText | Field 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 |
KeyField | The custom field whose value is the key on a keyboard — Ctrl |
PadField | The 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 |
TouchField | The 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 | |
|---|---|
Source | For diagnostics and traces. Never re-parse it. |
Mode | expression · script · condition. Only a pure mode may be used as a gate. |
Tree | Absent when Error is set, since a half-tree is worse than none |
Error | Why 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.