Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Choices (story node)

A choice menu on a dialogue node. Script > lines compile into this object. Full presentation keys live here — the opening sketch is just the idea.

"choices": {
  "shuffle": false,
  "show_numbers": true,
  "style": "choice_button",
  "items": [
    {
      "text": "Take the key",
      "target": "vault",
      "set": { "has_key": true },
      "commands": [{ "type": "sfx", "value": "pickup" }]
    },
    { "text": "Leave", "target": "NEXT" }
  ]
}

Script sugar:

> Help her. -> help_branch
> Stay back. -> skip_branch
> Continue

Per-item set / commands are Mode B (JSON) only — not Mode A > sugar.

Usually you need

KeyTypeDefaultEffect
itemsarray—Required. Choice entries
stylestring or { "source" }game / choice_styleButton pack → themes/<name>.json
shuffleboolfrom layout.choices → falseDeterministic reshuffle. Forbidden when mode is free
show_numbersboolfrom layout → truePrefix N. on captions
containerobject{}Group layout / plate / host
itemobject{}Defaults merged into every item

That covers stacked button menus. Floating sprite picks and full chrome are below.

All group keys

KeyTypeDefaultEffect
itemsarray of objects—Choice entries
containerobject{}Group presentation — container
itemobject{}Defaults for each item root (layout, style, interaction, …)
stylestring or { "source" }see precedenceButton element pack name. Empty "" falls through to game
sourcestring—Pack compose → themes/<name>.json. Marks group pack-backed (sprite inheritance)
shuffleboollayout → falseDisplay shuffle. Forbidden with free
show_numbersboollayout → trueNumber labels
timerobject—Optional countdown — Timed choices

Not allowed on the group root: gap (use container.layout.gap or layout.choices.gap), top-level layout / transition / shader / audio / interaction (those live under container).

Style name precedence (button groups): node choices.style → active choice_style → layout.choices.style. See Styling model.

Timed choices

Optional choices.timer starts when the menu is shown. A player pick cancels it. On timeout, either jump elsewhere or auto-pick an item (that item’s set / commands run).

"choices": {
  "timer": { "seconds": 8, "on_end": "too_slow" },
  "items": [
    { "text": "Defuse", "target": "safe" },
    { "text": "Run", "target": "hall" }
  ]
}

Auto-pick (uses the chosen item’s body):

"timer": { "seconds": 5, "on_end": { "pick": 0 } }
KeyTypeDefaultEffect
secondsnumber > 0—Required
on_endstring or { "pick": int } or { "pick_id": string }—Timeout jump, or auto-pick by source index / item id
idstring__choice_<node_id>Timer id (saved with the session)

Mode B only — no Mode A sugar for choices.timer. Background timers use the timer command.

Container

Allowed roots: layout, style, transition, shader, audio, interaction, source.

container.layout

KeyTypeDefaultEffect
mode"stack" | "free""stack"Stack = vertical list; free = absolute item x/y
host"panel" | "overlay""panel"Sit in the panel choice slot vs full-screen overlay
render_mode"button" | "sprite""button"Group-wide. Sprite uses item style.sprite faces
anchor_h"left" | "center" | "right"overlay: "left"Overlay horizontal anchor
anchor_v"top" | "center" | "bottom"overlay: "top"Overlay vertical anchor
offset_x / offset_ynumber0Nudge the host
x / ynumber—Absolute group position when used
width / heightnumber ≥ 0—Group plate size only (not items)
min_width / min_height / max_width / max_heightnumber ≥ 0—Plate clamps
padding{ left, top, right, bottom }0Plate padding
gapnumber ≥ 0from layout.choices.gapStack item separation. Forbidden in free

Free vs stack

Rulestackfree
Item placementVBox + align_hAbsolute x / y
container.layout.gapAllowedForbidden
shuffleAllowedForbidden
Item align_hAllowedForbidden
Item x / yForbiddenAllowed

container.style

KeyTypeEffect
sourcestringPack pull for the plate
bg_colorcolor stringPlate fill
bg_texturetexture specPlate texture
border_colorcolor stringBorder colour
border_widthnumberBorder width
corner_radiusnumberCorner radius
layout / shader / interactionobjectNested same shapes as container roots

style_key is rejected — use source.

container.transition

KeyTypeEffect
kindtransition kindDefault kind
durationnumber ≥ 0Seconds
on_show / on_hidetransition kindEnter / exit
transition / transition_in / transition_outtransition kindLegacy aliases
transition_durationnumber ≥ 0Legacy duration

Kinds: instant, fade, slide_up, slide_down, slide_left, slide_right. Group-scoped only — not copied onto items. on_hide plays for host: "overlay"; a host: "panel" group leaves with the line change so the plate never keeps the old choice height. See Transitions.

