Skip to content

State & Variables

Declaring variables in GameInit and the namespace/story-flag model for reading and writing state.

Reference: Declarations for (declare:) / (refine:), and Loops, Arrays, Numbers & Strings for namespace queries.

GameInit & Variables

Boot sequence overview

The engine does not run GameInit on startup. Instead, it boots to a title screen (@system(title)), and GameInit runs in two cases: when the player starts a new game via the (game-new:) macro, and internally when the engine loads a save slot (to re-establish the variable schema before deserializing saved values). This keeps variable declarations separate from the title screen flow:

ana
:: TitleScreen @system(title)

@zone(text)
My Game

@zone(options)
(link: "New Game")[(action: _Engine_NewGame)]

:: _Engine_NewGame

(game-new:)

(game-new:) executes @system(init) and then navigates to whatever passage GameInit declares with (start:).

Dev fallback: if no @system(title) exists, the engine auto-runs GameInit directly. Useful for testing without a title screen.


GameInit

GameInit is marked @system(init). It runs when (game-new:) fires, and also internally on every save load so that global variables are declared before saved values are applied. This is where you:

  • Declare all global variables
  • Declare the first game passage with (start:)
  • Configure engine systems
  • Define items, relationships, equipment slots, containers, skills
  • Set time configuration

Variable declarations

Global variables are declared with (declare:) in GameInit. The engine infers the type from the default value, and it's locked in for the whole game.

ana
:: GameInit @system(init)

// Batch flat namespace — declare many vars under one prefix at once:
(declare: $player,
  name, "",
  gold, 100,
  maxHealth, 100,
  reputation, 0,
  traits, [],
  events, [])

(declare: $world,
  timeOfDay, "morning",
  days, 0,
  events, [])
// $world.location is set automatically by the [location:id] passage tag —
// you don't declare or set it manually.

// Single var with bounds (auto-clamped on add/sub):
(declare: $player.health, 100, 0, 100)

Types:

  • "" → string
  • 0 or 100 → number
  • true / false → boolean
  • [] → array

You cannot change a variable's type after declaration. You can have as many namespaces as you need: $player, $world, $apartment, and so on.

Declaring the starting passage

Every GameInit must declare a starting passage with (start:):

ana
(start: MainStreet)

This tells the engine where to navigate after GameInit completes. It does not navigate immediately; navigation happens after the full passage executes.

Engine configuration

ana
// Declare the layout for game scenes
(layout: Layout_Standard)

// What shows in the header shortcut bar
(header-shortcuts: inventory, questlog)

// Which feedback categories show in the overlay (omit to show all)
(feedback-categories: "statchanges", "relationships", "items")

// Enable autosave every time (goto:) navigates
(save-autosave: true)

// Enable the story log (Debugger → Trace panel)
(story-log: enabled)

// Custom audio channels beyond the built-in four (bgm, sfx, ambiance, voice)
(audio-channel: "radio")

System definitions

Define your world's cast, items, containers, slots, skills, and time in GameInit:

ana
// Time periods (or use the defaults: morning/afternoon/evening/night)
(time-periods: "dawn", "morning", "midday", "afternoon", "dusk", "evening", "night")
(time-mode: cycle)

// NPC data
(npc-define: "bartender", name, "Old Bill", gender, "male", age, 58, location, "bar", events, [])

// Relationships — global defaults: apply to all $rel.* unless overridden
(rel-defaults: 0, -100, 100)
(rel-tiers: -50, "hostile", 0, "neutral", 50, "friendly", 80, "close friend")

// Stats — declare with bounds in one call (already done above for player.health)

// Skills — $skill.* auto-applies 0–100 bounds by default
(declare: $skill.persuasion, 0)
(declare: $skill.lockpicking, 0)

// Equipment slots
(equip-define: weapon)
(equip-define: armor)
(equip-define: ring, 2)

// Named containers
(container-define: "apartment_closet")
(container-define: "car_trunk")

// Items
(item-define: "beer", stackable, name, "Beer", type, "consumable", value, 5)
(item-define: "lockpick", stackable, name, "Lockpick", type, "tool", value, 8)
(item-define: "iron_knife", unique, name, "Iron Knife", type, "weapon", damage, 8, value, 25)

GameInitSave

A companion passage runs after save data is loaded. Use it to re-fire anything that doesn't serialize cleanly, such as active timers or reactive bindings:

ana
:: GameInitSave @system(init_save)

(trigger: @on(gameLoad))

Variables, Namespaces & Story Flags

Every piece of readable state in Ana has a $variable path.

Unified state access

PathExampleInline(get:)Write
Flat variable$player.gold(set:)
Nested variable$npc.bartender.name(set:)
Bare global$questComplete(set:)
Temp variable_roll(set:)
Relationship (default)$rel.bartender(add:) / (sub:)
Relationship (named dim)$rel.bruce.friendship(add: $rel.bruce, "friendship", n)
Inventory count$inv.beer(add: $inv, ...) / (remove: $inv, ...)
Equipment slot$equip.weapon(add: $equip.weapon, uid)
Container item count$container.chest.beer(add: $container.chest, ...)
Item template property$item.iron_knife.damage(item-define:) (init only)
Status active flag$status.poisoned(status-apply:) / (status-remove:)

