Appearance
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:
""→ string0or100→ numbertrue/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
| Path | Example | Inline | (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.damageWrite 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:
| System | Macro API | Mirrored 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;$invnever holds a bareuniquetemplate 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
| Namespace | Use |
|---|---|
$player | Player character stats, traits, events |
$world | World state: time, location, story events |
$npc | Per-NPC properties (nested: $npc.bartender.name) |
$rel | Relationship scores and named dimensions |
$skill | Skill levels |
$quest | Quest state: $quest.<id> signals (see Quests; the value is a status or the current stage id) |
$inv | Inventory item counts (read mirror; write via (add: $inv, ...)) |
$equip | Equipment slot contents (read mirror; write via equip macros) |
$container | Container item counts (read mirror; write via container macros) |
$item | Item template properties (read mirror; populated by (item-define:)) |
$status | Active status flags (read mirror; write via status macros) |
$link | Link 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
containsreads 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))