MeridiansMeridians

Scenario — the game loop [Flow]

From the Meridians Wiki · Public · Maintained · joint

A game is a branch; a GameRoom runs the turn loop as a phase machine. The player-facing loop is Brief → Prepare → Commit → Consequence. Generation alternates with deliberate player action: the engine briefs the seat, the seat prepares its reading, the seat commits, then the world plays the consequence forward. Structural phase identifiers remain read → write → play → resolution → graph → arc → scoring → game-theory for persisted state and protocol compatibility. Vocabulary: LANGUAGE.md. The screen layout is in ui/scenario-ui.

The turn state machine

stateDiagram-v2
    [*] --> Setup
    Setup --> Turn: computer mode · Director proxies all seats

    state Turn {
        [*] --> PerspectiveGen
        PerspectiveGen --> Read: Brief — public + private views (off-clock)
        Read --> Write: Prepare — open Streams + add Priors
        Write --> StreamGen: preparation closes (write clock)
        StreamGen --> Play: GEN — candidate Streams seeded · cards priced + dealt
        Play --> Showdown: Commit — back moves with conviction (play clock)
        Showdown --> ArcGen: reveal + realism verdicts · Director vetoes / dictates
        ArcGen --> FateScoring: GEN — Merge + continuation · settle
        FateScoring --> GameTheoryReview: Impact lands
        GameTheoryReview --> PerspectiveGen: Strategy read · next round
    }
    Turn --> [*]: Director ends → Report
  • Generation alternates with player phases. Perspective Gen writes the public account + each seat's private view off the canon (players never see canon; the Director sees all). Stream & Intuition Gen seeds each seat's candidate streams, then prices and deals the hand. Arc Gen folds the Merge into the continuation, then settles (decay→income) and scores.
  • Streams are perspective-owned — one per seat, no shared belief pool. Prepare poses questions + adds Priors; a Prior that moves a Thread your way cheapens its cards (−ln p falls) and earns conviction.
  • The Merge is the only place separate seats' streams meet. Each contested thread settles per RESOLVE_BIASrandom (seeded draw over conviction-shaped odds) · highest-cost (rarest action forced through) · realism (impartial AI judge). A universal realism pass then interprets reality around the chosen winner, producing a Director-editable telling · reasoning · closure that rides onto the merge and is injected into the continuation prompt.
  • Attribution is intrinsic: each Arc Gen decomposes the realized stance shift per Thread — Aumann–Shapley on Fate/KL, conserving exactly — into running Impact plus neutral External. Goals are optional personal trackers that never affect attribution.

The engine data-flow

useScenario.advance() is the one-click progression: per phase it runs the right pure-engine side-effects + AI calls, then commits to the store.

flowchart LR
    Adv["advance() — one click per phase"]
    Adv -->|read| AP["generateArcPerspective"]
    Adv -->|write| GS["game-streams — seed + deal"]
    Adv -->|play| AG["game-agent — LLM turn"] --> ECO["economy — cost / settle"]
    Adv -->|play→showdown| DC["detectConflicts"] --> SET["settlement — draw / rule pick"]
    SET -->|winner per group| CR["game-realism — impartial judge"]
    CR -->|telling · reasoning · closes| Merge["MergeResolution (auditable)"]
    Adv -->|resolve / Arc Gen| Merge --> GEN["generateScenes → BULK_ADD_SCENES"]
    GEN --> SCO["scoring — Aumann–Shapley"]
    AP & GS & SCO & ECO --> Store(["store — UPSERT_GAME_ROOM ·<br/>CREATE_MERGE · COMMIT/CLOSE_STREAM"])
  • Showdown is the Realism review. The impartial judge (ai/game-realism) explains why one action prevails and what happens next directly beside the revealed cards. The Director may set another winner or open Refine consequence to steer and re-run the interpretation without leaving the table.
  • Fast commitment, deliberate consequence. Card choice, seat-Stream seeding, perspective delivery, and Impact attribution use no thinking budget. Reasoning is reserved for durable judgement and authored output: conflict/Realism settlement, the causal graph + continuation Arc, and the Strategy read.
  • Pure engine (lib/client/game/): engine (phase machine + deal) · economy (pricing/settle) · scoring (Impact) · settlement (contested draws) · agent (deterministic fallback) · attribution. AI (lib/engine/ai/): game-narration · game-streams · game-agent · game-conflicts · game-realism · game-cast.

