Skip to content

The generated constants and stores

NodeTypes, GameBoards, EntityCodes, GameTags, MediaRegistry and the four stores — the typed names that replace every string literal in your game code.

All documentation — The generated code
On this page

Almost everything a sync generates exists so your game code never types a string literal. A renamed board should break your build, not your story.

The constants

NodeTypes and NodeTypeIds

The most useful of them, and the least obvious.

NodeTypes carries one nested static class per node type the project declares, holding that type’s id, its code, and a constant for every field code it declares:

Constant
TypeIdThe stored node type id — what StepNode.TypeId carries
TypeCodeThe author’s own code for the type
one per fieldThe field’s code, ready for step.Field(…)
<Field>PictureFor a position or area field, the code of the picture field it is placed on — so a handler finds the picture without spelling its code
TrackLengthFieldThe code of the node-level number field that ends the type’s track; only on a type that names one
one nested class per repeaterCarrying RepeaterCode and that repeater’s own field codes
<Repeater>.StartField · .DurationFieldThe codes of the block’s number fields that place a row in time; a duration never appears without a start
<Repeater>.PauseFieldThe code of the number field a conversation waits after each row; only on a repeater drawn as a conversation (bubbles or screenplay)

RepeaterCode is what PlayStep.Blocks(…) takes. The role constants (StartField, PauseField, <Field>Picture, …) are how a handler reads a rule the editor decided without spelling the field’s own code — the same field is read by the same constant in the editor’s own player.

Each constant carries the field’s label and type in its doc comment, and a field backed by an option set says so and tells you to read it with field.Option().

NodeTypeIds is the same ids as a flat list, one constant per type. It is superseded by NodeTypes.<Type>.TypeId — the two always agree — and is still emitted so an existing call site keeps compiling. Use it where you need an id and nothing else about the type: a set of types to skip, a player policy declaration, a key in a table of your own.

The rest

ClassHolds
GameBoardsOne constant per board, plus GameStartBoard and GameStartNode
BoardRegistryNames — every board name
EntityCodesOne constant per entity, grouped into a nested class per definition
GameTagsOne constant per tag, plus Label(string tagId)
MediaRegistryOne constant per media item
MediaIndexThe three lookup maps, and KeyOf(string idOrCode)
OptionCodesOne class per option set, each carrying EnumCode and its option constants
OptionLabelsOf(value), the author’s label for a typed option; Of(value, language, loadJson), its translation, falling back to the master label
EntityFieldsOne constant per entity field, the key ResolvedEntity.Fields is read by, grouped into a nested class per definition
LocalizationRegistryDefaultLanguage, SupportedLanguages, and the three resource-path helpers

MediaIndex is what the media catalog is installed from, and the reason that bootstrap line names three maps.

LocalizationRegistry.GetUILocalizationPath returns the same address UIStrings.ResourcePath does. There is one address, computed two places, and they are kept equal deliberately.

The stores

Four objects, composed into the IGameState your generated code is written against. All four are views over the one script runtime — none of them holds storage.

GameVariables

One typed member per variable, grouped into a nested class per context, plus the untyped surface the evaluator uses.

Member
one property per variableTyped. Reading and writing goes through to the runtime.
one property per contextThe nested class holding that context’s variables
AllCodesEvery variable code
GetVariable(string code) · SetVariable(string code, object value)
HasVariable(string code)
GetVariableEnumCode(string code)What lets the evaluator resolve an enum comparison
Reset()Forwards to the runtime’s own reset

EntityStore

Member
FindEntity(string code) · FindEntityById(string id)
AllEntities()
WithTag(string tagId)
HasEntity(string entityCode) · HasTag(string tagId)
GetEntityField(string entityCode, string fieldName)
SetEntityField(string entityCode, string fieldName, object value)Writes the playthrough overlay; the authored entity is untouched
Run<Definition><Field>(…)One per code field: runs the script the entity holds there. Returns a ScriptExecutionResult
Check<Definition><Field>(…)One per condition field: checks it. Returns a bool
Spawn<Definition>(<Definition> template, string instanceId = null)One per definition: spawns an instance and returns its id

A script field’s call is typed by the field’s roles. For an Item whose onUse script receives target and user, both Character:

ScriptExecutionResult RunItemOnUse(Item item, Character target, Character user)
ScriptExecutionResult RunItemOnUse(string item, string target, string user)
bool CheckItemUsable(Item item, Character target)

The first overload takes the generated entities; the second takes codes or ids, which is how a spawned instance is passed. Inside the script self is item, and each role is the argument of the same name, so GameData.EntityStore.RunItemOnUse(dagger, target: goblin, user: hero) reads as the script does — and a role renamed in the editor breaks the build at the named argument, rather than the fight at run time. A field whose script receives no roles takes the entity alone. The name joins the definition’s class and the field’s member name, so two definitions with a field of the same name never collide; should two joins still produce one name, the second takes a numeric suffix and the sync reports it. The same goes for two roles whose names differ only by case, such as target and Target: both stay, the second as Target2, and each still reaches the script under its own role name. A role whose definition the project no longer has is taken as a string and reported as EDEF009.

For drawing an entity — name, colour, portrait — use EntityPalette instead. This store is the data; the palette is the presentation, and it resolves through the runtime so a renamed or translated character cannot be current in one place and stale in another.

EnumRegistry

GetEnumValue(string enumCode, string optionCode) · GetEnumName(string enumCode, int value) · IsEnum(string code) · IsBitmask(string code).

CommandHandler

The bridge between a command an author declared and a method you write.

Member
Register(IScriptRuntime runtime, EnumRegistry enums)Called by the bootstrapper
Dispatch(string commandCode, object[] arguments)
DispatchAwaited(string commandCode, object[] arguments, …)
public virtual void On<Command>(…), on CommandHandlerBaseOne per declared command
DiagnosticsWhere a command you have not overridden is reported. Set by the bootstrapper

ProjectDatabase and the data classes

ProjectDatabase carries one EntitySet<T> per entity definition, and a Seal() the bootstrapper calls once. Every read throws until it has.

Around it:

EntityBaseThe generated base — implements IProjectEntity and the tag interface, so a project with no tags still compiles
one class per definitionIts custom fields as typed members. Inherits its parent definition’s class when the project nests definitions.
one class per traitShared by every definition that carries it, and nested where a trait carries another
EntityLoader.LoadAll(db, loadJson)Fills the database
TableDataOne method per spreadsheet, each returning a list of that table’s typed row class

A table’s row class has one typed member per column. No formula is evaluated and no reference is resolved at play time — the sync server materializes cells with entity and asset references already resolved, so what you read is a value.

Written for
Unity developers · Advanced integrators
Last updated