Expressions
An expression is a small piece of code that returns a value: true/false,
a number, or a string. Writers use them to decide which choices show up,
which scenes the player can reach, when an event fires, and how text reads.
This page is the orientation; for every operator and function the runtime
supports, see Expression operators and
Expression functions.
Where you write them
Anywhere the inspector shows a syntax-highlighted field: node and scene Visibility, event Conditions, scene Resolved when, and a handful of others. The editor validates as you type, autocompletes function and variable names, and underlines unknown identifiers in red.
The simplest expressions
The smallest expression is just a literal value or a variable:
true // a literal true
42 // a number
'hello' // a string
var_player_name // the current value of a variable
Variable names are bare identifiers: no quotes, no $, no dollar-prefix.
The editor’s autocomplete suggests them as you type.
Strings are written in single quotes throughout this manual, but both quote
styles work: 'hello' and "hello" mean the same thing. Double quotes are
handy for text with an apostrophe ("don't"), and single quotes for text
that quotes speech ('she said "run"'). When a string needs its own
delimiter inside it, escape it with a backslash ('don\'t').
Comparing values
To turn a number or string into a yes/no answer, compare it. The result is
true or false, which is what visibility and condition fields expect.
var_hp > 0 // alive?
var_score >= 100 // hit the threshold?
var_player_name == 'Alice' // string compare (case-insensitive)
var_attempts != 0 // tried at least once?
All six comparisons (==, !=, <, <=, >, >=) work on numbers, and
== / != work on strings.
Combining with &&, ||, !
Most real visibility gates chain a few conditions together:
var_hp > 0 && var_has_key // both must hold
var_helmet || var_shield // either is enough
!Played(node_warning_intro) // logical negation
(var_hp > 0 && var_mp > 5) || var_is_god // grouped with parens
&& is “and”, || is “or”, ! is “not”. The editor uses these symbols
when it generates expressions on your behalf (for example, the gates a
Dependency line writes), so they’re the form you’ll see most often in your projects. The
keyword spellings and / or / not work too if you prefer reading them
aloud. Parentheses group sub-expressions and override the default
precedence: comparison binds tighter than &&, which binds tighter than
||.
Referring to entities by id
When you pass a scene, node, tag, skill, or item to a function, you reference it by its id (the short, human-readable label you set in the editor), not by the long internal uuid:
Visits(scene_kitchen) // correct
Visits('scene_kitchen') // wrong: ids are bare, not quoted
Visits(d3f9-…-a8c1) // wrong: uuids are storage details
Each function expects a specific entity kind for each of its arguments, and its reference page calls that out. Renaming an entity’s id afterwards keeps existing expressions working as long as you rename through the editor; it rewrites references everywhere at once.
Calling functions
Most of the expression power comes from functions. The two you’ll reach for most often are play-history checks:
Played(node_meet_alice) // did this node play?
Visits(scene_kitchen) >= 2 // have they entered this scene twice?
There are around fifty in total, covering math, strings, time, lists, and sequence slots. Skim Expression functions to get a feel for the categories; you don’t need to memorize them.
Scene-scoped play history
This is the rule that catches new writers most often. Played(X),
Selected(X), and Seen(X) track state per visit of the scene
containing X. Every time a scene reactivates, it starts with an empty
visit-state; what happened in the previous visit doesn’t count.
For whole-run queries (“has the player ever encountered this content?”)
use the Ever variants:
PlayedEver(X)instead ofPlayed(X)SelectedEver(X)instead ofSelected(X)SeenEver(X)instead ofSeen(X)
Rule of thumb: if you’re asking “did this happen in this conversation
with this character?” use the per-visit form. If you’re asking “has the
player ever encountered this content at all?” use Ever.
Time in expressions
Time functions split into two families that answer different questions.
Clock functions (CurrentHour,
IsTimeBetween) answer
“what time is it right now”, and wrap every 24 hours: CurrentHour() is
never more than 23. Elapsed functions
(AbsoluteMinutes)
answer “how much time has passed since the run started”, and grow
monotonically, never wrapping. Day
sits between the two: it’s a whole-number day count that increments
whenever the clock crosses midnight, independent of what time of day the
project’s Time config starts at.
Every time function requires time to be enabled for the project (Project, then Time). With time disabled, an expression that calls any of them fails validation rather than throwing a surprise error mid-playthrough. This applies uniformly, including the duration constructors and decomposers (FromHours, TotalDays, and so on) even though they don’t read the clock directly.
The runtime auto-declares a time variable when time is enabled: a legacy
alias of AbsoluteMinutes.
It still works in existing expressions, but new ones should call
AbsoluteMinutes() directly. The name time reads as clock-of-day to a
new writer, when what it actually holds is the elapsed-minutes total; the
validator warns on bare time references for this reason.
Durations (any int that represents a span of minutes) compose across the
From* and Total* functions: FromHours(2) builds a 120-minute duration
for use inside a condition or visibility expression, and
TotalHours reads a
duration back out in whole hours. These constructors are for expression
fields only; an event field like Advance Time’s Minutes takes a plain
number, not an expression.
Boolean coercion
When a non-boolean value lands somewhere a boolean is expected (a
visibility expression, an If(...) condition, an operand of &&), the
runtime coerces it:
0,0.0, empty string,'false'(case-insensitive) →false- Anything else (non-zero numbers, non-empty strings) →
true
This means var_hp on its own works as a visibility gate (true when
non-zero), the same as var_hp != 0. The Bool(x) function exposes the
rule explicitly when you want to be clear about the coercion.
See also
- Error messages: Expressions: panel messages about undeclared identifiers and
Played/Selectedscope faults.