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 pfalls) and earns conviction. - The Merge is the only place separate seats' streams meet. Each contested thread settles per
RESOLVE_BIAS—random(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
/scenarioclients and Showdown use the same canonicalGameRoomas the Director studio. The blinddirectorandlowest-costbiases 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-roundis 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-advanceis 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-commandis the only non-generative driver — one synchronousGameCommandthrough the puregameReducer(play a card,endTurn/lockIn,chat,move,streamEdit,setContested/veto/addSeats/endGame/…).applied:falsemeans it was a no-op (wrong phase, not the seat's turn, insufficient conviction). It is how a GM makes the human move at astoppedForstop, then hands back togame-play-round.game-resolveis 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:truesettles + scores without the graph/arc.- Ending early closes the unfinished questions.
endGamecloses 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
needsStudioboundary is deliberate. Onlygame-play-round/game-resolverun the generative continuation headless; a baregame-advancefromplay/resolution/… returnsneedsStudiorather than doing it wrong. Prefergame-play-round → game-resolvefor a fully headless game. - Headless commits echo live. Every op commits
UPSERT_GAME_ROOMon the record stream, so an open studio and any joined/scenarioclient (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-round≈advance()'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.