Skip to content

Config, Save, Keybinds & Passage Utilities

Engine-wide configuration, the save/load system, keybindings, and passage existence/utility queries.

Engine Configuration

Configuration macros are called once in GameInit (@system(init)) and control engine behavior for the entire game session.


All config macros at a glance

Every macro below is called from GameInit @system(init). Grouped by system, with the section that documents it in full.

SystemMacro(s)Sets up
Core(layout: LayoutName)The active layout template; see UI
Core(start: PassageName)The passage shown when a new game begins
Core(systems-enable:) / (systems-disable:)Turns systems on/off with dependency validation (dice, quests, achievements, skills, inventory, equipment, containers, status, shops, relationships, currency); see System Toggles
Variables(declare: ...)All variable registration (flat, batch, bounded, skills, per-NPC relationships); see Declaration Macros
Variables(npc-define: "id", ...)NPC entities (preferred over (declare: $npc.id, ...)); see NPC System
Variables(ns-nested: "name")A custom nested namespace (like npc); see Loops & Namespace Queries
Relationships(rel-defaults: ...) / (rel-tiers: ...)Global bounds/initial and tier labels for $rel.*; see Relationships
Skills(skill-range: min, max)Default bounds for $skill.* variables; see Skills
Items(item-define: "id", ...)Item templates (stackable or unique); see Items
Equipment(equip-define: slotName[, capacity])Equipment slots; see Equipment
Containers(container-define: "id")Named containers; see Containers
Time(time-mode:), (time-periods: ...), (time-calendar:), (time-format:)Day/period structure, calendar, and clock display; see Time
Dice(dice-range: modMin, modMax)The stat-modifier scale for (dice:); see Dice Rolling
Statuses(status-define: "id", ...), (status-duplicate: mode)Status effect templates and re-apply behavior; see Status Effects
Keybinds(keybind-define: "id", ...)Custom keyboard shortcuts; see Keybind System
Quests(quest-define: "id", ...), (quest-link-format: ...)Quest definitions and reward-link prefixes; see Quest System
Achievements(achieve-define: "id", ...)Achievement definitions and tiers; see Achievement System
Shops(shop-define: "id", ...), (shop-add: ...)Shop merchants/markup and stocked items; see Shop System
Currency(currency-define: ...)Named currencies; see Currency System
Theme(theme-define: "id", "Label"), (theme: "id")Available themes and the default/active one; see Theme System
Transitions(default-transition: key, "value"[, durationMs])Default passage/text transition effects; see Transitions
Media(video-defaults: flag, bool, ...)Default (vid:) playback flags; documented below
UI / Feedback(header-shortcuts:), (feedback-categories:), (feedback-default:), (notify-position:), (feedback-position:), (save-autosave:)Header bar, feedback overlay position/filtering, and autosave toggle; documented below
Audio / Accessibility(audio-channel: name), (font-scale: N)Extra audio channels and UI text scale; see Audio / Accessibility

header-shortcuts

(header-shortcuts: label, label, ...)

Registers labels for shortcuts shown in the header bar.

ana
(header-shortcuts: "Inventory", "Quests", "Map")

video-defaults

(video-defaults: flag, bool, ...)

Sets the engine-wide default playback flags for (vid:), as comma-pairs. Keys: loop, muted, autoplay, controls — each takes a boolean. Any flag a (vid:) call omits falls back to these defaults. Out of the box: loop on, muted on, autoplay on, controls off (autoplay requires muted in most browsers).

ana
(video-defaults: loop, false, controls, true)  // most clips: play once, with controls

feedback-categories

(feedback-categories: category, category, ...)

Limits the feedback overlay to the listed categories. If omitted, all feedback is shown. (Per-call custom messages, feedback, "…", always show regardless of this filter.)

ana
(feedback-categories: "statchanges", "relationships", "items")

feedback-default

(feedback-default: category, on|off, ...)

Turns the default feedback message for a category on or off, in alternating category/state pairs. Off means mutation macros in that category stop emitting their automatic message, but a per-call feedback, "message" override still shows. Use this to globally quiet a noisy category without sprinkling feedback, false on every call.

ana
(feedback-default: "statchanges", off)            // silence automatic stat messages
(feedback-default: "items", on, "statuses", off)  // multiple pairs

Feedback behavior table: what emits a message by default, and in which category:

ActionEmits by default?Category
(add:) / (sub:) numeric variableYes, "name ±N → value"statchanges
(add:) / (sub:) on $rel.*Yes, "±N relationship with id"relationships
(add: $inv, …) / (remove: $inv, …)Yes, "+N id" / "-N id"items
(add: $container, …)NoNone
equip displacement / unequip-to-inventoryYes, "Unequipped: name"items
(destroy: …)NoNone
(status-apply:) / (status-remove:)Yes, "Name applied/removed"statuses
(pay:) / (earn:)Yes, "Spent/Received: amount"items
(shop-buy:) / (shop-sell:)Yes (success + failure reasons)items
quest state changesYes, "Quest …: title"quests