Shipped boundary: joined /scenario clients and Showdown use the same canonical GameRoom as the Director studio. The blind director and lowest-cost biases were removed; Realism plus Director editing is the supported consequence path.

Driving a game headless (MCP)

The same table is fully drivable over MCP — a Director (or an AI seat) runs the loop with no studio open. The headless surface splits the one-click studio advance() into ops that mark where the AI must run, because a headless caller cannot silently do a generative step wrong. The full loop and its parts (all Scenario-group ops, in ops catalog):

flowchart TD
    create["**create-game**<br/>stand up the table · mints a guest-pass<br/>token per non-director seat (`passes`)"]
    create -->|affordance| rr1["read-scenario<br/>see the table + each seat's pass"]
    create --> play

    play["**game-play-round** — the player half, on-rails<br/>deliver perspectives · seed + deal ·<br/>auto-drive every AGENT seat"]

    play -->|"`stoppedFor: seatId`<br/>(a human/guest is up)"| cmd["**game-command** — one sync move<br/>play · endTurn/lockIn · chat · move ·<br/>streamEdit · table controls"]
    cmd -.->|"resume the rails"| play

    play -->|"`stoppedFor: null`<br/>`readyToResolve: true`"| resolve["**game-resolve** — the world half<br/>settle contests · merge-CRG · continuation<br/>arc · score Impact · open next round"]

    resolve -->|affordance| arc["read-arc<br/>the continuation the resolve wrote to canon"]
    resolve -->|"next round open (`read`)"| play

    play -.->|"lower-level alternative"| adv["game-advance — ONE phase transition<br/>(read→write deal → write→play)"]
    adv -.->|"generative continuation<br/>is studio-only"| studio(["`needsStudio` — no-op<br/>RESOLVE→GRAPH→ARC not run headless"])
  • Two ways to walk a round. game-play-round is the on-rails driver — one call runs a round's whole player half (perspectives → deal → every agent seat's play) and stops at the first human seat (stoppedFor); an all-agent table plays its player-half in a single call. game-advance is the lower-level one-phase-at-a-time transition that mirrors the studio's phase machine. Both are generative on the READ/WRITE deal; both refuse the continuation.
  • game-command is the only non-generative driver — one synchronous GameCommand through the pure gameReducer (play a card, endTurn/lockIn, chat, move, streamEdit, setContested/veto/addSeats/endGame/…). applied:false means it was a no-op (wrong phase, not the seat's turn, insufficient conviction). It is how a GM makes the human move at a stoppedFor stop, then hands back to game-play-round.
  • game-resolve is the world half, headless — it runs the SAME full pipeline the studio's Arc Gen does (settle → merge-CRG → continuation arc → Impact scoring → economy) and commits the SAME artifact set (CREATE_REASONING_GRAPH · BULK_ADD_SCENES · CREATE_MERGE · COMMIT/CLOSE_STREAM · SET_ARC_SCORE_FEEDBACK), then opens the next round. noContinuation:true settles + scores without the graph/arc.
  • Ending early closes the unfinished questions. endGame closes every still-open Stream owned by a participating seat on that branch, including a card already played but not yet folded into a Merge. It preserves the Stream in the record as closed rather than deleting the unfinished Scenario evidence.
  • The needsStudio boundary is deliberate. Only game-play-round/game-resolve run the generative continuation headless; a bare game-advance from play/resolution/… returns needsStudio rather than doing it wrong. Prefer game-play-round → game-resolve for a fully headless game.
  • Headless commits echo live. Every op commits UPSERT_GAME_ROOM on the record stream, so an open studio and any joined /scenario client (via a minted guest-pass token) update in real time — the same single-writer path as the studio.

The studio and MCP are the same room, one writer: game-play-roundadvance()'s READ+WRITE+PLAY player half, game-resolve ≈ its Arc Gen world half. See the MCP practice in mcp-chains.

Scenario closes the shipped play loop; it does not yet close the cross-session training loop. The product-level Gameplay loop preserves this state machine as the inner loop and maps the missing Consequence → Debrief → Adapt → next Brief bridge.