Variables
Variables remember story facts across the whole life: has_pet, honest_reputation,
dog_debt.
Declare what you touch
Section titled “Declare what you touch”Every variable a fragment uses is declared in that fragment, in one or both of two blocks that sit between the header and the prose:
*use crimes_committed is_gay*enduse
*declare local enum shat_yourself { no, a_little, a_lot } = no local count times_shat*enddeclare*use names shared state somebody else established. *declare introduces one:
[local|innate] flag|count|enum <name> [{ members }] [= value]. Both are optional, take
either order, and close by name: *enduse and *enddeclare.
The same two blocks declare the people and places your fragment brings with it, with a
kind word in front: character kevin. See
World Entities.
Using a name you did not declare is a build error. That is the point of the whole
thing. A variable nobody set reads as 0, so a typo like crimes_commited does not fail -
it makes a branch that never fires and a scene no player ever sees. Declaring turns silence
into a message.
If you get one of these, the error tells you which block the name belongs in, and suggests the variable you probably meant.
Mark it local unless another fragment needs it. A local belongs to your fragment
alone: behind the scenes it is stored as your_fragment_id.name, so your counter and
somebody else’s cannot collide, and you can name it whatever reads best. Only a local can
have a = default, because shared state starts unset and no fragment can know what ran
before it. The one exception is an innate, which is settled before
any fragment runs at all.
Shared state is a conversation, not a decision. You cannot add to the shared registry yourself; mention it in your submission thread and a maintainer records what it means. That is deliberate: a variable every future fragment can read is worth one sentence of agreement, and “what counts as a crime” is a question the type cannot answer.
It is recorded when your fragment is accepted, in the same commit, so a submission that does not make it in never leaves a reserved name behind. A shared variable nobody sets is a promise nobody kept, and the build says so.
The three types
Section titled “The three types”| Type | Is | Takes |
|---|---|---|
flag | true or false | *set: x, *set: x = false, *unset: x, *if x, *if !x |
count | a whole number | *set: x = 3, +=, -=, *if x >= 2, and dice rolls |
enum | one of its members | *set: x = a_little, *if x = no, *if x != a_lot |
Enums compare only with = and !=. There is no >= on an enum, because the members
have no order the game can see. If you want something that goes up, that is a count whose
meaning you write down.
*set: has_pet # shorthand for has_pet = true*set: has_pet = false # kept, but marked undone ("no longer")*set: dog_debt = 2 # integers work too*set: fate = random(6) # rolls 1..6 once, right here, and remembers it*set: visits += 1 # counts up from what it already was*set: purse -= 3 # and down*set: purse -= random(4) # rolls 1..4 once and subtracts that*unset: has_pet # erased entirely - as if it never existed= random(N) resolves to a concrete 1..N the moment the line runs - later
conditions, the display, and the saved life all agree on the same number. It’s the
roll-once companion to dice checks.
- Names are snake_case (lowercase). Casing is what separates variables from stats -
HAPis a stat,hapwould be a variable.age,playedandpronounsare reserved. - Several names can share one line, comma-separated, mixing any of the forms.
*set:/*unset:lines are positional, like*effects:- they take effect where they stand.
Born with it: innate
Section titled “Born with it: innate”Some facts about a character are not made by a scene, only found out in one. How a body takes to nicotine, which hand it favours, how well it holds a drink: your character had these before the story reached them.
*declare innate count nicotine_affinity = random(6) innate flag left_handed = chance(10)*enddeclareAn innate is rolled once, when the life begins, for every life, whether or not the
fragment that declared it ever plays. From then on it is an ordinary shared variable: other
fragments *use it and read it like anything else.
| Written | Means |
|---|---|
innate count x = random(6) | 1 to 6, rolled at birth |
innate flag x = chance(10) | true in ten lives out of a hundred |
innate count x = 5 | everyone starts at 5 |
Why not just roll it in the scene? Because then whichever fragment happens to be first owns a roll that is not about it, and every other fragment touching the same fact has to carry the same “roll it if nobody has yet” guard. Write the trait once, in the declaration, and the smoking scene can get on with being a smoking scene:
*if nicotine_affinity >= 5 Warmth, and then quiet, and the world a polite step further away.*else Your head aches and your stomach turns over. You don't get it.*endifA few rules follow from what an innate is:
local innateis a build error. An innate nobody else can read is a local with a default, which already works. The whole value is that the fact outlives the fragment that noticed it.random(N)is for a count,chance(N)for a flag. One reading each, so the error tells you which one you wanted.chancetakes 1 to 99: for always or never, saytrueorfalse.- Two fragments may declare the same innate, as long as they declare the same one. A life cannot be born on two different dice, and the build says so, naming both files.
- An innate does not show in the Life panel. The panel is a record of what happened, and nothing has happened yet. Showing a player their nicotine affinity at age zero spoils a scene they may never reach.
*set:on an innate stands. It is where a life starts, not a constant the runtime keeps restoring underneath you.
Like any shared variable, an innate belongs in content/variables.json, where a maintainer
records what the dice mean: 1 is a headache and a turned stomach, 6 is love at first drag.
Counting with += and -=
Section titled “Counting with += and -=”+= and -= change a variable relative to what it already holds. The right-hand
side is an integer or a random(N) roll, never true/false.
*set: visits += 1*if visits = 1 First time here.*else You have been here before.*endifA variable that was never set counts as 0, and a boolean counts as 1 for
true and 0 for false. A counter therefore needs no setup: the first
+= 1 produces 1. This is how you count visits, picks, coins, or patience, and
it is the backbone of the hub idiom. To print the
number in prose, write {visits} - see
value tokens.
Scratch with *tmp
Section titled “Scratch with *tmp”Some numbers only matter while the scene is being read: a nerve counter you build up over three choices, a roll you want to use twice. Those are declared where they are written, and they never leave the fragment:
*tmp count nerve = 0*tmp flag asked = falseA transient is readable for the rest of the fragment, exactly like anything else, and then it is gone. It is never written to the save, never visible to another fragment, and never in the Life panel. It always says what it starts as, because it starts fresh every time the fragment runs.
It sits inline rather than in *declare on purpose. That block is your fragment’s
contract with the rest of the corpus, and a transient has no counterpart there: nobody
else can read it and nothing persists it. If you want a number that lasts the life, that
is a local.
*tmp takes a flag or a count. An enum has no obvious value to start at, so it is
not one of them.
Clearing vs erasing
Section titled “Clearing vs erasing”Two ways to take a fact back, telling two different stories:
*set: has_pet = falsekeeps the variable as an undone fact - it happened, and was undone. For integers the convention is-1.*unset: has_petdeletes it - gone from the store, and in conditions indistinguishable from never having existed. Leave no trace. Use it for bookkeeping cleanup or facts the story should genuinely forget.
In conditions, !var is true for falsy values (false, 0, negative) and for
variables that don’t exist - both forms of clearing satisfy it. A variable is truthy
only while it holds true or a positive integer. See
Conditions.