MeridiansMeridians

The pentad in the app — measurement, surfaces, post-hoc actions, roadmap

Source path: knowledge-base/specs/pentad/app-integration.md

# The pentad in the app — measurement, surfaces, post-hoc actions, roadmap

Status: **step 3 shipped.** Flags and composition diagnostics are shipped as derived
projections; PostHoc settings, MDL-based Acting growth, debt reads, κ analytics, and the Maintain
surface are built. Acting growth runs at the generation seams in the arc, world, futures, game, and
program-merge operations, deliberately not during extraction or creation. This document is the
argument and the sequence for shifting the
UI, generation, and measurement of Meridians around the [pentad](README.md). Every "today" cell below was
verified against the code at the paths cited; every "plan" cell is proposal. Where a formula is given it is
a shipped calculation or an explicitly labelled diagnostic. The reader-first white paper's
claim — *discover what your choice changed* — is what this plan exists to make true in the product.

Grounding: `src/lib/core/forces/domain-utils.ts` (raw forces, snapshots, activity),
`force-grading.ts`, `theme-analysis.ts`, `thread-log.ts`, `thread-market.ts`; the UI consumers
`ScorecardPill`, `ForceAnalytics`, `ForceTimeline`, `DomainReport`, `ArcDetail`/`SceneDetail`,
`ThreadDetail`, `BranchGraphView`; settings in `src/types/domain/settings.ts` and
`DomainSettingsModal`; spatial ops in `src/lib/server/ops/catalog.ts`; Acting in
`EntityUnderstandingPanel`.

---

## 1. Activity is not the world; it is the visible part of it

Today the engine measures **activity**: five raw per-scene quantities over the pentad, each
rank-Gaussian normalised, then combined —

```text
Fate_i   = Σ_threads v_t · D_KL(p⁺ ‖ p⁻)         belief moved on Threads this scene touched
World_i  = ΔN + √ΔE                              entities and edges added
System_i = Σ_rules −log p(topic)                 surprisal of newly binding rules
Theme_i  = Σ_signals |Δ salience|                 Theme movement in this Scene
Causality_i = Σ_flags w(stakes)·ln(1 + alternatives)  choice-space removed in this Scene
A_i      = Σ_c w_c · F_c,i                         the five-force activity curve
```

Every one of these is a **per-scene increment**: what this scene added to the record. That is exactly why
they are the visible forces — a scene can be scored on its own. Theme is graded from structure breadth ×
depth over valid leaf slots, while recurrence (attention softmax over salience, entropy, coverage,
grounding) remains a diagnostic across the whole selected prefix. Causality is graded from native Flag
information against the prose-length density expectation for the selected interval.

The five channels are visible in activity, while their grade and diagnostics retain distinct currencies.
Theme recurrence, Causality reach, and Fate's open mass remain longitudinal readings beside the
scene-level curve. A reader can therefore see both what moved at a Scene and what that movement meant
across the record.

So the measurement programme has two halves:

| Half | Forces | Kind of number | Today |
|---|---|---|---|
| Activity | World, System, Fate, Theme, Causality | per-scene increment, rank-Gaussian normalised | shipped |
| Composition | Theme recurrence, Causality reach, Fate open mass | relation over the record at a vantage | shipped as diagnostics |

The same five channels feed `ForceSnapshot`, Activity, and Signature. The scorecard still keeps grade
currencies and longitudinal diagnostics legible rather than collapsing them into one number.

**Flag.** Causality now contributes a scene-local Flag information series to Activity and Signature,
while its Flag-information grade and H_open / reach remain longitudinal diagnostics. Theme contributes its
canonical signal-point series in the same way, while Theme recurrence and structure realisation remain
available beside it. Extraction and generation are five-force.

---

## 2. Candidate formulas for the invisible forces

All three are computed at a **vantage** `(branch, entry)` over the resolved entry prefix, like
`scoreThemeSystem` already is. None writes state. Each has a falsifier so it can be rejected.