All paths work the same way in expressions, conditions, and (get:):

ana
// inventory check inline
(if: $inv.beer >= 2)[You have enough beer.]

// status check inline
(if: $status.poisoned)[You are poisoned!]

// equipment check inline
Your weapon: $equip.weapon

// named relationship dimension inline
(if: $rel.bruce.friendship >= 50)[Bruce trusts you.]

// item property inline
Damage: $item.iron_knife.damage

Write API for game systems

$variable paths for game systems are read mirrors: they stay in sync automatically but are not directly writable via (set:). Use the dedicated macros to mutate them:

SystemMacro APIMirrored Variable
Inventory(add: $inv, "id", n), (remove: $inv, "id", n)$inv.id
Equipment(add: $equip.slot, uid), (remove: $equip.slot), (swap: $equip.slot, uid)$equip.slot
Containers(add: $container.id, "item", n), (remove: $container.id, "item", n)$container.id.item
Items(item-define: "id", key, val, ...) (definitions only)$item.id.key
Status(status-apply: "id"), (status-remove: "id")$status.id
Rel. named dim(add: $rel.id, "dim", n), (sub: $rel.id, "dim", n)$rel.id.dim

Limitations

  • $inv.templateId = stackable item counts only; $inv never holds a bare unique template id. Unique items are tracked by generated UID ($inv.item_2); (has:), (count:), (remove:), and (destroy:) also accept the unique template id and resolve to a matching carried instance, and (add: $inv, "id") on a unique template creates one automatically. (ids: $inv) returns the raw keys (template ids and/or instance uids).
  • $equip.slotName = UID of the first item in the slot, or "" if empty. For multi-capacity slots use (get: $equip.slot) to get the full list.
  • $item.templateId.key = template properties only. Per-instance overrides on unique items: (get: $item, uid, "key").

Reserved namespaces

NamespaceUse
$playerPlayer character stats, traits, events
$worldWorld state: time, location, story events
$npcPer-NPC properties (nested: $npc.bartender.name)
$relRelationship scores and named dimensions
$skillSkill levels
$questQuest state: $quest.<id> signals (see Quests; the value is a status or the current stage id)
$invInventory item counts (read mirror; write via (add: $inv, ...))
$equipEquipment slot contents (read mirror; write via equip macros)
$containerContainer item counts (read mirror; write via container macros)
$itemItem template properties (read mirror; populated by (item-define:))
$statusActive status flags (read mirror; write via status macros)
$linkLink interaction state: $link.choice is set by (link-choice:)/(link-cycle:) (auto-declared)

Note $rel.tiers is reserved, so don't name an NPC "tiers".

Two kinds of state. Most of these are read mirrors: you read $inv.beer, $equip.weapon, $rel.bartender, $quest.find_thief directly in prose and conditions, but you write them through verbs ((add:), (remove:), (quest-start:), …), never (set:). The engine keeps the readable mirror in sync. Plain variables ($player.gold, $world.days, your own $game.*) are the other kind, read and written with (set:)/(add:)/(sub:). When in doubt: if a system macro created it ((item-define:), (quest-define:), (equip-define:)), treat its mirror as read-only.

Temp variables

Temp variables (_name) live only for the duration of a passage execution; they're never saved and never carried between passages. Use them for intermediate calculations.

ana
(set: _roll to (random: 1, 20))
(if: _roll >= 15)[You succeed.]

Story flags: event arrays vs booleans

Story flags (things that have or haven't happened) should use event arrays, not individual boolean variables.

Don't do this:

ana
// GameInit
(declare: $flags.metBartender, false)
(declare: $flags.foundNote, false)

// Later
(set: $flags.metBartender to true)
(if: $flags.metBartender)[...]

Do this:

ana
// GameInit — event arrays are declared as part of batch declaration
(npc-define: "bartender", name, "Old Bill", ..., events, [])
(declare: $world, days, 0, ..., events, [])

// Later — push a string tag to record the event
(add: $npc.bartender.events, "met")
(add: $world.events, "found_note")

// Check with the contains operator
(if: $npc.bartender.events contains "met")[...]
(if: $world.events contains "found_note")[...]

Why:

  • One array declaration covers unlimited events, with no per-flag boilerplate
  • contains reads naturally as prose: (if: $player.events contains "met_kyle")
  • "Scoped" clear is just resetting the array: (set: $world.todayEvents to [])
  • Arrays serialize automatically, so there's no risk of forgetting a declaration
  • Individual $flags.* booleans accumulate and become impossible to track at scale

Use $ variables for things with real values: gold amounts, health, relationship scores, the player's name. Use event arrays for "did X happen."

Scope by namespace owner. Put events where they naturally belong:

  • $player.events: things the player has done
  • $npc.bartender.events: history with a specific NPC
  • $world.events: major story beats that never reset
  • $world.todayEvents: events that reset each day (via (set: $world.todayEvents to []) in @on(dayAdvance))