Interaction Vocabulary¶
Status: Directional contract for fragment-era interactivity Authority: Story owns interaction meaning; service transports fragments; clients render affordances and gracefully degrade unsupported widgets. Current render-contract authority:
STORYTANGL_WIDGET_VOCAB.md, with repo-current status tracked inWIDGET_CONTRACT_RECONCILIATION.md.
StoryTangl is not trying to turn every client into a general game engine. The engine and handlers own legality, state transitions, scoring, and hidden state. The client-facing vocabulary should instead describe what kind of player action is being requested, what state is legible, and how a renderer may collect a payload.
This note remains useful as interaction-design rationale. The unified widget
vocabulary supersedes it for portable fragment widgets, ProjectedState
section values, tier tags, and conformance fixture planning.
The current choice fragment is the minimal form of this contract. The broader
vocabulary below describes the features we are building toward as fixtures and
widgets grow beyond plain story choices.
Design Split¶
Do not collapse these four vocabularies:
Layer |
Question |
StoryTangl owner |
|---|---|---|
Rules model |
What is legal and what changes state? |
|
Interaction model |
What action shape is requested? |
journal fragments and choice |
Presentation profile |
What can this client render well? |
web, CLI, Ren’Py, future clients |
Narrative projection |
What does this mean in story terms? |
prose, dialog, outcome routing |
The renderer consumes interaction requests. It does not infer legal moves from rules and it does not execute rules locally.
Client Capability Floor¶
The gateway API is the only capability every client can be assumed to have:
the service-facing create/update/resolve-choice and info-read operations
described in docs/src/design/service/FRAGMENT_STREAM_CONTRACT.md. Every
interaction shape must therefore degrade to text, numbered choices, and simple
prompts.
The CLI floor is:
render prose and visible state as readable text
render choices in order, including locked choices and reasons
collect
text,quantity, and visiblepieceselections through promptssubmit the same UUID
edge_idplus payload that a rich widget submitsoptionally submit raw command text through a typed
find_edgerequest
Web, Ren’Py, Godot, or tabletop-like clients may render richer controls, but those controls are affordances over the same visible fragments. They are not a client-side rules runtime.
Core Terms¶
Surface¶
A surface is a renderable interaction area: a choice list, hand, board, document packet, piece pool, shop, map, status panel, or resource economy.
In the fragment stream, surfaces are usually represented by group fragments
plus member fragments. group_type="scene" is the current top-level shell;
future groups such as zone, hand, board, or packet should follow the
same id-reference pattern.
Entity¶
An entity is a renderable or targetable object: card, piece, document, die, credential, clue, actor, location, board cell, generator, or inventory item.
At the client boundary, entities should appear as fragments or group members
with small capability fields rather than as deep class hierarchies. A card-like
piece might carry kind, display_state, zone_ref, labels, and hints. The
handler still owns the live graph object.
Zone¶
A zone is a container or locus for entities: deck, discard, hand, field, bag, wallet, credential packet, shop, queue, board cell, or inventory.
Zones matter because many interactions are target-constrained:
accepts:
kind: pieces
min: 1
max: 1
constraints:
target_zone_ref: f-zone-player-hand
Decision legibility requires the referenced zone to be renderable in the current shell when the choice is open.
Affordance¶
An affordance is the user-facing action offer: select, inspect, reveal, play, discard, buy, spend, take, put, arrange, confirm, cancel, pass, stand, hit, deny, arrest, or ask.
Today, ChoiceFragment is the concrete affordance carrier. text, available,
blockers, accepts, and ui_hints describe how the client can present and
collect the action.
Altitude note — two senses of “affordance.” Here the word is the render-layer action offer: what the player sees they can do, carried by
ChoiceFragment. This is distinct from the VM open-edgeAffordanceprimitive in AFFORDANCE_MODEL.md, which is a broadcast requirement-bearing edge on the runtime graph. The two are related by projection — a bound VM affordance projects to anAction, which serializes to aChoiceFragment, which the client renders as the affordance defined here. Same word, three layers apart: that document governs how an offer comes to exist and bind; this one governs how a bound offer is presented and collected.
Move¶
A move is the handler-facing committed action. It is what resolve_choice or a
future interaction endpoint receives after the renderer collects any payload.
Keep the distinction sharp:
Affordance: “Show a card from your hand.”
Payload:
{piece_ids: ["rust-map-card"]}Move: handler-valid committed action derived from choice id plus payload
Procedure¶
A procedure is a structured interaction cycle: single choice, best-of-N contest, push-your-luck, inspection loop, drafting, bidding, trick, combat round, shop transaction, resource tick, puzzle attempt, or timed challenge.
Procedures are handler/runtime concepts. Clients should see their state through fragments: status, zones, pieces, choices, blockers, outcomes, and events.
Resource And Transaction¶
Resources are typed quantities: coin, health, time, action points, suspicion, influence, generator output, piece reserves, or production inputs.
Resource-loop interactions should expose ledgers and transactions as renderable state rather than burying them in prose. The handler owns arithmetic; the client shows current quantities, available purchases, unavailable reasons, and audit events.
Payload Contract¶
choice.accepts describes the payload shape requested by an open affordance.
It should stay explicit enough that a CLI can ask for the same value without a
special widget library.
Near-term accepted shapes:
|
Payload |
CLI rendering |
|---|---|---|
|
|
numbered choice |
|
|
line prompt with optional validators |
|
|
integer prompt with min/max and reason text |
|
|
numbered entries from a visible target zone |
Later, compose can combine those simple parts:
accepts:
kind: compose
parts:
- role: amount
accepts: {kind: quantity, min: 1, max: 7, unit: coin}
- role: target
accepts:
kind: pieces
min: 1
max: 1
constraints: {target_zone_ref: z-room}
The corresponding payload should remain explicit:
{
parts: {
amount: {quantity: 2},
target: {piece_ids: ["guard"]}
}
}
The client may enforce simple visible validators to avoid bad submissions, but backend validation is authoritative.
Command Resolution¶
Classic IF-style command input is a second affordance over the visible action surface, not a requirement that every client embed a language parser.
The preferred model is backend-authoritative and keeps exploratory command resolution separate from concrete action submission:
The runtime emits ordinary visible choices for the turn.
If raw command input is authorized, envelope metadata advertises advisory grammar hints.
A client may render a command bar or a CLI prompt.
A capable client may use the hints for autocomplete, preview, and piece highlighting.
The client submits
{find_edge: {kind: "command", command: "..."}}.The backend either resolves exactly one action and applies it, or returns the current envelope with a typed inline
UxEvent.
Grammar hints are optional and must be treated as denormalized convenience metadata derived from the visible turn surface. They must not contain hidden verbs, nouns, aliases, or targets.
The service-provided fallback is deliberately mechanical: it uses each visible choice label as an exact example and frame, takes the first word as the verb, and maps visible piece labels to piece ids. It leaves aliases and placeholders empty. Authored world metadata may provide richer aliases, templated frames, and placeholders without changing command authority.
The first web-client hint shape is deliberately advisory:
metadata:
grammar:
examples: ["take lamp", "open door"]
verbs:
- verb: take
aliases: [get]
frames: ["take {noun}"]
- verb: open
aliases: []
frames: ["open {noun}"]
nouns:
- noun: lamp
aliases: [lantern]
piece_ids: [lamp]
- noun: door
aliases: []
piece_ids: [iron-door]
Clients can use this for placeholders or autocomplete. A CLI or minimal client
can ignore it and submit raw text through the same find_edge request.
When a raw command does not advance the story, the backend returns an inline,
non-replayed UxEvent. The event’s type, severity, message, and details can
distinguish no match, ambiguity, rejection, or payload validation failure.
Because UX guidance is not narrative output, it lives in
RuntimeEnvelope.ux_events rather than the journal fragment stream.
Outcome¶
Outcome is broader than win/lose/draw. A client may need to render success, failure, partial success, mixed result, continue, abort, route chosen, or terminal state. Current game enums remain useful, but fragment-era clients need result fragments or user events that explain what became true.
Record¶
RoundRecord is the mechanics-specific form of a more general interaction
ledger. Future interaction records should preserve request id, selected move,
public before/after state, result, and notes so replay, diagnostics, generated
prose, and tests can share the same audit trail.
Interaction Classes¶
These classes describe effect and rendering expectations:
Class |
Meaning |
|---|---|
observe |
Reveal or present information without changing world state. |
inspect |
Examine a target; may reveal hidden fields or consume an action. |
select |
Choose one or more alternatives without necessarily committing. |
commit |
Irreversible or state-changing decision. |
manipulate |
Move, take, spend, place, arrange, buy, or discard. |
confirm |
Acknowledge a result or advance a phase. |
query |
Ask for explanation, hint, rule, or status. |
Plain story choices are commit or confirm affordances with no additional
payload. Card, piece, credential, and resource interactions add target and
constraint metadata.
Visibility¶
Hidden information needs a first-class presentation contract. Do not encode all non-public information as simply absent.
Useful visibility states include:
private to handler
known to narrator
visible to player
visible to owner only
inferable or hinted
revealed after commit
intentionally misrepresented
remembered in history but no longer on the surface
The fragment stream should expose only what the client may render. Narrative composition may know more than the renderer, but that knowledge must not leak through interaction payloads.
Graceful Degradation¶
Every rich interaction should degrade to choices plus readable state.
Examples:
A web card UI may render hand zones and selectable pieces.
A CLI may render the same zone as a numbered list and collect a piece id.
A CLI command prompt may submit a
find_edgecommand request without local grammar support.A Ren’Py view may show a menu plus a small status panel.
A client without zone support may show the unknown group fallback but should still let ordinary choices work.
ui_hints can request icons, hotkeys, widget families, or layout preferences,
but hints are advisory. accepts and blockers are the portable contract.
Mechanic Coverage Targets¶
The vocabulary should cover the current mechanics survey without creating a client-side rules runtime:
Classic sandbox IF: visible location state, exits, inventory, blocked actions, command text, and backend UX feedback.
Rock-paper-scissors: simultaneous or staged commit, opponent tell, dominance relation, round result, best-of-N scoring.
Blackjack/21/22: deck, hand zones, hidden dealer state, hit/stand, threshold and bust result.
Nim and piece games: shared pools, take/put quantity constraints, shrinking legal moves, terminal policy.
Bag RPS and bidding: commit a typed quantity bundle from a reserve.
Credentials/Papers-Please-like scenes: inspect documents, reveal mismatches, request search, pass/deny/arrest disposition.
Incremental/resource loops: wallets, generators, costs, production ticks, and transaction history.
These are fixture and widget targets, not a mandate to add parallel game logic to the client.
Near-Term Implementation Targets¶
The next web/client work should stay small and contract-driven:
Keep
zoneandpiecewidgets small and generic; do not add game-specific board logic to the client.Add
ChoiceInputViewforpick,text,quantity, andpieces.Add canonical fixtures for a quantity interaction and a small sandbox-like turn with visible room/inventory zones.
Keep raw command input as a typed
find_edgerequest, separate from UUID-addressed direct choice submission.Render failed or ambiguous command resolution from inline, non-replayed
RuntimeEnvelope.ux_events.Add
composeafter the simple payload widgets are stable.Add browser E2E only after payload widgets and command feedback settle enough that tests will not cement an interim UI shape.
Non-Goals¶
No universal rules DSL at the client boundary.
No client-side game handler or scoring runtime.
No new fragment hierarchy separate from
BaseFragmentandtangl.journal.fragments.No hard dependency on one UI framework’s widget taxonomy.
No requirement that every client render every rich surface.