### 2.1 Fate as weight (open mass)

```text
H_open(v) = Σ_{Threads open at v} u_t · H(p_t(v))       u_t = log(1 + volume_t), H = Shannon entropy
```

What it says: how much unresolved, evidenced uncertainty the world carries right now. It **falls** when a
question collapses and **rises** when a scene opens one. Reads: `Thread.closeEntryId`, `Stance` logits via
`stanceAsOf` (`thread-market.ts`). Falsifier: if `H_open` does not drop at scenes readers name as
resolutions, the weighting is wrong. Volume decay also lowers `H_open` during quiet stretches, so the
closure-attributable drop (`Σ collapse`) is the discriminating quantity.

This is the number the current Fate increment cannot give: a scene of enormous belief movement (high
`Fate_i`) may leave the world *more* certain (low `H_open`) — a payoff — or *less* — a twist that adds
outcomes. Today the two are conflated in one line on the chart.

### 2.2 Theme as recurrence

```text
Θ_i = Σ_{signals s in scene i} |Δsalience_s| · log(1 + n_θ(s), i)        n_θ = prior scenes carrying Theme θ
```

What it says: a Theme's first appearance is worth nothing to the *force* (it is not yet a pattern); each
recurrence is worth more. This is recurrence, not development or growth: `|Δsalience|` counts rising
and falling signals alike, and the logarithmic count is frequency-weighted. The total is length-dependent,
so it should not be naively compared across Domains of different lengths. This is the rule that makes
Theme invisible in a single scene and visible across the record, made numeric. It replaces the flat
`Σ|Δsalience|` scene points that `scoreThemeSystem` currently caps at 25 rather than replacing them,
while keeping attention, entropy, coverage, and grounding as diagnostics.
Falsifier: in a Domain with one dominant Theme, `Θ` must rise monotonically with recurrence; in a Domain
whose signals are all singletons, `Θ` must be ~0 even if salience is high.

### 2.3 Causality as entropy flow and reach

Causality is the transition structure over Fate: not a scalar force but a graph, with a flow measure
attached. Three layers.

1. A flag is a point event. Fate is a probability vector `p_t` over a Thread's outcomes, moving scene
   by scene. A flag is the scene where it stops moving: `p⁻ → p⁺ ≈ δ_k`, outcome `k` now fixed on this
   branch — concretely `(thread, scene, k, p⁻, p⁺)`. World and System are counts of state; Fate is a
   distribution; a flag is a change of distribution. That is why it is the least visible force: it can
   only be seen between two scenes, never in one.

2. Causality is the graph on flags. Edges are `dependsOn`: flag B needed flag A to be possible — a DAG
   whose vertices are collapses and whose edges are "A's answer was in the support of B's question". A
   branch is an assignment of values to a set of questions; two branches with the same rules and cast
   differ exactly in their flag vector. This branch-as-Flag-vector claim is now a computed comparison
   surface: `diffFlags`/`diffBranchFlags` retain the shared questions, mark divergent rows, and identify
   the first divergence without adding canonical state. "What can lead to what" is reachability in that DAG: divergent
   flags are vertices with high out-degree, conditional flags are vertices whose in-degree is what
   matters.

3. The measure is a flow of uncertainty. With
   `H_open = Σ_open ln(1+v_t)·H(p_t)` the stock of weighted possibility still in play, a collapse is
   the flow out of it: `v_t·(H(p⁻) − H(p⁺))`. Fate measures information gained (KL — how far belief
   moved); Causality measures possibility spent (entropy drop — how much of the open space was closed
   off). Same currency (nats), different operator: Fate is the derivative of belief, Causality is the
   divergence of possibility. The flows at a scene must sum exactly to the closure-attributable drop in
   the stock — that is what makes it a measure rather than a plausible number.

Fate says where the world is leaning; Causality says which leanings became facts, in what order, and
what each fact foreclosed. It is invisible per scene by construction because it is a difference operator
across scenes — as Theme is an integral across scenes.