Every macro in this table accepts a trailing feedback, "custom message" (replace) or feedback, false (silence) pair as its last arguments. Custom messages support $var interpolation and bypass the category filters above.

ana
(earn: 50, feedback, "He flips you a $50 bill.")
(status-apply: "poison", feedback, false)

notify-position

(notify-position: "position")

Sets the screen position of the notification stack. Valid positions: top-left, top-right (default), bottom-left, bottom-right, top-center, bottom-center. An unrecognized value falls back to top-right. Call in GameInit (or any time; it applies to subsequent notifications).

ana
(notify-position: "bottom-right")

feedback-position

(feedback-position: "position")

Sets the screen position of the feedback overlay. Same valid positions as (notify-position:); default is top-left.

ana
(feedback-position: "top-center")

save-autosave

(save-autosave: true/false)

Enables or disables autosave. When enabled, the engine writes a session save on every (goto:). Default: disabled.

ana
(save-autosave: true)
(save-autosave: false)

debugger

(debugger: enabled/disabled/cheater)

Controls the developer debugger (the panel toggled with the backtick key) in the built game. Call in GameInit.

ModeEffect
enabledDefault. The debugger is available, including in the production build.
disabledThe backtick toggle does nothing; players cannot open the debugger.
cheaterThe debugger is available, but changing any variable through the Eval panel flags the playthrough and disables all further achievements. The first time the evaluator is opened, the player is warned. Reading values never flags anything.

The cheater flag is part of the save data, so a flagged playthrough stays flagged; loading a save made before the cheat clears it, and a new game starts clean. The debugger is bundled into every build (so a single distributable file can be tested and modded); disabled keeps it inert rather than stripping it.

ana
:: GameInit @system(init)

(debugger: cheater)

Save


save

(save: slot)

Saves to a numbered slot (1–9). User-created saves should use slots 1 and above.

Slot 0 is the autosave slot. The engine writes to slot 0 automatically when autosave is enabled (see (save-autosave:) below). Do not write to slot 0 manually from game passages; it is reserved for engine-managed autosaves.

ana
(save: 1)
(save: 2)

load

(load: slot)

Loads from a slot. Triggers GameInitSave @system(init_save) after loading.

ana
(load: 1)

save-delete

(save-delete: slot) deletes the save in the given slot.

ana
(save-delete: 2)

save-list

(save-list:) returns an array of slot metadata for building a save UI.

ana
(save-list:)

save-export

(save-export:) downloads the current game state as a JSON file. Slots live in the browser's storage for one origin, so they don't travel with the player; an exported file does. Use it for backups, moving a game between devices or browsers, or attaching a save to a bug report.

ana
(link: "Export save")[(save-export:)]

save-import

(save-import:) opens a file picker for a previously exported JSON save and restores the game from it, running the same version-migration and corrupt-save handling as (load:). A file that is not valid JSON, or not an Ana save, is rejected with a notification.

ana
(link: "Import save")[(save-import:)]

game-new

(game-new:) resets all systems and starts a fresh game (runs @system(init) → the (start:) passage). Its opposite is (game-end:).

game-end

(game-end:) unloads the current game (resetting runtime state and systems, clearing the session, and restoring the layout your GameInit set with (layout:)) and returns to the @system(title) passage, so the title renders clean, with no stale clock or location. Neither macro confirms; pair (game-end:) with (confirm:) for a "Back to Title" prompt.

ana
:: _Engine_NewGame [action]
(game-new:)

// "Back to Title" with a confirmation prompt:
:: _Menu_BackToTitle
(confirm: $world.confirmReturn, "Return to the title screen? Any unsaved progress will be lost.", "Yes, quit", "Cancel")[
    (if: $world.confirmReturn)[(action: _Menu_QuitToTitle)]
]

:: _Menu_QuitToTitle [action]
(game-end:)

Autosave configuration (in GameInit)

ana
(save-autosave: false)          // disabled
(save-autosave: true)           // fires on (goto:) only (default)

Keybind System

Engine-level keyboard shortcuts. Players can remap them (overrides saved in localStorage). Keybinds are not saved in the game save; they are per-browser accessibility config.