container.audio

KeyTypeEffect
on_show / on_hidestring (sfx name)Group show / hide sound

container.interaction

KeyTypeEffect
focusableboolGroup focus hint
hooksobjecton_show, on_hide → action payloads
hooks_overrideobjectOverride or clear hook payloads

Not copied onto items.

container.shader

KeyTypeDefaultEffect
namestring—Shader stem
paramsobject{}Uniforms
target"bg" | "full""bg"Apply target
pass"direct" | "subviewport""subviewport"full + direct forbidden
marginnumber or sides object0Expand host

container.source

String pack ref; deep-merged under the container.

Items

Each entry in items:

KeyTypeDefaultEffect
idstring""Stable id (unique in the node); used by animate / choice_item:…
textstring—Caption. Need text or text_key
text_keystring—Catalog key (Localization)
targetstring or omitNEXTJump target — targets
setobject—Flags applied on pick before navigating (same as node set)
commandsarray—Commands run after set, before enter target. No jump / call / return / story input — use target to navigate. Meta when skips individual pick commands the same as enter batches
rulesobjectvisible + enabledVisibility
layoutobjectfrom item / gameItem layout
styleobjectfrom item / gameItem style
transitionobject—Item enter / focus / confirm FX
shaderobject—Same shader shape as container
audioobject—on_focus / on_confirm sfx names (prefer on_confirm for a one-off pick sound)
interactionobjectfocusable trueHooks, hotkey, hit_test
state_visualobject—Per-state look / animation
sourcestring—Per-item pack compose
debugany—Passthrough

On pick

  1. Menu node exit_commands (if any)
  2. Item set
  3. Item commands
  4. Enter target → that node’s set → its enter commands

Omit target (or "NEXT") to continue to the next node after the menu. For a loop that re-shows the same menu without re-typing a line, use a menu-only node (empty text + choices) and point target back at that node — not at a say+choices node.

Rules

KeyTypeEffect
show_ifexpression stringHidden when falsy
disable_ifexpression stringShown but disabled when truthy

Item layout

KeyTypeModeEffect
align_h"left" | "center" | "right"stack onlyHorizontal align
x / ynumberfree onlyAbsolute position
width / heightnumber ≥ 0bothItem size; omit in sprite for native face size
rotation_degreesnumberbothRotation

Item style

KeyTypeEffect
sourcestringElement pack pull
text_colorcolor stringCaption colour (sprite packs often transparent)
icon / hover_icon / pressed_icon / focus_iconpath stringGlyph + text; ignored when sprite theme applies
icon_max_widthnumberIcon width cap
spriteobjectOnly when group render_mode is "sprite"

style.sprite

KeyTypeEffect
normal / hover / focus / pressed / disabledtexture specState faces
scale_mode"stretch" | "fit" | "cover" | "keep"Face scaling

Item transition

KeyTypeEffect
kind / durationkind / number ≥ 0Defaults
on_show / on_focus / on_confirmtransition kindEvent kinds

Item interaction

KeyTypeDefaultEffect
hotkeystring—Custom action; not reserved (choice_1…choice_5, choice_prev / next / cycle / confirm, dialogue_advance); unique per node
focusablebooltrueFocus eligibility
hit_test"rect" | "alpha" | "polygon""rect"Hit mode; alpha / polygon need sprite render
hooksobject—on_show, on_focus, on_blur, on_confirm, on_hover_enter, on_hover_exit
hooks_overrideobject—Override or clear payloads

Item state_visual

KeyTypeEffect
normal / hover / focus / pressed / disabledentry objectPer-state look
fallback_orderstring[]Resolve order

Entry keys: source, shader (null to clear — unlike shader_params, which is kept when present), shader_params, animation or animations (not both). Animation keys: preset, property, to, from, duration (> 0), ease, trans, relative.

Targets

KeywordResolves to
NEXTNode next, else next array node
ENDStory end (null)
STARTPack entry node (start by default), else the first moment in the file

Omitted target = NEXT. Resolved at load. Avoid node ids named NEXT / END / START (keyword wins; warning). Name your opening moment :: start when New Game and START should agree — Scenes and jumps.

Sprite vs button inheritance

ConditionInherits from layout.choices
render_mode: "sprite" without choices.sourceNo button chrome — author container + item style.sprite on the node
Sprite with choices.sourcePack supplies chrome; pack item is the item base
Eithershuffle / show_numbers / gap fallbacks still apply if unset on the node

Example pack: examples/example-05-ui-and-polish/themes/choice_yes_no.json (free + sprite + overlay).