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 optional systems (dice, quests, achievements, skills) on/off; 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-periods: ...), (time-mode:), (time-start:), (time-calendar:), (clock-format:), (date-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
UI / Feedback(header-shortcuts:), (feedback-categories:), (feedback-default:), (notify-position:), (feedback-position:), (save-autosave:), (story-log:)Header bar, feedback overlay position/filtering, and autosave/story-log toggles; documented below
Audio / Accessibility(audio-channel: name), (font-scale: N)Extra audio channels and UI text scale; see Audio / Accessibility

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

Registers labels for shortcuts shown in the header bar.

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

(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: 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: "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: "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: 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)

(story-log: enabled/disabled)

Enables the player-facing story log: a scrollable modal listing visited passages in reverse order, opened with the configured hotkey (default J).

ana
(story-log: enabled)
(story-log: disabled)

(story-log-data:)

Expression macro. Returns the display trace as a reversed array (most recent passage first) for use in the _Story_Log passage. Only meaningful when (story-log: enabled) is set.

ana
:: _Story_Log

@zone(modal)
(each: _entry in (story-log-data:))[
    _entry
]
(link: "Close")[(modal-close:)]

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: slot)

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

ana
(load: 1)

(save-delete: slot) / (save-list:)

ana
(save-delete: 2)
(save-list:)     // returns array of slot metadata for building a save UI

(game-new:) / (game-end:)

(game-new:) resets all systems and starts a fresh game (runs @system(init) → the (start:) passage). (game-end:) does the opposite: it 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. Authors declare default bindings; players can remap them (overrides saved in localStorage). Keybinds are not saved in the game save; they are per-browser accessibility config.


Pre-registered engine defaults. The following keybinds are always active at engine boot. Authors do not need to register these; they can be overridden by calling (keybind-define:) with the same id in GameInit:

IDDefault keyTarget passageNotes
menuEscapeGameMenuAlways closes an open modal first
inventoryiInventoryScreenOpens as modal
story-logj_Story_LogOnly active if story log is enabled
achievementsaAchievementsScreenOpens as modal
questsqQuestLogOpens as modal

Escape additionally always closes the top-most open modal regardless of keybind configuration; this is hardwired behavior and cannot be disabled.


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

Declares a keybind. Call in GameInit. The engine pre-registers 5 default bindings (see table above); calling (keybind-define:) with the same id in GameInit overwrites them.

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: "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: "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: "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.
  • Engine defaults: if GameInit does not call (keybind-define: "inventory", ...), the engine uses i → InventoryScreen. Same for "menu" → GameMenu. Override by calling (keybind-define:) with the same id.

Passage Utilities


(passage-exists: "PassageName") / (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. (exists:) is a shorter alias.

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

(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: 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

Opens or closes a modal overlay (inventory screen, character sheet, etc.). Pauses passage execution. Closing returns to exactly the state before the modal opened.

ana
(modal-open: inventory)
(modal-open: questlog)
(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 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.
]