Status: layers 1–3 ship as derived projections; dependency edges are derived from the record
(co-movement, attribution edges, opens) — a model-refined edge stage remains optional.

For the shipped projection, the scene aggregate and retrospective diagnostic are:

```text
u_t          = log(1 + volume_t)
collapse_f  = u_t · (H(p⁻) − H(p⁺))                at the closure node
C_i         = Σ_{flags f collapsed at i} collapse_f                         atomic Causality at scene i
reach_f(v)  = |{ flags g at or before v : f ∈ dependsOn_g }| + |forecloses_f|  retrospective diagnostic
```

Here `p⁻` is the belief at the previous trajectory node, `p⁺` is the belief at the closure node, and
`H` is unnormalised Shannon entropy in natural-log units. `collapse_f` is the closure-attributable flow
of `H_open`: it is the open Fate mass that closure removed, weighted by the stance volume entering the
scene. More outcomes open means more mass; `normalizedEntropy` is the rejected alternative because it
would erase that option-set contribution. A flag that settled a 50/50 counts more than one that
confirmed a 95. The dependency term is zero until step 4; the shipped projection is deliberately the
direct collapse sum. `reach` cannot be known when the flag is minted — it is read at a later vantage
and is the closest thing the engine will have to *this is the choice that mattered*.
Falsifier: `reach` should rank the disclosure scene in the paper's worked example above the delegation
scene, and both above filler; if hand-labelled turning points do not rise to the top of `reach` on the
Harry Potter benchmark Domain, the dependency extraction is wrong, not the reader.

`C_i` is the Causality channel in the five-force chart; `reach` remains a per-flag readout, not a
series. `ForceSnapshot` carries all five rank-normalised channels. The grade uses native Flag information
against the prose-length density expectation; signed collapse and reach remain separate diagnostics.

`openMassSeries` currently replays the resolved prefix once per scene, so its implementation is O(n²)
over the timeline.

### 2.4 The scorecard

```text
World    grade  deduction
System   grade  deduction
Fate     grade  deduction
Theme    grade  deduction  + coverage / competition / grounding diagnostics
Causality grade  deduction + open mass H_open at cursor, top flags by reach
overall  Σ grades (each an anchored logistic of realisation)
```

Rendered **in the order of visibility** — World, System, Fate, Theme, Causality — not the current
Fate-first order, so the UI teaches the frame every time it is opened.

---

## 3. Where the pentad lives in the app

Principle: **Causality is shown at the things it connects, not as a fifth blob.** A flag is shown *at* the
Thread it collapsed and *on* the entry where it happened; its dependencies are edges between flags; its
reach is a number on the flag. This mirrors how Theme is already shown (purple attribution beside the
triad, a tree, a rack of carriers) and avoids a competing canvas cohort.

| Surface | Today (verified) | Plan |
|---|---|---|
| `ScorecardPill` (topbar) | Fate, World, System, Theme rows; hard-coded colours | Five graded rows in visibility order; `H_open` at cursor; colours from tokens |
| `ForceAnalytics` / `ForceTimeline` | Fate/World/System series + Theme + activity | add `Θ` (replacing flat Theme points) and `C_i` series; a second "composition" pane: `H_open` line with flag markers at collapses |
| `ThreadDetail` | outcomes, stance, movement, replay | **Flag block** when closed: value, vantage, `collapse`, `dependsOn` (linked), `opens`/`forecloses`, `reach` at cursor, nature chips (divergent · conditional · personal) |
| `SceneDetail` / `ArcDetail` | force sign readout, Theme moments | "What this scene collapsed" list → flags; "what it depended on" |
| Inspector: new **Thread closures** panel | — | branch's closure ledger in entry order; filter by nature, Thread, seat; click → Thread/entry |
| Stage: Threads view | lifecycle replay at cursor | collapsed Threads drawn with their flag; dependency edges between collapsed Threads as a faint overlay |
| `BranchGraphView` / Branch Chat compare | lineage columns, `compareBranchIds` | **Branch diff over Thread closures**: shared question set, rows = Threads, cells = collapsed value per branch; differing cells highlighted; "first divergence" marker |
| `DomainReport` / slides | force table, arc grades | five-force table; a "what led to what" page: top flags by reach with their dependency chains |
| Extraction job detail | World/Fate/System/Theme counts | a Causality phase card: flags minted, dependency edges, unresolved collapses |
| Tokens (`globals.css`) | `--color-fate/world/system`, Theme violet; no fifth slot | add `--color-causality` + `.category-color-causality`; migrate the four hard-coded literal sites (report, scorecard, analytics) to tokens in the same change |

