Narratomidocs
State and logic

Variables

Reference for every variable type and scope, including defaults, how each type is written, and what a new playthrough resets.

A variable is a named piece of story state with a declared type, a declared scope, and a required default. You declare variables in the right dock under Project, then Variables. Every reference elsewhere (Set beats, conditions, HUD widgets, cast bindings, {tokens}) is a dropdown of declared variables, and the compiler rejects a reference to a name that was never declared.

Names

A name must match [A-Za-z_][A-Za-z0-9_]*: letters, digits and underscores, never starting with a digit. That is the same grammar an interpolation token uses, so a legal name is always writable as {name} in a line and always typeable in condition syntax. The panel refuses a new or renamed name that breaks the rule and suggests a fix ("Try lantern_oil").

Names beginning with @ are reserved for the runtime's own bookkeeping and are rejected at compile time with RESERVED_VARIABLE_NAME. Two variables cannot share a name in the same storage document; the panel says "already taken" before you can commit the second.

Renaming a variable from the panel rewrites every reference to it in one go: Set beats, conditions, bindings, HUD widgets and tokens. Deleting one does not. The delete confirmation lists what will break, then leaves those uses pointing at a name that no longer exists, which the compiler reports.

Types

TypeHoldsDefaultCompare it againstWrite it with
flagtrue or falsefalse== true, == falseassign, toggle
numberany number0a number literal, with ==, !=, <, <=, >, >=assign, add, subtract
texta string""a string literal, with ==, !=, and lexicographic orderingassign
lista list of strings, no duplicates[] (empty)contains(bag, "crowbar") and count(bag) >= 2 onlyassign, add entry, remove entry
options (single)one of its declared choicesthe choice you picka choice string, with == and !=assign
options (multiple)a set of its declared choicesthe choices you tickcontains() and count() onlyassign, add entry, remove entry
casta cast member id, "" for none""a cast id string, with == and !=assign
assetan asset id, "" for none""an asset id string, with == and !=assign

An options variable declares its choices once, on the variable, as a comma-separated list plus a multiple toggle. Every surface that writes or tests it then offers those choices instead of a free text box, and a value outside them is a compile error (UNKNOWN_OPTIONS_CHOICE). Removing a choice never rewrites a default or a beat that still uses it; the panel warns inline and the compiler names each remaining use.

cast and asset are reference types. They hold ids, picked from the project's cast list and asset library, which is what makes cast bindings and variable-bound stage-direction assets authorable.

Values are stored as booleans, numbers, strings and string arrays, and nothing else. options, cast and asset are all string-backed, which is why they appear unchanged in the bundle format.

Which operations a type allows

The Set beat's operation dropdown is keyed to the declared type, so you cannot build a write the compiler will refuse:

  • flag: = and toggle
  • number: =, + add, - subtract
  • list and multiple options: =, + add, - remove (one entry at a time)
  • text, single options, cast, asset: = only

Subtract is a face, not a stored operation: it saves an add with a negated value. Adding an entry that a list already holds does nothing, and so does removing one it does not hold. A list is a set, not a tally.

Scopes

Four scopes exist: project, chapter, scene, persistent. Three of them do less than the names suggest.

ScopeWhat it actually does
projectStored on the project document. Visible everywhere.
chapterStored on the open chapter's document.
sceneStored on the open chapter's document.
persistentStored on the project document, and survives across playthroughs.

Nothing in the runtime resets a chapter variable when play leaves a chapter, or a scene variable when it leaves a scene. All declared variables are seeded into one flat snapshot that lives for the whole playthrough. What scope really decides is storage: project and persistent live on the project meta document, the other two live on the chapter document you have open. That has one visible consequence. Two variables with the same name in different storage documents are two separate variables, and the panel flags it.

persistent is the only scope with runtime meaning. See persistent state for what it is for and where the values are kept.

What a new playthrough does

Starting a story sets every declared variable to its declared default, then lays this browser's remembered persistent values over the top. A persistent variable that has never been remembered falls back to its default, which is why a default on a persistent variable applies on the first play and never again.

Two counters ride along in the same snapshot under reserved names you cannot declare: how many times each scene has been entered, and how many times each choice option has been taken. Both start at zero on a new playthrough and are never carried between playthroughs. The seeded generator's position lives there too, which is what randomness is about.

Reading a variable back

Two ways. In prose, a token: {gold} in a line prints the value at the moment the line renders. See tokens. In logic, a condition on an edge, a choice option, or a HUD widget's visibility.

A token naming a variable that is not in the snapshot is left on screen verbatim, braces and all, so you can see the gap. A list prints its entries joined by commas, so {bag} reads crowbar,torn note.

On this page