# 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 in `WIDGET_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? | `GameHandler`, VM/story handlers | | Interaction model | What action shape is requested? | journal fragments and choice `accepts` | | 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 visible `piece` selections through prompts - submit the same `choice_id` plus payload that a rich widget submits - optionally submit raw command text to a reserved interpretation choice 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: ```text 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. ### 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: | `accepts.kind` | Payload | CLI rendering | | --- | --- | --- | | `pick` or absent | `{}` or no payload | numbered choice | | `text` | `{text: string}` | line prompt with optional validators | | `quantity` | `{quantity: int}` | integer prompt with min/max and reason text | | `pieces` | `{piece_ids: string[]}` | numbered entries from a visible target zone | | `raw_command` | `{text: string}` | command prompt submitted to interpretation edge | Later, `compose` can combine those simple parts: ```text 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: ```text { 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: 1. The runtime emits ordinary visible choices for the turn. 2. If raw command input is authorized, the runtime also emits a reserved choice such as `edge_id="interpret_command"` with `accepts.kind="raw_command"`. 3. A client may render that choice as a command bar or as a CLI prompt. 4. A capable client may use advisory grammar hints for autocomplete, preview, and piece highlighting. 5. On submit, the backend resolves or rejects the command and returns a normal `RuntimeEnvelope`. 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 first web-client hint shape is deliberately advisory: ```text metadata: grammar: examples: ["take lamp", "open door"] verbs: ["take", "open"] nouns: ["lamp", "door"] ``` Clients can use this for placeholders or autocomplete. A CLI or minimal client can ignore it and submit raw text to `interpret_command`. When a raw command does not advance the story, the backend should return a renderable feedback fragment. A future `interpretation` fragment can cover: - `ambiguous` - `unknown_verb` - `unknown_noun` - `blocked` - `impossible` - `validation_failed` A client without a dedicated interpretation renderer can show the message as ordinary content. ### 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 raw text to `interpret_command` 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 interpretation 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: 1. Keep `zone` and `piece` widgets small and generic; do not add game-specific board logic to the client. 2. Add `ChoiceInputView` for `pick`, `text`, `quantity`, and `pieces`. 3. Add canonical fixtures for a quantity interaction and a small sandbox-like turn with visible room/inventory zones. 4. Keep raw command input as a reserved `raw_command` choice that submits `{text}` to the backend; grammar hints are optional presentation metadata. 5. Add `interpretation` rendering after the backend shape exists; fallback to content is acceptable before then. 6. Add `compose` after the simple payload widgets are stable. 7. 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 `BaseFragment` and `tangl.journal.fragments`. - No hard dependency on one UI framework's widget taxonomy. - No requirement that every client render every rich surface.