Colour is an open decision (§7 of the README). The constraint: it must sit beside red/green/blue/violet
and read as "between" rather than "another thing" — amber is the obvious candidate; decide on the canvas.

---

## 4. Post-hoc actions: settings and the Maintain surface

Today: `postHoc` persists automatic-maintenance policy for spatial mapping and Acting growth; spatial
ops (`generate-board-topology`, `drain-spatial-mapping-debt`, `rebuild-location-hierarchy`) remain
manual/MCP with buttons in `SpatialSettingsModal`. Acting growth is MDL-based
(`assessActingGrowth` → `maintainActingGrowth`) with `keep-evidence`, `interpret`, `reinterpret`, and
`hold` verdicts. It runs at the arc, world, futures, game, and program-merge generation seams, and
deliberately does not run in extraction or creation, matching the
`acting-maintenance-invariants.test.ts` ratchet.
Post-hoc bindings are attributable and revisable: `selectedBy: "post-hoc"` may be maintained, while a
Will-selected binding is held and never overwritten.

Step 3 status: **shipped** — the `postHoc` settings block, pure spatial/Acting debt read,
κ composition analytics, Tier-0 `read-post-hoc-debt`, Maintain settings section, MDL-based Acting
growth from #234 are built. Will bindings are held; post-hoc bindings are attributable to the
maintenance pass.

Plan — one settings block and one surface:

```ts
// DomainSettings (flat, backward compatible; spatialAutoMaintain migrates in)
postHoc?: {
  spatial:   { autoMaintain: boolean; reorganizeAtBirth: boolean };          // from spatialAutoMaintain
  acting:    { autoMaintain: boolean };                                      // MDL growth; Will binding stays manual
  flags:     { detect: "off" | "on-extraction" | "on-commit"; };             // projection, never a write
  questions: { synthesise: "off" | "on-extraction" | "on-commit"; };         // ranked report, never a write
}
```

**Maintain** — a surface (inspector utility panel or Domain menu) that lists every post-hoc operation with
its last run, what it read, and a run button, grouped by what it interprets:

```text
Interpretations of System     Rebuild world · Map levels · Drain mapping debt · Maintain Acting growth
Invisible forces              Detect flags (Causality) · Re-score Theme recurrence
Synthesis                     Synthesise questions at cursor
```

Rules: interpretations and projections may run automatically; **binding** an Acting read, **committing**
a synthesised question as a Thread, or **overriding** a detected flag's nature is Will and stays manual
and attributable. Each op is an ordinary catalog entry (`params`, `result`, `commits`, `engine`) so MCP
and the Program can call it; the Program's `UPDATE_DOMAIN_PROGRAM_CONFIG` gains optional post-hoc steps
after Merge.

---

## 5. Flag detection integrated in extraction and generation

### 5.1 Extraction — a Causality phase

Insert after Fate re-extraction and Domain assembly (the thread log is priced by then) and before the
topic-tree/spatial phases:

```text
phase  Causality (flags)                                   projection-first
read   Threads (outcomes, closeEntryId, closeOutcome), ThreadLogNode{closed, infoGain, preVolume, updates},
       scene threadDeltas/worldDeltas/systemDeltas, attribution edges, branch entry sequence
step 1 deterministic: every closure node → candidate Flag{threadId, outcomeId, vantage, collapse}
step 2 deterministic: derive co-movement, attribution, and opens edges plus transitive reach from
       the record; model citation/refinement remains optional and is not built
emit   Thread closure ledger + dependency edges as a derived extraction artifact keyed by branch
```

Step 1 needs no model and can ship first; it makes the Flags panel and `C_i` (without the dependency
term) real. Step 2 is the LLM stage and is where "effective extraction of flags from canonical text"
lives. Its prompt joins the `prompts/core/forces.ts` family; the existing "never a fourth causal axis"
locks are about Theme and stay.

### 5.2 Generation — flags as read-only context

`generateScenes`, `scene-response`, plan/prose/script generation get a `<closures>` block at the vantage:
collapsed values, their natures, and the open Threads with highest `H` and `reach`-weighted dependents.
Effect: continuation generation is constrained by *what this timeline has already made certain* and
oriented toward *what is load-bearing*, which is the concrete meaning of "all five forces integrated into
generation." Flags are input only; the reducer's typed deltas remain the only writer.

The ambient force reading is structured so each row remains inspectable at the generation boundary:

```xml
<forces read="World→System→Causality→Fate→Theme"
        hint="five forces; read = display order and derivation from the record; couplings = measured per work">
  <force id="World" is="what exists" carries="entities, places, artifacts, knowledge, state"/>
  <force id="System" is="what binds" carries="rules, permissions, institutions, economies"/>
  <force id="Fate" is="what is open" carries="p over each Thread's outcomes"/>
  <force id="Theme" is="what recurs" carries="propositions across scenes"/>
  <force id="Causality" is="Fate settling" carries="Flag = scene where a Thread becomes fact on this branch; forecloses the rest"/>
  <will is="not a force" carries="attributable authority from outside — reader, seat, Director, agent — settles a Thread"/>
  <generation serves="the world's recorded leaning" never="manufacture consequence | settle a Thread canon leaves open | disclose what a lens does not hold"/>
</forces>
```

Pulse also emits the record-specific Constitution and force-flow evidence:

```xml
<constitution source="fitted|unread">
  <coupling from="World" to="System" lag="1" r="0.410" n="20" t="2.31" />
</constitution>
<flow>
  <edge id="lag1:World&gt;System" relation="coupling" lag="1" verdict="readable"
        corr="0.410" pairs="20" t="2.31" />
</flow>
```

The `<constitution>` rows are emitted by `src/lib/engine/ai/context-pulse.ts` from fitted couplings in
the current record. Flow IDs use `lag1:` and `lag2:`; lag 1 is not a generating ring and lag 2 is not a
controlling star. `read` on the `<forces>` block is the symbolic display order only, while Constitution
and Flow are unconstrained, record-specific measurements. An empty fitted Constitution means the forces
read as independent; `unread` means the record is too short for a readable edge.

Flags make visible how Causality and Fate moved in this record: a settled Thread is the collapse
that shapes this scene's branch. Questions make visible the Fate → Theme edge: what hangs on a question
is what the scene must make legible. Reader-facing anticipation questions ship as a deterministic
projection from settling Flags; they are never model-generated and remain ordinary scene question-bank
records with optional provenance.

### 5.3 Question synthesis operation

`synthesise-questions` (catalog op, Tier-0): reads the five forces at a vantage; emits ranked open
questions with candidates and the forces that make each open. This is a deterministic read: the five
forces already carry the answer, and a model layer would only paraphrase it. Output is a
report/projection the Director can promote to Threads by an explicit commit. This is distinct from the
existing scene question-bank operation, which stays.

---

## 6. Roadmap — six steps, each with a falsifier

