Voxta docs

Flags

State bits and variable conditions that gate actions, buttons, events and contexts.

Flags are named booleans that track the current state of a chat. Simple strings: is_thirsty, wears_hat, door_closed. You set them from actions, events, or scripts; you read them from conditions on actions and contexts.

A character with the flag wears_hat set will:

  • Have the wear_hat action filtered out (it's a no-op — they already wear it).
  • Have the {{ char }} is wearing a hat context render in every prompt.

That's the core loop. Everything else is variations.

Setting and unsetting

A flag is set by including its name. Prefix with ! to unset:

wears_hat           # set
!wears_hat          # unset

You can do both in the same effect — e.g. an action that takes off a hat and reveals a hairstyle might set !wears_hat, ponytail_visible.

Enum flags (mutually exclusive)

Some flags are mutually exclusive — a character can be standing OR sitting OR dancing, but not all three. Manually unsetting "all the other poses" every time you change is tedious. Voxta solves this with dot-syntax enum flags:

pose.standing
pose.sitting
pose.dancing

Setting pose.dancing automatically unsets pose.standing and pose.sitting. The pose. prefix means "all flags starting with pose. are mutually exclusive."

Unsetting enums

  • !pose.standing — unsets only pose.standing. Other pose.* flags untouched.
  • !pose — unsets all pose.* flags. Useful for resetting a category.

Flag conditions

Conditions are mini JS-like boolean expressions used on actions and contexts.

ExpressionMeaning
pose.standingActive only if pose.standing is set.
!pose.standingActive only if pose.standing is NOT set.
pose.standing && !door.closedBoth must hold.
hold.empty_bottle || hold.empty_glassEither holds.
(a || b) && !cUse parens to group.

Variable conditions

A condition can also read chat variables with $name, so you don't need a mirror flag for every number:

$gold >= 50 && !transit
$hp < 10 || wounded
$class == "mage"
$gold - $debt >= 0

A missing variable makes comparisons false (and !$name true). Writing a referenced variable re-evaluates whatever reads it, so button visibility updates on its own.

Labels are templates too, so a button can show the value: Bribe the guard ({{ variables.bribe_cost }}g).

Setting from a script

import { chat } from "@voxta";

export function trigger(e) {
  // Set a flag
  chat.setFlag('wears_hat');

  // Unset
  chat.unsetFlag('wears_hat');
  // Equivalent:
  chat.setFlag('!wears_hat');

  // Enum
  chat.setFlag('pose.sitting');  // auto-clears pose.standing etc.

  // Multiple at once
  chat.setFlags('wears_hat', 'pose.standing', '!is_drenched');

  // Check
  if (chat.hasFlag('wears_hat')) { /* ... */ }
}

See Scripting for the full chat-state API.

Expiring flags

Set a flag that auto-clears after a duration:

// Expires after 10 conversation messages
chat.setFlag('cooldown', { messages: 10 });

// Expires after 2 minutes of chat time
chat.setFlag('cooldown', { seconds: 120 });

Useful for cooldowns, "just happened" markers, and short-lived emotional states.

Patterns

Once-ever events

Use the Once checkbox on an action — Voxta auto-generates an invisible _action_name flag and an !_action_name condition. Fires once per chat.

State machines

Use enum flags for state machines. state.intro, state.investigation, state.confrontation — only one is set at a time, and transitions are explicit.

Location / slot navigation

A state machine where the state is where the user is. One enum flag holds the location, and every context, action and button is gated on it:

Flag:     places.lobby   (or places.backyard, places.corridor, …)
Context:  "{{ user }} is standing in the lobby…"    filter: places.lobby
Button:   "Go to the backyard"                       filter: places.lobby
          → sets places.backyard

Setting places.backyard clears places.lobby, so the lobby context and buttons vanish and the backyard's appear — one flag, one source of truth. See the Stage Editor for the visual side.

Looping events

Anchor an event's trigger to a flag the event itself sets, with a message gap. It fires, re-sets the flag, re-arms, fires again.

What's next

On this page