The engine ships no default keybinds. Every binding comes from your (keybind-define:) calls, so the keybind list (and the player's rebind screen, which is driven by (keybind-ids:)) contains exactly the screens your game uses. A game that disables achievements never defines an achievements binding, and none appears.

Escape always closes the top-most open modal regardless of keybind configuration; this is hardwired behavior and cannot be disabled, so players are never stranded in a modal even with zero keybinds defined. To open a menu with Escape, define a menu binding yourself.

A typical GameInit keybind block for a game using the standard UI templates:

ana
(keybind-define: "menu",         key, "Escape", passage, "GameMenu")
(keybind-define: "inventory",    key, "i",      passage, "InventoryScreen")
(keybind-define: "equipment",    key, "e",      passage, "EquipmentScreen")
(keybind-define: "quests",       key, "q",      passage, "QuestLog")
(keybind-define: "achievements", key, "a",      passage, "AchievementsScreen")

keybind-define

(keybind-define: "id", key, "key", passage, "PassageName")

Declares a keybind. Call in GameInit. The id is the binding's stable name; calling (keybind-define:) again with the same id overwrites that binding; a different id adds a separate one. Pick a meaningful id (it becomes the player-facing label in the rebind screen).

Optional kwarg action, "goto" navigates rather than opening a modal. Defaults to "modal".

ana
(keybind-define: "inventory", key, "i",      passage, "InventoryScreen")
(keybind-define: "menu",      key, "Escape", passage, "GameMenu")
(keybind-define: "map",       key, "m",      passage, "WorldMap", action, "goto")

keybind-disable

(keybind-disable: "id")

Disables a keybind without removing it. Useful for temporarily suppressing a shortcut (e.g., inventory disabled in combat).

ana
(keybind-disable: "inventory")

keybind-rebind

(keybind-rebind: "id", "newKey")

Remaps a keybind to a new key. Saves the override to localStorage so it persists across sessions. Intended for player-facing settings screens.

ana
// In a settings action passage:
(keybind-rebind: "inventory", "k")
(notify: "Inventory key rebound to K.")

keybind-ids

Expression macro. Returns an array of all defined keybind IDs. Use in settings screens to build a rebinding UI without hardcoding IDs.

ana
(each: _id in (keybind-ids:))[
    _id, current key: (print: (keybind-key: _id))
]

keybind-key

(keybind-key: "id")

Expression macro. Returns the effective key string for a keybind: the player's remapped key if set, otherwise the author default.

ana
Inventory is bound to: (print: (keybind-key: "inventory"))

Behavior rules

  • Form element focus: keybinds are suppressed when an <input>, <textarea>, or <select> element is focused.
  • Modal open: when a modal is open, all keybinds except Escape are suppressed. Escape always closes the top modal (hardwired, independent of any keybind).
  • No engine defaults: a keybind exists only after GameInit calls (keybind-define:) for it. There are no built-in bindings, so a game gets shortcuts only for the screens it declares.

Passage Utilities


exists

(exists: "PassageName"). Expression macro. Returns true if a passage with that name exists in the game. Use to gracefully degrade when optional content (like a mod passage) might not be present.

ana
(if: (exists: "DartsMod_Game"))[
    (link: "Play darts")[(goto: DartsMod_Game)]
]
(if: (exists: "SecretRoom"))[You notice a hidden door.]

Aliases: (passage-exists:)


log

(log: value, "level")

Writes a value to the browser developer console, prefixed with [Ana]. The optional second argument sets the level: debug, info (default), warn, or error. A debugging aid; it produces no in-game output.

ana
(log: $player.gold)                 // info level
(log: "reached the docks", "debug")
(log: $quest.find_thief, "warn")

trigger

(trigger: eventName)

Manually fires an event hook, causing any @on(eventName) passages to execute. The preferred form passes the event name as a string, which is also how you fire custom mod events. An @on(...) directive form is accepted as an equivalent for the built-in events.

ana
(trigger: "dayAdvance")          // preferred: string form (works for custom events too)
(trigger: "mymod.boss_defeated") // custom event
(trigger: @on(gameLoad))         // accepted equivalent for built-in events

(modal-open: screenName) opens a modal overlay (inventory screen, character sheet, etc.). Pauses passage execution. See (modal-close:) to dismiss it; full detail in Modals.

ana
(modal-open: inventory)
(modal-open: questlog)

(modal-close:) closes the top modal overlay, returning to exactly the state before it opened.

ana
(modal-close:)

Mods

The engine populates a reserved $mods namespace at boot, keyed by mod name, so a game can react to which mods are present. Each entry exposes author, version, requires, conflicts, passages (names of the mod's passages), wraps (passages it @wraps), and into (hooks it @intos).

ana
(get: $mods, "Extra Outfits", "version")   // "1.2.0"
(each: _hook in (get: $mods, "Extra Outfits", "into"))[ … ]

$mods is engine-managed; don't (declare:) into it. mods is also a reserved namespace name.


mod-list

Returns an array of the names of all loaded mods (the ids in $mods).

ana
(each: _m in (mod-list:))[
    Loaded: _m
]

mod

(mod: "Mod Name")

Returns true if a mod with that name is loaded, for optional, mod-aware content.

ana
(if: (mod: "Extra Outfits"))[
    You see an extra rack of clothes against the wall.
]