| # | Step | Ships | Falsifier / exit |
|---|---|---|---|
| 1 | **Thread closure projection (deterministic)** — closure nodes → Thread closure ledger; `ThreadDetail` closure block; Thread closures panel; `--color-causality` token | Causality visible for the first time — **shipped (derived; `ForceSnapshot` unchanged)** | every closed Thread on a benchmark Domain shows a Thread closure with the right value and entry |
| 2 | **Composition measures** — `H_open`, `Θ` recurrence, `collapse`; scorecard in visibility order | Theme and Fate-as-weight numerically distinct from activity — **shipped (derived diagnostics; Theme recurrence and entropy flow graded; `ForceSnapshot` unchanged)** | §2 falsifiers hold; Theme singleton-Domain ≈ 0 |
| 3 | **Post-hoc settings + Maintain** — `postHoc` block, Maintain surface, spatial/Acting/flags/questions ops registered | one place that keeps the interpretations of System current — **shipped; MDL Acting growth (#234) runs at the arc/world/futures/game/program-merge generation seams, with post-hoc bindings attributable and Will bindings held** | operators can run and audit every post-hoc op without losing manual authority |
| 4 | **Dependency extraction (deterministic)** — `dependsOn`/`opens`/`forecloses`/nature; `reach` | Causality as a force that can be *understood* — **shipped (deterministic edges + reach; model refinement optional)** | hand-labelled turning points rank top by `reach` |
| 5a | **Causality alluvial** — Thread closure collapse bands in Influence | **shipped (projection-only)** | settled Thread closures render by nature and collapse |
| 5b | **Causality topic-tree leaves** — native Flags plus surviving Thread closures under owning Thread topics | **shipped** | canonical Flags and legacy Thread closures remain inspectable without duplication |
| 5c | **Causality extraction tab** — assembled-Domain Flag summary and open mass | **shipped** | completed extraction exposes Flags and H_open |
| 5 | **Branch diff over Thread closures** — compare view; "first divergence" | *parallel worlds differ in their Thread closures* becomes a screen — **shipped (`diffClosures`, `diffBranchClosures`, `read-closure-diff`, and the ThreadsView comparison surface)** | two benchmark branches diff to exactly the Threads that differ |
| 6 | **Synthesis + generation reads Flags** — `synthesise-questions`; anticipation questions; `<closures>` context; conditioned `questions → script` pipeline; Script lens knowledge and descriptive force reading | deterministic synthesis, anticipation questions, and the conditioned generation reads — **shipped**; the reconfigurable pipeline configuration record in README §4.3 remains unbuilt | the three configurations in README §4.3 run from one config record |

Steps 1–2 are pure/`core` work with vitest coverage and touch no reducer action. Step 3 adds one settings
field group, MDL Acting maintenance, catalog entries, and a panel. Steps 4 and 6 add LLM stages behind `callGenerate`. Step 5 is
UI over a pure `diffFlags(a, b)`.

---

## 7. Why this is the right path — the case, and how to reject it

- **It builds on what measures well.** The original per-scene activity math remains intact for Fate,
  World, and System while Theme and Causality now join the same five-force aggregate; longitudinal
  composition diagnostics remain beside it. No
  existing chart, grade, or test changes meaning.
- **It makes the paper's claim testable.** *Discover what your choice changed* is `reach` on a flag and a
  branch diff. If step 4's falsifier fails, the claim fails visibly rather than rhetorically.
- **It respects the writer boundary.** Every new quantity is a projection; every new op is
  interpretation or synthesis; every promotion to canon is an attributable commit.
- **It teaches the frame by use.** Visibility order in the scorecard, flags at Threads, dependencies as
  edges: the UI shows a reader why the invisible forces are invisible — you have to have read the whole
  record to see them.
- **It is cheap to abandon.** Steps 1–3 are worth having even if Causality never earns a grade.

Reject it if: closure nodes are too sparse on real Domains for flags to be interesting (measure on the
benchmark set first); or `reach` and hand-labelled turning points do not agree after one round of prompt
work — in which case Causality stays a named, unmeasured force and the paper's Appendix A sentence stands.
Open on GitHub

Raw Markdown source