Bindings & the data tree
Anywhere you write narration you can drop a {{binding}} that resolves, at play time, from the live game state — the hero’s name and HP, the last damage they took, how the last fight went, a story flag you set three scenes ago. One scene, many shapes. This is what lets you write reactive, “the DM remembers” prose without branching to a new node every time.
{{ in any narration field and an autocomplete opens, listing every path the engine can resolve, grouped by area (Character · Node · Event · Story…). You never have to guess a path — pick it from the list.The data tree (what you can reference)
The state is a nested tree with a few roots. The common leaves:
- character.* —
name,level,class,subclass,species,hp/maxHp,ac,str…cha,purse.gp/purse.totalCp,inventory.count. Deep forms exist too:character.stats.hp.current,character.abilities.str.mod. - event.* — the most recent thing that happened:
event.lastRoll.total,event.lastDamage.amount/event.lastDamage.type,event.lastHeal.amount. This is how you write “the fall opens a gash ({{event.lastDamage.amount}}damage).” - battle.* — post-fight stats you can react to:
battle.hero_dealt,battle.ally_dealt. - node.* —
node.title,node.visits(how many times the player has been here). - story.* — every variable you declare (flags, counters, clocks). A flag
met_mirareads as{{met_mira}}orstory.met_mira.
Two kinds of binding
A plain path — {{character.hp}} — prints the value. An expression — anything with an operator or a ternary — computes one:
You stand at {{character.hp}} of {{character.maxHp}} —
{{character.hp < character.maxHp ? "wounded and wary" : "hale and ready"}}.
{{met_mira ? "She nods as you enter." : "A stranger watches from the bar."}}Expressions support ternaries (a ? b : c), comparisons (== != < > <= >=), and || / &&. They are pure — no side effects — so they’re safe and always replay the same way.
{flag? A | B} for conditional text or a bare {token}. That was an earlier system; the engine still reads it so old published stories never break, but it is legacy. Write everything new as {{ flag ? "A" : "B" }} and {{token}} — the autocomplete and chips only work with double braces.Best practice: collapse branches with expressions — until a branch needs its own bindings
Two near-identical scenes that differ by one line don’t need to be two nodes — fold them into one with a ternary:
// Before: two scene nodes (n_stays, n_parts)
// After: ONE node
{{mira_permanent ? "She clasps your arm. \"Lead on.\"" : "She melts into the dusk."}}The limit to know: an expression’s quoted text is taken literally — a {{binding}} written inside the quotes will not resolve. So if one branch needs its own bindings (e.g. “you dealt {{battle.hero_dealt}}”), keep it as its own node, or chain several small bindings side by side instead of nesting:
// Good — small bindings in sequence, each resolves:
{{mira_debt ? "I pulled you up once today. " : ""}}{{character.name}}, {{battle.hero_dealt > battle.ally_dealt ? "you held your own." : "get some roads under you."}}How bindings look in the editor vs. in play
In the Studio (and in Test play) a resolved binding is highlighted as a gold chip so you can see exactly which words are dynamic. In a real player’s run it renders as plain prose — it reads like the sentence around it, never like a code token. Hover any chip in the editor to see what it resolves to.
Next: Triggers & actions →
