Decision architecture — building up to the fork
From the Meridians Wiki · Public · Maintained · human-contract
Status: rough spec (direction). Not an implementation contract; nothing here ships until scheduled.
Companion to the visual-novel delivery (§3 owns the commitment contract — when a fork may be presented at all) and to budgeted fate exploration (which owns finding divergent candidates). This document owns the part neither covers: how the narrative is oriented so that a fork arrives as a cliffhanger — a moment the reader has been made to care about — rather than a menu that interrupts a scene.
Branching carries the fork into the Reading instrument, while production carries its asset and cost consequences. Systems owns everything below a fork — texture and accumulating picks, and the gates they later open — which is why the gate here can afford to be strict. Flags owns what a commitment is as a record once taken: its nature, the vantage it was taken from, and what it is later read against.
The distinction is the whole document:
A choice is only a choice when it moves the world (§3 of the delivery spec). A choice only lands when the world has already been made to lean.
A fork that qualifies structurally can still be inert. The reader's experience of consequence is produced before the choice screen, by the scenes that raise the question — and the instrument already carries typed vocabulary for exactly that curve, so this is measurable rather than a matter of taste.
1. The shipped dramatic vocabulary
Two typed layers already describe rise and release, and neither was built for this — which is why they are trustworthy here.
Thread events (ThreadLogNodeType, src/types/domain/thread.ts) classify what each scene did to an open
question:
| Types | What it means for a fork | |
|---|---|---|
| RISE | setup, escalation | the question is being loaded — a direction is planted, stakes deepen |
| FALL | resistance, complication | adverse pressure; the leader dips, a flip becomes thinkable |
| TURN | twist | the expected outcome is denied — the strongest cliff material there is |
| WIDEN | opening | a new possibility enters contention |
| HOLD | pulse | attention maintained, nothing expected |
| NARROW / RESOLVE | closure, payoff | the question closes — the one thing a cliffhanger must not do |
Scene beats (BeatFn, src/types/domain/beat.ts) classify how prose delivers each moment: advance,
turn, reveal, shift, foreshadow build; resolve releases; breathe paces. Propositions are further
classified Anchor / Seed / Close / Texture by forward and backward activation — a Seed plants
forward and is harvested later, which is a cliffhanger's semantic signature stated in the type system.
So "orient the narrative around the decision" is not a new mechanism. It is a cadence constraint on the arc terminal, expressed in vocabulary the generator already emits and the record already stores.
2. When a fork is a cliffhanger — the deterministic gate
A fork qualifies as a cliffhanger when all of the following hold. Every test is a pure derivation over stored state; none requires asking a model whether the moment is exciting.
- Withheld payoff. On the thread the fork resolves, the terminal scene emits a RISE / TURN / WIDEN event
(
escalation,twist,opening) and nopayofforclosure. This is the mechanism: the question is loaded and left open. A terminal scene that pays its own thread off has already answered the reader's question, and the fork after it is administrative. - A live question. The thread's stance is genuinely unsettled — small
getStanceMargin().marginand highnormalizedEntropy(src/lib/core/forces/thread-market.ts). A thread sitting at 92% has a foregone conclusion; stopping the reader there manufactures suspense the world does not have. - Typed divergence. At least two candidate continuations differ in
WorldExpansiondeltas, per the delivery spec's commitment contract, and ideally with opposed signs on the fork thread's stance delta — the exploration spec's strongest orthogonality signal. Same-direction candidates are a pace change, not a dilemma. - Irreversibility. At least one candidate carries a delta that cannot be undone by a later scene —
systemDeltas/ownershipDeltas, a thread crossingisThreadClosed, an entity leaving play. A choice whose branches reconverge is a scenic detour, and the reader learns that within two forks. - Legible in one sentence, from the reader's seat. The question must be sayable as the reader's own concern without exposing anything outside their information boundary. If naming the stakes requires private material, the fork belongs to a different seat.
A fork failing (1) or (2) should not be deleted — it is still a legitimate branch point for exploration and the flowchart. It simply is not a stopping point for a reader: the delivery passes through it on the accepted continuation and never raises a choice screen. Stopping the reader is the scarce resource.
The anti-metric rule holds (as in the exploration spec): this gate decides where to stop the reader. It
must never be fed back into prompts as "write me a cliffhanger", and the generator must not be tuned to make
the gate pass more often. A story in which every scene ends on a twist is not tense, it is noise — and the
BeatSampler's markov chain exists precisely because beat distribution is a property of a real corpus, not
a preference.
3. Cadence — where a cliffhanger is allowed to fall
The pacing unit already exists: SCENES_PER_ARC (4). An Episode is an arc, and the arc terminal is the
only place a delivery may end on an unresolved question. So the shape of a prepared Episode is:
scene 1 breathe / inform — the situation, re-established for a returning reader
scene 2–3 advance / frame / shift — the question is loaded; pressure compounds
scene 4 turn / reveal / foreshadow, NO resolve — the cliff
├─ fork (if the §2 gate passes) → choice screen
└─ otherwise → accepted continuation, no interruption
Three consequences worth stating because each is a real constraint on generation, not a nicety:
- Terminal-scene bias, not global bias. The beat cadence override applies to the last scene of an arc
only. Biasing the whole domain toward
turnproduces exhausting prose and corrupts the sampler's correspondence with its source corpus. - Openings pay off the previous cliff. A resumed Episode must discharge the tension it left — the first
scene after a commitment shows the consequence (the delivery spec's after-commitment readout is the UI half;
this is the prose half). A cliff whose payoff never arrives is the medium's cardinal sin, and it is
detectable: a RISE event with no subsequent
payoff/closureon that thread anywhere downstream. - Not every arc ends on a cliff.
all_threads_resolvedandpayoffterminals are how a route reaches an ending. A structure that can never rest has no endings, and endings are what make replay meaningful.
4. The choice screen is a projection of a Reading
The fork material is already modelled. A Reading (src/types/domain/reading.ts) is an arc-anchored
cohort of priced Futures, each carrying name, description, variables, priorLogit, reasoning,
considered (the rejected option space), breaks (the falsifier) and opens (what it primes) — and the
Reading itself records asOfEntryId + contextScope, i.e. the vantage in time it was taken from.
That is a choice screen with provenance, and it maps cleanly onto the contract:
| Reader-facing element | Source | Rule |
|---|---|---|
| the option label | Future.name + description, rewritten in second person | never a raw id, never a probability |
| what it would set in motion | opens | shown as flavour, not as a promise of outcome |
| what was weighed | considered | behind an affordance, never dumped on the screen |
| the odds | softmax over priorLogit | after commitment only — before it, a prior is an instruction |
| what would have made this wrong | breaks | travels to the ending card, not the choice screen |
| the vantage | asOfEntryId / contextScope | the honest "as of scene N" — a hindsight-scoped cohort must say so |
Two cautions:
- Vocabulary collision.
Readingis taken, and it means this — a priced cohort of futures. The reader's session is a Playthrough with a cursor; never call it a reading session in code or copy (Play vocabulary governs). - A hindsight-scoped cohort is not fork material. If a
Readingwas generated withcontextScope: "full", its options were drafted knowing what came after the fork. It is a fine analytical artifact and a poor choice screen; forks should be prepared fromasofcohorts cut at the fork entry.
5. Live posture — a decision window is a Scenario, not a menu
When a commitment spends Will rather than selecting a passage, the resolution path already exists and is
substantial: the Scenario table (src/types/domain/scenario.ts) runs a typed phase machine —
read → write → play → resolution → graph → arc → scoring — over seats with conviction economies,
concealment regimes (house / stealth), contested-thread settlement (ResolveBias) and a per-seat
projection boundary enforced at projectForSeat.
So the live fork is not a new mechanism either:
- A reader's commitment in live posture is a play at the
playphase, priced by the conviction economy — the cost is the weight of the decision, and it is already scarcity-modelled. - Concealment is already enforced at the projection boundary rather than hidden in the UI, which is exactly what a seat-shaped choice screen requires.
- The
resolution → graph → arcsteps are how the commitment becomes canon with provenance. The delivery never resolves anything itself; it hands off and waits. - Scenario's
Goal(a seat's private target that "NEVER affects the score") is the honest model for reader intent: a stated aim is a tracking aid, not a lever on truth.
The reader-facing consequence: a live fork must be visibly heavier than a traversal fork — it costs conviction, it can be contested by other seats, and it may not resolve the way the reader chose. Presenting the two with the same affordance would be the single most damaging simplification available here.
6. Failure modes this design is trying to prevent
- The fake cliff. Stakes announced at the arc terminal, then discharged in the first line of the next
scene. Detectable: a RISE event followed immediately by a
payoffwith no intervening material. - The false dilemma. Options that read differently and land identically. Held by the delta gate — and it is worth logging, because a generator that keeps producing convergent sisters is the signal that the divergence instruction in exploration is not biting.
- The truncation cliff. Ending mid-sentence or mid-action to manufacture tension. The medium's version of suspense is a committed turn — something happened and its consequence is unknown — not a withheld predicate. Cheap, and readers punish it.
- Choice fatigue. A fork at every arc terminal is as bad as none: the reader stops reading the question and starts optimising. The §2 gate is deliberately strict so that stopping is rare and means something.
- Suspense theatre. A near-certain thread dressed as a dilemma. Held by the entropy/margin test — and this is where the instrument's honesty pays off: it knows the question is settled, so pretending otherwise would be lying with its own numbers on file.
- Priors as instruction. Showing the odds first. Already a rule in the delivery spec; restated because it is the one that will be argued for on "helpfulness" grounds.
7. The honest ledger
| Requirement | State | Where |
|---|---|---|
| Typed thread events (rise / fall / turn / widen / resolve) | shipped | ThreadLogNodeType |
Typed beats + Seed/Anchor/Close proposition classification | shipped | BeatFn, PropositionClassification |
| Beat cadence sampled from a real corpus (markov + fn→mechanism) | shipped | BeatSampler |
| Stance margin, entropy, closure/abandonment derivations | shipped | forces/thread-market.ts |
Priced future cohorts with considered / breaks / opens and an as-of vantage | shipped | Reading, Future |
| Scenario phase machine, conviction economy, seat projection boundary | shipped | scenario.ts, projectForSeat |
| Arc grain as the pacing unit | shipped | SCENES_PER_ARC |
| Scene-exit gate for hanging/live Threads | shipped (partial; divergence and irreversibility remain untested) | src/lib/core/scripts/scene-exit.ts |
| Unpaid-cliff detector (rise/turn/widen with no same-scene payoff) | shipped | src/lib/core/scripts/scene-exit.ts |
| Script lenses, reader reveals, and knowledge asymmetry | shipped (derived) | src/lib/core/scripts/, ScriptMoment.reveals |
Choice screen as a Reading projection, priors withheld until commitment | build | reader surface (reader.md) |
| Fork-qualification readout for the Director (why this stopped / didn't) | shipped (authoring surface) | SceneScriptView, read-scene-script |
8. Open questions
- Who authors the question text — derived from the thread's own phrasing (honest, sometimes dry) or written per fork by the composer (vivid, another paid call, and a place for drift to enter)?
- May a reader ever be stopped at a fork that fails the gate, if they explicitly asked to be stopped at every branch point (an explorer's mode)? My instinct: yes, but labelled as a branch point rather than dressed as a dilemma.
- How many forks per Episode at most — one (clean, matches the arc grain) or one per open thread that qualifies (denser, risks the optimising posture)?
- Does an unpaid cliff block promotion in the exploration ladder, or merely warn? Blocking is stricter and could stall a branch whose payoff is genuinely two arcs out.
- Whether the terminal cadence bias is a domain setting or a per-Episode preparation option — a domain whose corpus rarely ends on turns should probably be allowed to say so.