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
| Type | Holds | Default | Compare it against | Write it with |
|---|---|---|---|---|
flag | true or false | false | == true, == false | assign, toggle |
number | any number | 0 | a number literal, with ==, !=, <, <=, >, >= | assign, add, subtract |
text | a string | "" | a string literal, with ==, !=, and lexicographic ordering | assign |
list | a list of strings, no duplicates | [] (empty) | contains(bag, "crowbar") and count(bag) >= 2 only | assign, add entry, remove entry |
options (single) | one of its declared choices | the choice you pick | a choice string, with == and != | assign |
options (multiple) | a set of its declared choices | the choices you tick | contains() and count() only | assign, add entry, remove entry |
cast | a cast member id, "" for none | "" | a cast id string, with == and != | assign |
asset | an 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:=andtogglenumber:=,+ add,- subtractlistand multipleoptions:=,+ add,- remove(one entry at a time)text, singleoptions,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.
| Scope | What it actually does |
|---|---|
project | Stored on the project document. Visible everywhere. |
chapter | Stored on the open chapter's document. |
scene | Stored on the open chapter's document. |
persistent | Stored 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.