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.
Key Type Default Effect
itemsarray — Required. Choice entries
stylestring or { "source" } game / choice_style Button pack → themes/<name>.json
shufflebool from layout.choices → false Deterministic reshuffle. Forbidden when mode is free
show_numbersbool from layout → true Prefix 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.
Key Type Default Effect
itemsarray of objects — Choice entries
containerobject {}Group presentation — container
itemobject {}Defaults for each item root (layout, style, interaction, …)
stylestring or { "source" } see precedence Button element pack name. Empty "" falls through to game
sourcestring — Pack compose → themes/<name>.json. Marks group pack-backed (sprite inheritance)
shufflebool layout → false Display shuffle. Forbidden with free
show_numbersbool layout → true Number 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 .
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 } }
Key Type Default Effect
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.
Allowed roots: layout, style, transition, shader, audio, interaction, source.
Key Type Default Effect
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_ynumber 0Nudge 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 ≥ 0 from layout.choices.gap Stack item separation. Forbidden in free
Rule stackfree
Item placement VBox + align_h Absolute x / y
container.layout.gapAllowed Forbidden
shuffleAllowed Forbidden
Item align_h Allowed Forbidden
Item x / y Forbidden Allowed
Key Type Effect
sourcestring Pack pull for the plate
bg_colorcolor string Plate fill
bg_texturetexture spec Plate texture
border_colorcolor string Border colour
border_widthnumber Border width
corner_radiusnumber Corner radius
layout / shader / interactionobject Nested same shapes as container roots
style_key is rejected — use source.
Key Type Effect
kindtransition kind Default kind
durationnumber ≥ 0 Seconds
on_show / on_hidetransition kind Enter / exit
transition / transition_in / transition_outtransition kind Legacy aliases
transition_durationnumber ≥ 0 Legacy 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 .
Key Type Effect
on_show / on_hidestring (sfx name) Group show / hide sound
Key Type Effect
focusablebool Group focus hint
hooksobject on_show, on_hide → action payloads
hooks_overrideobject Override or clear hook payloads
Not copied onto items.
Key Type Default Effect
namestring — Shader stem
paramsobject {}Uniforms
target"bg" | "full""bg"Apply target
pass"direct" | "subviewport""subviewport"full + direct forbidden
marginnumber or sides object 0Expand host
String pack ref; deep-merged under the container.
Each entry in items:
Key Type Default Effect
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 omit NEXTJump 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
rulesobject visible + enabled Visibility
layoutobject from item / game Item layout
styleobject from item / game Item 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)
interactionobject focusable true Hooks, hotkey, hit_test
state_visualobject — Per-state look / animation
sourcestring — Per-item pack compose
debugany — Passthrough
Menu node exit_commands (if any)
Item set
Item commands
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.
Key Type Effect
show_ifexpression string Hidden when falsy
disable_ifexpression string Shown but disabled when truthy
Key Type Mode Effect
align_h"left" | "center" | "right"stack only Horizontal align
x / ynumber free only Absolute position
width / heightnumber ≥ 0 both Item size; omit in sprite for native face size
rotation_degreesnumber both Rotation
Key Type Effect
sourcestring Element pack pull
text_colorcolor string Caption colour (sprite packs often transparent)
icon / hover_icon / pressed_icon / focus_iconpath string Glyph + text; ignored when sprite theme applies
icon_max_widthnumber Icon width cap
spriteobject Only when group render_mode is "sprite"
Key Type Effect
normal / hover / focus / pressed / disabledtexture spec State faces
scale_mode"stretch" | "fit" | "cover" | "keep"Face scaling
Key Type Effect
kind / durationkind / number ≥ 0 Defaults
on_show / on_focus / on_confirmtransition kind Event kinds
Key Type Default Effect
hotkeystring — Custom action; not reserved (choice_1…choice_5, choice_prev / next / cycle / confirm, dialogue_advance); unique per node
focusablebool trueFocus 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
Key Type Effect
normal / hover / focus / pressed / disabledentry object Per-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.
Keyword Resolves 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 .
Condition Inherits from layout.choices
render_mode: "sprite" without choices.sourceNo button chrome — author container + item style.sprite on the node
Sprite with choices.source Pack supplies chrome; pack item is the item base
Either shuffle / 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).