Appearance
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.
| System | Macro(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 controlsfeedback-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 pairsFeedback behavior table: what emits a message by default, and in which category:
| Action | Emits by default? | Category |
|---|---|---|
(add:) / (sub:) numeric variable | Yes, "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, …) | No | None |
| equip displacement / unequip-to-inventory | Yes, "Unequipped: name" | items |
(destroy: …) | No | None |
(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 changes | Yes, "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.
| Mode | Effect |
|---|---|
enabled | Default. The debugger is available, including in the production build. |
disabled | The backtick toggle does nothing; players cannot open the debugger. |
cheater | The 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
Escapeare suppressed.Escapealways closes the top modal (hardwired, independent of any keybind). - No engine defaults: a keybind exists only after
GameInitcalls(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 eventsmodal-open
(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
(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"))[ … ]
$modsis engine-managed; don't(declare:)into it.modsis 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.
]