# Dust2 Live Kill Market — Architecture (C4) The system is a DAG of append-only, offset-addressed logs ("streams") connected by deterministic transformers. Every runtime component obeys one contract: > **component = (declared input streams + offsets, deterministic transformer, one output stream)** The only impure edges are the system boundary (demo/broadcast ingestion, user input capture, the epoch gateway's reading of the operator's epoch document and of a partner chain's final blocks, and a run's start reading the epoch store — futarchy-system.md §7, §11 W10; allocation-market.md §5). Everything interior is a pure fold, so any derived state is recomputable and verifiable from the root logs — verifiable meaning RECOMPUTABLE by anyone holding them: the hash chain binds every record, and the ingress binds an account and an operator by recorded existence (gateway-protocol §3 rule 2), so the record proves self-consistency, not authorship — except a customer's instruction relayed by an operator, signed with Ed25519 (RFC 8032) and verified in the exchange fold from its recorded bytes (matching-engine §5), whose record proves authorship by the customer key; signed digests, state roots and proofs belong to the verifiable-order-book design, outside this document. `state_version` is always the offset of the game-state stream. The replay clock is the only clock any transformer, record or stamp sees. - Level 1 — [System Context](#level-1--system-context) - Level 2 — [Containers](#level-2--containers) - Level 3 — [Components / Stream DAG](#level-3--components-the-stream-dag) - [Stream lineage & commitment model](#stream-lineage--commitment-model) - [Timelines, time travel, and forking](#timelines-time-travel-and-forking) - Execution flows live in [flows.md](./flows.md) - Per-layer state machines live in [state-layers.md](./state-layers.md) — every stateful entity as a fold, every transition an appended event - Byte-level contracts: [event-catalog.md](./event-catalog.md) · [settlement-contract.md](./settlement-contract.md) · [matching-engine.md](./matching-engine.md) · [allocation-market.md](./allocation-market.md) (the Allocation Layer — the vault-capacity epoch) · [futarchy-system.md](./futarchy-system.md) (the same layer as a chain of blocks under core law 8 — the block interface, the epoch document's system hash, the config loader edge, the fixture emitters, the config defaults) - Implementation views (scheduler, ERD, crates, deployment): the dev-kickoff note (gitignored, local-only) - Economics & governance authority (binding — CLAUDE.md core law 7): this repository's economics work is bound by private authority notes — gitignored and untracked (they exist in a working copy and nowhere else), kept verbatim, never edited here; the tracked economics specs, [allocation-market.md](./allocation-market.md) and [futarchy-system.md](./futarchy-system.md), are DOWNSTREAM of them — they cite them section by section, pin every open value as an owned field of the epoch document, and the economics review (CLAUDE.md §2–4) holds them to them; the pre-push hook's economics arm refuses a push without that review's mark --- ## Level 1 — System Context ```mermaid C4Context title System Context — Dust2 Live Kill Market (v0.1) Person(bettor, "Retail Bettor", "Buys/sells Higher and Lower claims with play money; cashes out or holds to settlement") Person(operator, "Operator / Researcher", "Runs replays, trains models, reads preflight and evaluation reports") System(dkm, "Dust2 Kill Market", "Prices final round-kill distributions per team and exposes them through a CLOB over deterministic historical replays") System_Ext(corpus, "Historical CS2 Demo Corpus", "Professional Dust2 demo files — source-agnostic: whatever provider supplies them, the ingestion edge normalizes into the canonical match log") System_Ext(opencs2corpus, "OpenCS2 Match Archive", "Professional CS2 matches (parquet tables) — the training corpus; the quarantined opencs2-edge adapter normalizes eligible matches into the SAME canonical CS2 match log (event-catalog 6.12)") System_Ext(dota2corpus, "Betty-Dota2 Match Archive", "Professional Dota 2 matches (parquet tables) — the second title's source; the quarantined dota2-edge adapter normalizes eligible matches into the dota2 match log (event-catalog 6.11, the title-agnosticism slice)") System_Ext(feed, "CSTV / Commercial Feed", "Live fragment source (out of scope; interchangeable with the demo corpus behind the same ingestion interface)") Rel(bettor, dkm, "Views quotes, trades, cashes out", "Browser / WebSocket") Rel(operator, dkm, "Ingests data, trains, evaluates, verifies", "CLI") Rel(dkm, corpus, "Batch-ingests demo files", "filesystem") Rel(dkm, opencs2corpus, "Batch-ingests the training corpus", "filesystem") Rel(dkm, dota2corpus, "Batch-ingests the bounded dota2 slice", "filesystem") Rel(feed, dkm, "Streams broadcast fragments (out of scope)", "HTTP fragments") ``` **Trust boundary note:** every external system is an untrusted input. Ingestion proves *transformation* correctness only; source authenticity (the oracle problem) is out of scope; source attestation, not ZK, is the design that addresses it. --- ## Level 2 — Containers ```mermaid C4Container title Container Diagram — Dust2 Kill Market Person(bettor, "Retail Bettor") Person(operator, "Operator / Researcher") System_Boundary(dkm, "Dust2 Kill Market") { Container(ui, "Trading Screen", "Browser SPA", "Single responsive screen: replay clock, two market cards, best bid/ask, price history, Higher/Lower entry, position, cash-out, settlement result") Container(gw, "Gateway", "WebSocket/HTTP", "Streams market + game state out; captures user actions into the order log via the input gateway") Container(runtime, "Exchange Runtime", "Deterministic component (hot path)", "Replay cursor, feature extraction, both pricing models, both bookmakers, deterministic merge, matching engine with its own collateral view (fold of market events + settlements)") Container(clearing, "Clearing House", "Deterministic component (async, off hot path)", "Authoritative ledger and settlement: balances, positions, market pots, round payouts, void unwinds (void_refund rows; payout reversals out of scope) — a pure fold over lifecycle + market events + order-log deposits + match-log outcomes (state-layers §7 consumes row); emits the settlement stream") Container(ingest, "Ingestion Pipeline", "Batch parser + preflight validator", "Parses demos into canonical match logs; enforces the settlement contract; emits the preflight report. Out of scope: tailing fetcher + incremental parser + live simulator") Container(train, "Training Pipeline", "Offline", "Chronological match split (recorded match dates: earliest 80% train, next 10% validation, latest 10% sealed test); trains and freezes baseline_v1 and quantized gbm_v1 with content hashes") Container(verify, "Verification Harness", "Invariant taps + re-runner", "Solvency, stale-fill, settlement checkers at every offset; determinism re-run with stream-hash diff") Container(projection, "Projection", "Read-only outlet", "Record catalog + JSONL projection of any segment (d2s cat); the renderer the cycle report reads through") Container(scenario, "Scenario runner", "Validation outlet", "Lays, runs and records the two-cycle and operators artifacts through the fixture emitter and the cycle runner (d2-scenario two-cycles | operators), and the regression scenario matrix, each committed case an operators artifact (d2-scenario matrix); writes projections and the run report (d2-scenario report), reads none back") Container(eval, "Evaluation Suite", "Offline consumers", "RPS with within-round averaging, match-clustered CIs, calibration, coverage, diagnostics; produces the held-out report") ContainerDb(store, "Stream Store", "Append-only canonical-binary segment files (deterministic CBOR) — no database engines. Derived projections: JSONL (debug), Parquet (analytics)", "All logs: match, game-state, feature, probability (live + shadow), quote, order, market-event, settlement, lifecycle, control (model lifecycle + health events). Offsets, hash chains, lineage registry, derived snapshots. Format + full event catalog: event-catalog.md") ContainerDb(artifacts, "Model Artifact Store", "Files + run manifest", "Frozen model binaries, content hashes, seeds, pinned library versions") Container(alloc, "Allocation Layer", "Epoch gateway (impure edge) + allocation fold — a chain of declared blocks (futarchy-system.md): the epoch document and its checks, the fold B0–B15 and its composition `fold_to_settlement`, the ceiling route (the manifest's `allocation_ref`, `Scheduler::new_included` on an inclusion derived from the epoch log, `fold_through_inclusions`), the epoch registry (`futarchy::registry` — the typed predecessor answer rebuilt from the stored epochs alone, as of an opening); the epoch gateway's writes and the payout publisher (E5) are edges of the layer with fixture emitters (futarchy-system.md §7), not part of the fold", "One hashed epoch document per vault-capacity epoch; stakes recorded as facts from the partner chain; the raw and capped allocations, the next manifests sleeve ceilings, the exact-rational settlement and the payout table (allocation-market.md)") } System_Ext(corpus, "CS2 Demo Corpus") System_Ext(opencs2corpus, "OpenCS2 Archive") System_Ext(dota2corpus, "Betty-Dota2 Archive") System_Ext(chain, "Partner chain", "Holds the stake vault; its FINAL blocks carry the stakes the epoch gateway records (allocation-market.md §4–§5)") Rel(bettor, ui, "Uses", "HTTPS") Rel(ui, gw, "Orders in, streams out", "WebSocket") Rel(gw, store, "Appends order events; tails output streams") Rel(runtime, store, "Tails input streams; appends derived streams") Rel(clearing, store, "Folds market-event + round-end streams; appends settlement stream") Rel(ingest, corpus, "Reads demos") Rel(ingest, opencs2corpus, "Reads parquet tables (quarantined opencs2-edge adapter, event-catalog 6.12)") Rel(ingest, dota2corpus, "Reads parquet tables (quarantined dota2-edge adapter, event-catalog 6.11)") Rel(ingest, store, "Appends match logs + lifecycle events") Rel(train, store, "Reads training-split match logs") Rel(train, artifacts, "Freezes model artifacts + manifest") Rel(runtime, artifacts, "Loads pinned models by hash") Rel(verify, store, "Taps every stream; re-runs and diffs hashes") Rel(projection, store, "Reads segments; writes nothing back") Rel(scenario, store, "Writes a recorded cycle pair and its projections; reads no projection back") Rel(eval, store, "Reads probability + settlement streams (test split)") Rel(operator, ingest, "Runs") Rel(operator, train, "Runs") Rel(operator, eval, "Reads reports") Rel(operator, store, "Model lifecycle actions via the Python SDK (control stream)") Rel(chain, alloc, "Final blocks: stakes into the vault", "chain RPC (partner feed)") Rel(alloc, store, "Appends the epoch log (root) and the allocation stream; reads included runs sealed streams") Rel(alloc, artifacts, "The allocation record sets the next run manifests sleeve ceilings (allocation-market.md §8)") ``` **Why one process:** determinism. Single-threaded (or deterministically scheduled) execution, seeded everything, no wall clock in any transition function, fixed-point arithmetic in transition functions. Identical logs + manifest ⇒ byte-identical output streams. **Exchange vs Clearing is a logical split, co-located.** It is the same seam regulated venues use (matching engine vs clearing house). The two components communicate only via streams — never request/response — so the matching engine's collateral view is its own fold of its inputs, not a query to the ledger. Because no code path assumes co-location, the physical split into separate processes at the live stage is a deployment change, not a redesign. --- ## Level 3 — Components: the stream DAG C4 component level, rendered as the actual dataflow graph. Every edge is a named append-only stream; every node is a transformer (or an impure edge component at the boundary, marked ⚠). ```mermaid flowchart TB subgraph EDGE["Edge Layer (impure boundary)"] DEMO[("Demo files")] OCS2[("OpenCS2 parquet tables")] ING["⚠ Demo Ingestor
batch parse — live: discovery, tailing fetch, incremental parse, watermarks (the CSTV/simulator shape; the GRID Series Events capture is single-session, flows.md Flow 4 deviation)"] PRE["Preflight Validator"] UIGW["⚠ User Input Gateway
stamps + sequences actions"] SDKGW["⚠ SDK / Operator Gateway
stamps + sequences model lifecycle actions"] LIVESIM["Live Simulator
re-serves match logs fragment-wise"] end subgraph CORE["Stream Core"] MLOG[["match log
per match, canonical events"]] OLOG[["order log"]] CTRL[["control stream
model register / shadow / promote / suspend (operator root — §6.10 writer split)"]] LIN["Lineage & Commitment Registry
hash chains, parents, state-root slot (reserved — scope ledger); the EPOCH REGISTRY — every epoch log by epoch_id with its predecessor and successor (futarchy-system.md §3)"] SNAP["Snapshot Manager
derived, discardable checkpoints"] end subgraph REPLAY["Replay Layer"] RE["Replay Engine
cursor; emits state_version = offset; forwards round_end / round_restored / watermark / revision (event-catalog §6.2); only clock"] LCM["Market Lifecycle Manager
open / close / halt / void — one fold per board column, one lifecycle stream"] GS[["game-state stream ~4Hz default"]] LES[["lifecycle stream"]] end subgraph PRICING["Pricing Layer"] FX["Feature Extractor
one impl per (title, family) column — the manifest's title and its board select them, one extractor per column over the ONE projector; leakage control; fixed-point"] FS[["feature stream"]] MB["Baseline Model
frozen baseline_v1"] MT["Trained Model
quantized gbm_v1"] MS["Shadow Models 0..N
candidates; no bookmaker; report cards only"] PA[["probability stream A"]] PB[["probability stream B"]] PC[["shadow probability streams"]] end subgraph MM["Market-Making Layer"] BMA["Bookmaker A
baseline-driven; folded state: inventory, posted risk, resting mirror, staged payouts — the quote fold below"] BMB["Bookmaker B
trained-driven; identical risk rules and starting parameters"] QA[["quote stream A
tagged state_version"]] QB[["quote stream B
tagged state_version"]] end subgraph EXCH["Exchange Layer (hot path)"] COMP["Complement Transform
buy Lower @ p ⇒ sell Higher @ 1−p"] MERGE["Deterministic Merge
total order (replay_ts, class, stream, offset); inputs: lifecycle, settlements, quotes, root ingress (orders, quoting records; control records ride class 3 into the registry fold, never here)"] ME["Matching Engine
price-time priority; version-validity; partial fills; admission vs own collateral view = fold of market events + settlements; the admission chain R0 · R9 · R4 · R1 · R2 (risk-engine.md §3); signed customer instructions verified here, Ed25519 verify_strict over the recorded bytes (matching-engine §5)"] MES[["market-event stream
accepts, fills, cancels, rejections"]] end subgraph CLR["Clearing Layer (async, off hot path)"] LED["Ledger (authoritative fold)
balances, positions, market pots"] SET["Settlement Engine
pays per claim-series terms (CS2 kills: k/5); void unwinds (void_refund rows; payout reversals out of scope); emits settlement_complete / void_complete per epoch, naming the epoch's family on a board (one column per family)"] SES[["settlement stream"]] end subgraph IFACE["Interface Layer"] GW["Gateway
WebSocket out / orders in — serves the run's full stream directory read-only (gateway-protocol.md; the drawn GS/MES/SES edges are the primary consumers)"] UI["Trading Screen"] end subgraph VER["Verification Layer (read-only taps)"] VSOL["Solvency Checker
collateral ≥ liability at every offset"] VEQ["Collateral-View Equality Checker
the matching engine's collateral view equals the ledger's, field for field, at every shared offset outside an in-flight settlement or void group (matching-engine §6 — equality of the two folds, not a committed root)"] VSTALE["Stale-Fill Checker"] VSET["Settlement Checker
independent recompute from match log"] VDET["Determinism Harness
re-run + hash diff"] VMODEL["Model Health Tap
invalid-rate / coverage breach → auto-suspend"] ZK["ZK Prover (out of scope)
async; proof stream per round"] end subgraph OFFLINE["Offline Layer"] TRAIN["Training Pipeline"] EVAL["KPI Evaluator
RPS, clustered CI, calibration, coverage"] DIAG["Diagnostics Collector"] REP["Report Generator"] end subgraph ALLOC["Allocation Layer (across runs — allocation-market.md; as blocks: futarchy-system.md)"] CHAIN[("Partner chain
final blocks: stakes into the vault")] EDOC[("Epoch document
config snapshot + rule chain + system hash — the operator's bytes")] EGW["⚠ Epoch Gateway
the config loader (the document in — futarchy-system.md §7 E1), the operator's inclusions and the harness's verdicts, and the chain's final blocks as stakes — a partner feed"] ELOG[["epoch log
root, one per epoch document (event-catalog §6.16)"]] AFOLD["Allocation Fold
the chain of futarchy-system.md §4 — B0 document check, close, raw share, capped share, capacities, returns, window, utilization, estimator (v1/v2 chains; the v3 chain scores the unfiltered series directly), feasible set, exact-rational optimizer, liabilities, payouts on the RAW share, conservation; planned: the note's cap redistribution and cash minimum, the balance series from the runs, group rows, participation yield; + the desk-period chain (risk-engine.md §4)"] ASTR[["allocation stream
derived — one record per block (event-catalog §6.17)"]] end DEMO --> ING --> MLOG OCS2 --> ING MLOG --> PRE MLOG -.->|"live path (out of scope)"| LIVESIM -.-> ING MLOG --> RE --> GS GS --> LCM --> LES PRICING -.->|"the house model sets a house line (legend)"| LCM GS --> FX --> FS FS --> MB --> PA FS --> MT --> PB FS --> MS --> PC SDKGW --> CTRL CTRL -->|"binds models ↔ bookmakers, effective next market_open"| PRICING LES -->|"market_open (plugin-§9 effect timing) / market_resumed (plugin-§11 deferred pull) — the registry fold's status view"| PRICING CTRL --> MM PRICING -.->|"model_output_invalid — model_health stream (derived, event-catalog §6.10 writer split)"| VMODEL PA --> BMA --> QA PB --> BMB --> QB MES -->|"own fills + quote echoes fold back"| BMA MES -->|"own fills + quote echoes fold back"| BMB LES -->|"market lifecycle"| MM SES -->|"own payouts — posted risk released at the barrier"| MM OLOG -->|"forecast_submit / ladder_submit — external sleeves"| MM UI --> GW --> UIGW --> OLOG OLOG --> COMP --> MERGE QA --> MERGE QB --> MERGE LES --> MERGE MERGE --> ME --> MES SES -->|"collateral feedback"| MERGE SES -->|"settlement_complete barrier"| LCM MES --> LED LES -->|"terms + phase markers — the settle trigger"| SET OLOG -->|"account_created — deposits seed the ledger"| LED MLOG -->|"the title's own records, for the outcome recompute (legend)"| SET LED --> SET --> SES MES --> GW GS --> GW SES --> GW GW -->|"any run stream on request — full directory read surface (gateway-protocol §2)"| UI MES --> VSOL MES --> VEQ SES --> VEQ MES --> VSTALE SES --> VSET MLOG --> VSET MLOG --> VDET OLOG --> VDET CTRL -->|"root input when the run carries one"| VDET MLOG --> TRAIN CTRL --> VMODEL VMODEL -.->|"auto-suspend event"| CTRL PA --> EVAL PB --> EVAL PC --> EVAL MLOG -->|"recorded outcomes"| EVAL SES --> EVAL MES --> DIAG EVAL --> REP DIAG --> REP CHAIN --> EGW --> ELOG --> AFOLD --> ASTR EDOC --> EGW SES -->|"each included run's SEALED settlement stream at its run-sealed offset — pnl per sleeve (allocation-market §9)"| AFOLD MES -->|"each included run's sealed market-event stream — posted / reservation peaks per sleeve (allocation-market §9)"| AFOLD LES -->|"each included run's sealed lifecycle stream — market_created gives each market's epoch and family for the barrier (allocation-market §9)"| AFOLD ASTR -.->|"the next runs' sleeve ceilings (legend)"| ME ASTR -.->|"the same ceiling, pre-checked"| MM ELOG -.->|"an included run's inclusion — derived from the log by the real fold; the run_included row's offset into EVERY derived stream's lineage record as included_at (allocation-market §8, B5)"| LIN ``` Legend: `[[double brackets]]` = append-only stream (the edges that matter); rectangles = transformers; ⚠ = impure boundary component; dashed = out-of-scope path OR a non-stream edge (the house-line edge into LCM is a pure in-process function of a recorded artifact — the DAG claim at the top of this document is over STREAM edges, and that edge closes no stream cycle). Three edges above are labelled briefly and spelled out here, because a label long enough to carry the rule is long enough to overlap its neighbours. The PRICING ⇢ LCM edge is the house-line families only (settlement-contract SC-13 rule 5): whichever model the family's house sleeve binds — the manifest's first house sleeve on a single-family run, the sleeve TAGGED to the family on a board — evaluated by the lifecycle policy's LINE SETTER on a match's opening snapshot to choose the line, as a pure in-process function of the snapshot and the recorded artifact (model-plugin-layer §5), never the probability stream. The match-log ⇢ SET edge carries the records clearing recomputes an outcome from: on CS2 `round_start` / kills / `round_end` under SC-1 and SC-2 (k from the kill events, never from reported counts; the roster from `round_start`); on LoL, Dota2 and synth the match envelope and the completion under SC-7 (`synth_discovered` / `synth_complete` on synth, event-catalog §6.18), plus on LoL and Dota2 the crossing, minute, per-side, gold-lead, tally and house-line settling frames through the title's own projector (SC-8 through SC-13). The allocation stream's two dashed edges are the sleeve ceilings of the next runs' manifests with their `allocation_ref` (allocation-market.md §8) — a manifest edge rather than a stream edge, read by the exchange's collateral view and mirrored by the quote fold's own worst-case pre-check. The Lineage & Commitment Registry and the Snapshot Manager are cross-cutting services over every stream — their edges are omitted for readability. The Allocation Layer ([futarchy-system.md](./futarchy-system.md) — the layer as a chain of blocks under core law 8) sits ACROSS runs: its one root is the epoch log, its edges the operator's epoch document (the config loader — config is the first edge input, law 8(b)) and the partner chain's final blocks; the Lineage & Commitment Registry's EPOCH REGISTRY records every epoch log by `epoch_id` with its predecessor and successor, which the loader reads to admit a document's predecessor (futarchy-system.md §3); it reads each included run's SEALED lifecycle, market-event and settlement streams once, at its run-sealed offset (allocation-market.md §9 — the run's final offset, once the determinism harness has verified it), and feeds the NEXT run manifests' sleeve ceilings — a manifest edge, so the run stays its manifest + root logs and the stream DAG stays acyclic ([allocation-market.md](./allocation-market.md) §8–§9). The Determinism Harness re-runs the whole DAG from its root logs (the run title's match log + order log, plus the operator control root when the run carries one — a run citing an allocation always does, the path by which the cross-run stop reaches it (risk-engine.md §5, §8 E7); a LoL or Dota2 run's own match log — the Riot log, the betty FRAME log of event-catalog §6.11 rule 11 — is that root, while the dota2 DEATHS root of §6.11 rules 1–4 feeds no DAG node, the scope ledger's row) plus the run manifest — and, for a run citing an epoch allocation, its inclusion derived from the epoch log (`Inclusion::from_epoch`, allocation-market.md §8: the row's offset is in every stream's lineage, so a re-run needs it) — which is why those are its only declared inputs. The ingest role has two batch sources into the SAME CS2 match log: demo files and the OpenCS2 parquet archive (the quarantined opencs2-edge adapter, event-catalog §6.12 — the training-corpus path). **The three genuinely hard nodes:** Demo Ingestor (data quality), Trained Model (the hypothesis), Matching Engine (correctness invariants). Everything else is thin plumbing over the stream core. ### Model feature contract (spec §5) The CS2 Feature Extractor (spec §5; the LoL, Dota2 and synth extractors each emit one perspective vector per snapshot (synth the one-lane `synth/v1`), model-plugin §8; the run manifest's title selects which one the runtime binds and its `board` selects one per family column, each emitting its own grid into the one feature stream) emits two declared feature sets: | Model | Features | |---|---| | Baseline (`baseline_v1`, frozen simple model) | current kills, players alive, round time, bomb state, team side | | Trained (`gbm_v1`, quantized gradient-boosted model) | all baseline features plus player health and armour, equipped weapons, player positions, recent kills, spatial and numerical team advantage | Team and player identity are features of neither model (team-agnostic by spec). Both output the full distribution P(final kills = 0..5), truncated so that final ≥ current kills and renormalized. ### Bookmaker risk state (spec §6) Each bookmaker's folded state is explicit, not implied: **starting bankroll, current inventory per market, maximum liability cap, and the shared spread and inventory rules.** Both bookmakers run identical rules and identical starting parameters — they differ only in which probability stream drives them. Collateral for worst-case liability is checked before a quote is emitted. #### The quote fold — normative fixed-point definition One implementation, instantiated per sleeve. House sleeves are driven by the probability streams the model registry binds to them (model-plugin-layer.md §9); external quoting sleeves are driven by recorded forecasts and client ladders (event-catalog §6.6) entering through the sanctioned impure edge — same fold, same rules (external code-in-the-loop quoting clients are in scope). All arithmetic is integer. Probabilities are Q.16 (`Σ p[k] = 65536`, model-plugin-layer.md §3); prices are computed in Q.16 mills; a unit claim pays `payout[k]` mills at outcome class `k` — the market family's recorded payout rule as DATA (a `PayoutVector`: one settlement price per class on the 0..=1000 grid, width `n` = the family's class count, 1..=8). For the CS2 kills family `payout[k] = 200·k` (the k/5 settlement grid, event-catalog.md §6.8 / flows.md Flow 3, over the SC-1/SC-2 kill count as enforced by `cs2/kills_over_5/v1`, homed in `ingest::cs2::claims::kills_payout`); for a match-winner family `payout = [0, 1000]`. The fold is title-neutral: the title router binds the vector to each market from its `market_created` terms and passes it per tick; the fold never resolves a `payout_rule` string and never spells a title's grid. A distribution whose width is not the market family's is halted before pricing (a binding error, never priced); likewise a market whose recorded `payout_rule` is not one the title router knows halts at binding rather than being priced against a guessed vector (the clearing posture — halt, never guess). The vector a market is first priced against is pinned for the market's life (`payout_rule` is an immutable `market_created` term, event-catalog §6.9 — the pin is taken on the first `quote()` call for the market, priced or silenced): a later tick offering a different vector halts (`PayoutRuleChanged`) rather than quoting two economics on one book, which the engine could never detect. `isqrt` is the integer square root `⌊√x⌋`. The fold consumes its inputs in the §2 merge order (matching-engine.md §2): at equal replay_ts, lifecycle markers precede the tick's distributions, so a closing market is closed in the fold before its distribution arrives — the never-rejected guarantee below is conditional on that ordering, which the runtime's tick loop provides. On a BOARD the loop runs LC for EVERY column, in the board's canonical order, before any column's FX/MOD, then FX → MOD per column in that order, then BM — so the guarantee holds board-wide (every column's closes are folded before any distribution of the tick), and within a column models evaluate ascending by ModelId as before. Given a distribution `p[0..n)` at `state_version` V for market M (`n` = the market family's class count; `p[0..=5]` on the kills family), the family's payout vector `payout[0..n)`, and the sleeve's own net inventory `net` in Higher units (positive = long): 1. `fair_q16 = Σ p[k]·payout[k]` — expected settlement value (`Σ p[k]·200k` on the kills grid). 2. `var_q32 = (Σ p[k]·payout[k]²)·65536 − fair_q16²`; `std_q16 = isqrt(var_q32)` — payout standard deviation in Q.16 mills. 3. `half_q16 = max(HALF_MIN, std_q16 >> SPREAD_SHIFT)` — the variance-aware half-spread: 2^−SPREAD_SHIFT standard deviations, floored. 4. `shade_q16 = clamp(−net·SHADE_PER_UNIT, ±SHADE_CAP)` — inventory shade: long inventory shifts both quotes down (sell faster, buy slower); short shifts up. 5. `mid_q16 = fair_q16 + shade_q16`. 6. `bid = floor_grid(mid_q16 − half_q16)`, `ask = ceil_grid(mid_q16 + half_q16)` — outward rounding to the QUOTE_GRID (rounding never narrows the spread). A side falling outside `[QUOTE_GRID, 1000 − QUOTE_GRID]` in either direction is omitted (shade can push a boundary ladder past either end); if both survive, `bid < ask` holds structurally (outward rounding of a positive spread on a shared grid). 7. `std_class_q16 = isqrt((Σ p[k]·k²)·65536 − (Σ p[k]·k)²)` — the outcome-CLASS-index standard deviation (kills on the CS2 grid; at most 0.5 on a binary family), a function of the family's class axis alone, never of the payout values. Precondition on the family: its classes are ORDINAL and its payout vector non-decreasing in the class index (true of kills and of a 2-class win/loss family; a family violating it must not bind this fold without amending this step, and the blotter's worst-case columns — the lowest and highest payout — are `payout[0]` and `payout[n−1]` under the same precondition) — which is why a 6-class kills vector sizes byte-identically to before the payout became data; `size = floor_to(SIZE_GRID, SIZE_MAX·SIZE_SOFT / (SIZE_SOFT + std_class_q16))` — confidence-scaled size, identical on both sides. 8. Worst-case pre-check (the "checked before a quote is emitted" law): `headroom = ceiling − Σ_open max(posted, 0) − Σ reservation − max(−realized, 0)`, the reservation over the sleeve's own resting quotes across every market **except the one being quoted** (the §4 step-2 cancel leg removes those levels before the step-3 check — matching-engine.md §4/§6); R1 v2 since gate G1 (risk-engine.md §9), the exchange's own `quote_collateral_headroom`, v1 before it `ceiling − Σ posted − Σ reservation`; the ladder shrinks by the **same greedy rule as matching-engine.md §4 step 3** — one implementation, shared with the engine, so a bookmaker batch is never capacity-rejected: what the fold emits is what the engine accepts. Shrunk to zero → the batch degrades to a pure cancel (matching-engine.md §8) and the tick is silent with reason capacity. The fold also folds every sleeve's accepted-batch version: a tick whose state_version is below the book version (matching-engine.md §4 step 1 — another sleeve may have advanced it) is skipped as silent-stale, a deliberate skip rather than a divergence, so an engine rejection of an emitted batch remains proof of mirror divergence and halts. The guarantee has a second precondition — the house BM step runs AT ITS MERGE SLOT: every record whose merge key precedes the batch's `(tick_ts, class 2)` (a deferred order's fill among them, and this tick's own lifecycle and settlement records at class 0 and 1 — a market closed or voided at tick_ts drains before the batches, so a same-sleeve market quoted in that tick sees the freed reservation) has folded into the mirror and the engine before the batch is computed, and the records at or after `(tick_ts, class 2)` follow it. The tick order is therefore pre-drain → ADV → LC → FX → MOD → DR → MG → ME/CL over the records before the slot → BM → ME/CL over the rest → TP; every merge key is unchanged. The defect it closes: priced before the merge, the step missed a deferred fill's cash — a bid-only ladder at a binding ceiling, sold whole by an order the strict join deferred into the next state tick, left the engine's headroom below the bid's unit cost while the mirror still fitted the bid, so the engine answered `quote_rejected{capacity}` and the fold halted `MirrorDiverged`. One observable consequence: a deferred external quoting record (class 3, stamped before the tick) now drives before the house in its tick, so that tick's cards and emissions list the external sleeve first. **The external drive rule and the client-ladder entry.** An external quoting sleeve is driven by its recorded order-log ingress (event-catalog §6.6 `forecast_submit`/`ladder_submit`) instead of a bound probability stream: one batch per record. A `forecast_submit` runs the full fold above (steps 1–8) with the recorded `p`; a `ladder_submit` skips the pricing steps and **enters at step 8** with the recorded levels — the same headroom mirror, the same shared greedy shrink, the same resting-mirror replace (an empty ladder is the pull: the always-admissible pure cancel). The sleeve's one driver is its recorded ingress; the system's pull at a feed stall — a zero-level batch the scheduler appends for every bound sleeve with resting levels on a market Open (risk-engine.md §3 R7) — is not a second driver: it carries no levels and drives the pure cancel alone. Four rules are normative: 1. **Merge-position consumption, stamp-named version.** A quoting record enters the tick's merge at its recorded key `(replay_ts, class 3, order log, offset)` — exactly like an order — and drives the fold INLINE when the merge reaches it; the batch (if any) folds into the engine at exactly that slot in the global sequence, is appended to the sleeve's quote stream stamped with the record's `replay_ts`, and is tagged with **the version of the state the record's stamp names** (the latest emitted state at or before the stamp — matching-engine §2: stamps only ever come from game-state events). Every input to the batch is therefore a pure function of the recorded data, so live serving and the headless re-run produce it byte-identically no matter which tick a live drain happened to assign the record to — the same argument that makes orders drain-invariant. **Tick assignment itself is made deterministic by the STRICT, STATE-GATED join** (the dev-kickoff note §1): a record joins the first STATE-emitting tick whose ts strictly exceeds its stamp, and the live driver holds drained records across non-state ticks to match — without this, a kill tick emitting no game-state version shares a stamp with the next state tick, and a live taker's fill folds into the house net one tick apart from the headless re-run (a single quote ladder diverges). At the record's slot the book version never exceeds the stamp-named version: the stamp-named state's house batches folded at or before the record's own state-emitting merge tick (the strict state-gated join places the record no earlier than that tick, where those batches — class 2 — sort ahead of it; a silent house tick leaves the book version lower still; later states' batches sort later), so the matching-engine §4 step-1 stale check stays a fold-side pre-skip, never a same-tick race. The record's `seen_version` is recorded client intent only, and never the batch tag. 2. **Structural validity is edge-enforced**: the gateway pre-validates a ladder with the engine's own §4 step-1 structural check (one implementation, exported by the exchange crate) before recording it, so the never-rejected mirror guarantee extends to client ladders; a recorded ladder failing the fold's re-check is corrupted history and halts (gateway-protocol §5 code 12 posture). 3. **An unpriced market halts; an off-scope market halts; a retired market skips; a phantom market halts** (the scope clause). Ahead of all of them sits the sleeve-index read — a defensive typed halt (`UnknownQuotingSleeve`, the gateway-protocol §5 code 9 posture) on the manifest-built `bookmakers` vector the ingress partition validated in the same tick, so unreachable. The quote-stream append (`append_quote`, one home for the house arm, the suspension pull and the external drive) carries its own typed halt, `MissingQuoteStream` — one quote stream per DECLARED sleeve, the stream set and the bookmaker list derived from the one manifest in `Scheduler::new` — a construction invariant the fold checks rather than assumes, unreachable while both come from that one manifest, not a wire fact and not gateway-protocol §5 code 9's posture. The PRICED test first: a created market no column claimed halts `UnpricedMarket` on either row (unreachable for a market the run's own columns created — every `market_created` is its column's, pinned — so the corrupted-history posture). Then the scope: a record whose sleeve does not COVER its market — an external sleeve the run manifest scopes to one family of a board (model-plugin §12, BK-4 on an external sleeve) naming another family's market — halts (`RuntimeError::QuotingScope`), checked ahead of the retirement test (only the PRICED test runs before it), so a retired off-scope market halts instead of vanishing, and guarded on the market being PRICED, so a never-created market keeps its own halt below whatever the sleeve's tag; the gateway refuses the same frame wire-side before any append (gateway-protocol §5 code 17), so meeting the record is corrupted history, and the predicate — the sleeve's column against the market's, the scheduler's ONE scope rule (`Scheduler::column_covers` by the index the fold holds; `sleeve_covers` by sleeve id at the gateway) — is pure in the manifest and the recorded `market_created` records and only widens over the run, so it has no race of the retirement kind. Then: a record whose market settled (and so retired from the fold, state-layers §5) before the merge reaches it is skipped without a batch — a live race the gateway cannot prevent (its known-markets set only grows), and deterministic because retirement is itself a fold of the streams. A record naming a market that never existed halts (corrupted history — the gateway validates existence before appending, gateway-protocol §5 code 7): a `ladder_submit` through the fold's UnknownMarket, a `forecast_submit` one step earlier at the payout binding (`UnpricedMarket` — the title router indexed no family for the market, so there is no vector to price against; a created market no column claimed halts on either row at the PRICED guard above, before the fold is touched). 4. **POSITION-CONSISTENT status**: the external fold's quote decision reads the market's status AT THE RECORD'S MERGE SLOT, not the fold's end-of-LC status. The runtime syncs it from the engine (which processes lifecycle markers in merge order) before driving. This matters under the defer-to-state-tick order rule: a forecast deferred into a closing tick sorts AHEAD of the `market_closed` in the merge, so the engine holds the market OPEN at its slot and accepts its ladder — the fold must agree, or a client's last quote before a round end is wrongly silenced (and the fold's end-of-LC status is already Closed there). This is the same position-consistent-status principle the model-plugin §11 suspension pull uses; the house BM step, priced at its merge slot (step 8), keeps the fold's end-of-LC status (LC still precedes BM; a closing market must not be quoted by the house). Constants (run-manifest-pinnable): `QUOTE_GRID` = 10 mills, `SPREAD_SHIFT` = 4, `HALF_MIN` = 2 mills, `SHADE_PER_UNIT` = 2^12 (1/16 mill per unit), `SHADE_CAP` = 100 mills, `SIZE_MAX` = 600 units, `SIZE_SOFT` = 2^18 (4.0 outcome classes — kills on the CS2 grid), `SIZE_GRID` = 100 units. Worked example — the Flow 1 tick at state_version 482, flat inventory (shade 0), both derivations exact: | | Trained (gbm_v1) | Baseline (baseline_v1) | |---|---|---| | `p` (Q.16) | [0, 13107, 28836, 17695, 4588, 1310] | [28836, 2621, 3277, 3277, 6554, 20971] | | `fair_q16` | 29 753 200 (453.998 mills ≈ $0.454) | 30 015 400 (457.999 mills) | | `var_q32` | 147 237 933 920 000 | 866 872 662 680 000 | | `std_q16` | 12 134 163 (185.15 mills) | 29 442 701 (449.26 mills) | | `half_q16` | 758 385 (11.57 mills) | 1 840 168 (28.08 mills) | | bid / ask | **440 / 470** | **420 / 490** | | `std_class_q16` | 60 670 (0.926 kills) | 147 213 (2.246 kills) | | size | **400** | **300** | These are Flow 1's quoted numbers: the trained bookmaker's $0.44/$0.47 × 400 and the baseline's wider $0.42/$0.49 × 300 from the same rules over a more diffuse distribution. The asymmetry of 440/470 around fair 453.998 is outward grid rounding, not shade (inventory is flat in the example). After the Flow 2 fill (the sleeve sells 100 at $0.47, net −100), the shade is +100·2^12 = +6.25 mills and the next flat-state tick quotes 440/480 — the short bookmaker bids the same but asks more. ### Scope ledger — what is in scope and what the design keeps open The growth path — from the demo system through the live cell, the real-money pilot and the onchain underwriting exchange — is mapped phase by phase in the real-world alignment note (gitignored, local-only). An item out of scope has its phase assigned there. | Item | Scope | Rationale | |---|---|---| | Stream core, replay, feature extractor, both models, both bookmakers, CLOB, ledger, settlement, trading screen | In scope | Spec §4–§9 and §12 deliverables | | Verification taps, determinism harness, hash-chained lineage | In scope | These are how the spec §10 zero-tolerance KPIs are checked — determinism is verified by re-run plus hash diff, so lineage is the mechanism, not extra scope | | Fixed-point transitions, canonical serialization, quantized canonical model | In scope (near-zero cost) | Required for byte-identical determinism anyway; also keeps the ZK door open | | Merkleized state roots at checkpoints | Out of scope — the segment footer RESERVES the slot (`state_root`: one flag byte, plus 32 bytes when set, written at seal — event-catalog §4) and every production writer seals it ABSENT; the hash chain is the only committed digest, no checkpoint event exists (state-layers §2's `Checkpointed` state has no producer) and no digest is signed (the one signature the system verifies is a customer's relayed instruction, matching-engine §5); the verifiable-order-book design fills it | The reserved slot keeps proof-carrying checkpoints possible at zero cost; filling it is the verifiable-order-book design's work, not a determinism requirement | | Allocation Layer — the epoch document, the epoch log, the allocation fold B0–B15 and its composition, the ceiling route, the epoch registry (allocation-market.md; as a chain of blocks with the epoch document as config snapshot + rule chain + system hash, the config loader edge and the fixture emitters — futarchy-system.md) | In scope: `crates/futarchy` — the epoch document's one encoder and closed, total decoder, the two hashes and the genesis anchor, the §3 checks and the transition checks over a typed registry answer, the declared chains B0–B15 and R0–R2 as data with computed identities, the E1 fixture (`emit-fixture futarchy_epoch`); the records with minted ids and the exact arithmetic (`Nat`, `BigRational`); B0–B4 (`fold_to_allocation`) over the E2 fixture log, reproducing the instance's allocation; B5 the ceiling route — run-manifest field 14 `allocation_ref` (omit-at-default), `futarchy::check_allocation_ref`, the typed `Inclusion` derived from the sealed epoch log (`Inclusion::from_epoch`) gating `Scheduler::new_included`, the row's offset in every derived stream's lineage (`included_at`), `fold_through_inclusions`; B6–B11 (`return_observed`, `window_closed`, `utilization`, `winsorized`, `estimate`, `feasible_set` — `winsorized` and `estimate` (v1/v2 chains; the v3 chain scores the unfiltered series — the note's initial specification)); B12 (`settlement`, the objective of record, `verify_settlement` — the tap before the solver; `fold_settlement`, the exact-rational active-set solver under §9 C47's pinned conventions; `settlement_reproduces` the chain-level tap); B13 (`fold_pool_liability` — the liabilities on the raw share, exact); B14 (`fold_stake_payouts` — one `stake_payout` per net position in B1's first-row order, `⌊r_ij P_i / R_i⌋` floored once); B15 (`fold_epoch_settled` — `tax_amount = ⌊T_tax⌋`, the one rounding charge, the four amounts re-adding to `R`); the composition (`fold_to_settlement` — B0–B15 over one epoch log and the post-close edge inputs `PostClose`, B1's positions carried to B14 from the first segment's one pass, B13/B14/B15 only on a closed window, B7's sleeve map one source per run keyed on the verdict; `payouts_reproduce` the second chain-level tap; futarchy-system.md §10's fixture gate holds end to end on the two real runs and on the three-title shape (lol, dota2 and cs2, `dev-fixtures::futarchy_three_title_epoch_v3`); the real-run fixture lives in `dev-fixtures::futarchy_served_runs`); the epoch registry (`futarchy::registry` — `rebuild`, `admit`, `predecessor`: the replay of admission over the stored epochs alone, as of an opening; the successor fixture `futarchy_successor_document`, the first Γ_C instance off the identity). An included run is STARTED by the runtime binary and the gateway host from the epoch store on disk (`--epoch`, `--epoch-store`; `runtime::epoch_store` the one read, `futarchy::inclusion_from_store` the pure loader — futarchy-system.md §10, §11 W10). The cycle is RUN by `d2-cycle` (`crates/cycle`) — the epoch gateway's operator half over a cycle directory (futarchy-system.md §7, §11 W13/W14/W15), the three-title cycle its gate; its `report` step reads through `crates/projection` (the record catalog over `stream_core::desc` and the JSONL projection `d2s cat`, event-catalog §8), which depends on stream-core and every record-minting crate, never on `verify`, and on which `cycle` and `scenario` depend (the validation outlet `d2-scenario`, futarchy-system.md §7 "The scenario runner"). The layer's edges, interfaces (futarchy-system.md §7) and not part of the fold — E2's chain reader and E5 fed by fixture emitters, the gateway's operator half a binary (`d2-cycle`, the fixture emitter kept for the in-process gates): the epoch gateway's writes (`epoch_opened`, the chain tail, `run_included`, `run_verdict`, the allocation stream persisted into the store) and the payout publisher (E5: the `stake_payout` rows and `epoch_settled` / `epoch_aborted`, submitted to the chain's vault). Every open value of the layer is a CONFIG default with its register row (CLAUDE.md law 7(c); futarchy-system.md §9); the remaining gates are futarchy-system.md §10's | Core law 7: the layer is written downstream of the private authority notes and every open value is theirs; specifying it before code makes the ceiling route, the facts-only custody and the exact-rational settlement reviewable | | The risk engine as chains — the admission chain's declared blocks R4, R9, R2 v2 (a producer since gate G10), R1 v2 (a producer since gate G1) and R7 (a producer at v1 since gate G8 and at v2 since gate G11), the desk-period chain R5, R6, R6b (producers from gate G3) and R8, the credit-economics chain RC1–RC6 (declared at v1, the futarchy fold its producer under a `caps_rule` v5 document), the leverage tap, the top-up, the sleeve suspension and the cross-run stop (risk-engine.md) | In scope as a SPECIFICATION: four admission blocks have producers: R0, R1 v2, R2 (v1, and v2 since gate G10), R7 (v1, and v2 since gate G11); the desk-period chain's R5, R6 and R6b have the futarchy fold as their producer at v1 from gate G3; the sleeve records' producer is the operator gateway from gate G5, appending the operator's frames and, from gate G9, the desk watch's (risk-engine.md §8 E3, E7); R4, R8 and R9 are declared or reserved and the credit-economics chain RC1–RC6 is declared at v1 with the futarchy fold as its producer under a `caps_rule` v5 document, their gates named (risk-engine.md §9; futarchy-system.md §10 "The credit gates") | law 8: every piece a block with one interface; the thesis's risk engine as a sole authority | | Operator-routed retail orders — operator sessions, customer subaccounts, Ed25519-signed instructions, resting (GTC) retail orders, no selling of units not held (gateway-protocol §2–§3, event-catalog §6.6–§6.7, matching-engine §3–§6, state-layers §6) | In scope as a SPECIFICATION | Signature verification lives in the exchange order fold: `verify_strict` is a pure function of recorded bytes, so every replayer re-verifies and the determinism rules hold (no clock, no RNG); the gateway decodes no instruction and verifies no signature. The one dependency is `ed25519-dalek`, pinned once in the workspace manifest with its RNG feature off — the exchange uses verification alone, and signing (RFC 8032's deterministic signature, which reads no RNG) is the key holder's: the customer's, or the fixture emitter's spec-derived keys (core law 3) | | Exchange/Clearing logical split (streams-only seam, settlement feedback edge, round-open barrier, collateral-view equality checker) | In scope | Determinism-safe by construction; it is the matching-vs-clearing seam real-money certification requires, and it is nearly impossible to retrofit cleanly | | Physical split of Exchange Runtime and Clearing House into separate processes | Out of scope | Deployment change only — no code path assumes co-location | | Live ingestion decomposition (Flow 4), live simulator | Out of scope — except the single-session GRID Series Events capture (`d2-grid live`, event-catalog §6.13 rules 12–13), in scope and deliberately NOT the Flow 4 decomposition (no watermark / resume / backfill; its stall detector writes the feed-health records, risk-engine.md §3 R7; flows.md Flow 4 deviation) | Spec §12: live data only after the pricing hypothesis passes | | Round restore / market void (Flow 5) — the restore TRIGGER and the settled-then-voided reversal | Out of scope | Needed only for live trading; the mitigation in scope: preflight marks backup-restored rounds ineligible so no market ever opens on one; a void after settlement halts typed (`VoidAfterSettlementUnimplemented`) | | Void unwind with open interest: settlement-contract SC-V — `sys/void_refund/v1` (1_000_029) from `clearing::Ledger::void_market` / `ClearingEngine::try_void`, staged and applied atomically at `void_complete` by `exchange::CollateralView::void_market` and the bookmaker's own sleeve fold | In scope | Σ posted = pot (matching-engine §6) makes one refund row per party fund the unwind exactly — no per-fill reversal; needed by every intra-match line family (a tie at the crossing frame, a line never reached) and by a cs2 round restored before settlement | | Race-to-N-kills family: settlement-contract SC-8 — `replay::race_to_kills` (`RaceTracker`, the ONE race fold; `RaceToKillsPolicy` over the `RaceTitle` seam LoL and Dota2 implement — the seam's per-match frame reader is `replay::lol::LolFrameReader` on LoL and `replay::dota2::Dota2FrameReader` (the roster binding `ingest::dota2::roster_side::RosterBinding` plus the objective tally) on Dota2, whose frame is per-slot; sequential lines as consecutive epochs), `clearing::race_to_kills::RaceOutcomeSource` (the match log through the title's own projector into the same fold), `bookmaker::binary::BinaryRouter>` (the match-winner router generalized over the `BinaryFamily` terms seam), `pricing::feature::lol_race` / `dota2::race` (`lol/race-v1` / `dota2/race-v1` = the title grid + the line and each side's kills-to-go; each extractor prices the line the policy has live), the run manifest's `family` + `race_lines` (`runtime::scheduler::title` binds the race column per (title, family)), the `lol-race-export` / `dota2-race-export` corpus arms, the SDK's `RaceTerms` / `threshold` | In scope (`runtime/tests/race_serve.rs` / `dota2_race_serve.rs`); the frame read and fold shared with the kills-at-minute family (`replay::frame_kills`) | The first intra-match line family: the crossing is decided on the per-frame snapshot (LoL's per-minute `state/v2`; Dota2's betty frame at the raw tick, its per-slot kills summed through the roster — frame-granular, SC-8 rule 2), a tie or an unreached line voids through SC-V; a run offers a BOARD of families: the manifest's `board` (model-plugin §12, MF-13 / BK-4; a single-family manifest keeps its bytes and `run_id`), the settlement BARRIER names its family and the clearing engine runs one column per family (event-catalog §6.8, settlement-contract SC-7 rule 4 — epochs are family-local; the exchange, the bookmaker and the lifecycle fold retire a SETTLEMENT barrier by (family, epoch), a void barrier by its named market_ids), the SCHEDULER runs one column per family (`runtime::scheduler::board` — one lifecycle fold, extractor and router per family over the one projector, each model evaluated on its own column's grid, the house setter per family; the equivalence pin `runtime/tests/board_serve.rs`: a board serves each family exactly as its single run does), and the MATCH WINNER is a board entry with no lines (`Family::MatchWinner`, bound to the default market's plug-ins; alone it is the empty board and is refused — one definition, one byte form). Proven end to end on the committed race fixtures (`runtime/tests/race_serve.rs` and `dota2_race_serve.rs`: a tie's unwind, a settled line's payout, an unreached line's unwind, both taps, byte-identical twice) | | Kills-over-N-at-minute-M family: settlement-contract SC-9 — `replay::kills_at_minute` (`MinuteTracker`, the ONE minute fold over the shared `replay::frame_kills::FrameFold` — the match fold, the title's per-match frame reader, the frame order and the frame ORDINAL, which the race fold drives too; `KillsAtMinutePolicy` over the `KillsAtMinuteTitle` seam LoL and Dota2 implement; sequential minutes as consecutive epochs), `clearing::kills_at_minute::KillsAtMinuteOutcomeSource` (the match log through the title's own projector into the same fold), `bookmaker::binary::BinaryRouter>`, `pricing::feature::lol_minute` / `dota2::minute` (`lol/minute-v1` / `dota2/minute-v1` = the title grid + the live line's minute and kills, the minutes to go, the total and its margin), the run manifest's `family` + `minute_lines` (`runtime::scheduler::title` binds the minute column per (title, family)), the `lol-minute-export` / `dota2-minute-export` corpus arms over the ONE `trainset::line_dataset` pair of passes, the SDK's `MinuteTerms` / `minute` | In scope (`runtime/tests/minute_serve.rs` / `dota2_minute_serve.rs`) | The first family settled on a NAMED frame: the line closes on the snapshot whose ordinal within the match IS its minute (the game minute on LoL, the M-th sampled frame on Dota2), OVER on a total of N + 1 or more and UNDER otherwise — a partition with no push, so the only void is the match ending before the minute (SC-V); the race and the minute family differ only in what they decide per frame, everything below the decision being one fold. Proven end to end on the committed fixtures (a paid over claim, an under claim settled at 0, an unreached line's unwind, both taps, byte-identical twice) | | Side-kills-over-N-at-minute-M family: settlement-contract SC-10 — `replay::side_kills_at_minute` (`SideMinuteLine{minute, kills, team}`, `SideKillsAtMinute`, the ONE minute fold `replay::kills_at_minute::MinuteTracker` made generic over WHAT a line is judged on — `MinuteJudged`: the total for SC-9, the team's own counter here; `SideKillsAtMinutePolicy` over the `SideKillsAtMinuteTitle` seam LoL and Dota2 implement), `clearing::side_kills_at_minute::SideKillsAtMinuteOutcomeSource`, `bookmaker::binary::BinaryRouter>`, `pricing::feature::lol_side_minute` / `dota2::side_minute` (`lol/side-minute-v1` / `dota2/side-minute-v1` = the title grid + the live line, ITS TEAM, the minutes to go, the team's count and its margin), the run manifest's `family` + `side_minute_lines` (MF-9 `[minute, kills, team]` triples; `runtime::scheduler::title` binds the side column per (title, family)), the `lol-side-minute-export` / `dota2-side-minute-export` corpus arms over the ONE `trainset::line_dataset` pair of passes, the SDK's `SideMinuteTerms` / `line_team`, the `lol-sideminute` / `dota2-sideminute` serves | In scope (`runtime/tests/side_minute_serve.rs` / `dota2_side_minute_serve.rs`) | The family differs from SC-9 in the judged count alone — the minute fold, its lines rule and its events are generic over that count rather than a second fold: two families, one body. Proven end to end on the committed fixtures (blue's line paid, red's line paid to the house where the total would have paid the claim, an unreached line's unwind, byte-identical twice) | | Gold-lead-over-N-at-minute-M family: settlement-contract SC-11 — `replay::gold_lead_at_minute` (`GoldLeadLine{minute, gold, team}`, `GoldLeadAtMinute`, the ONE minute fold generic over WHAT THE FRAME YIELDS — `MinuteJudged::Quantity`, `replay::frame_kills::FrameSource`: the kills pair for the kills families, `Gold` here, read on LoL from the ten `total_gold` fields of `state/v2` and on Dota2 from the roster-summed `net_worth`; `GoldLeadAtMinutePolicy` over the `GoldLeadTitle` seam), `clearing::gold_lead_at_minute::GoldLeadAtMinuteOutcomeSource`, `bookmaker::binary::BinaryRouter>`, `pricing::feature::lol_gold_lead` / `dota2::gold_lead` (`lol/gold-lead-v1` / `dota2/gold-lead-v1` = the title grid + the live line, its team, the minutes to go, the team's SIGNED lead and margin), the run manifest's `family: FamilyLines::GoldLead` (MF-10 `[minute, gold, team]` triples; the manifest's family WITH its lines one value), the `lol-gold-lead-export` / `dota2-gold-lead-export` corpus arms, the SDK's `GoldLeadTerms`, the `lol-goldlead` / `dota2-goldlead` serves | In scope (`runtime/tests/gold_lead_serve.rs` / `dota2_gold_lead_serve.rs`) | The first minute family judged on a quantity other than kills: the frame seam is generic (`FrameSource`, `Frame`, the manifest's `FamilyLines`) and the family adds a quantity and a line type; the market itself — the team's gold lead over N (N = 0 is "the team is ahead", a level game UNDER; a median-lead line is a ~25 % market) — settles on the side serves' numbers to the mill on the committed fixtures | | Tally-over-N-at-minute-M families: settlement-contract SC-12 — the seam: the projectors FORWARD `structure_destroyed/v1` / `objective_slain/v1` (event-catalog §6.15.1 / §6.11.1), each title's frame READER (`replay::lol::LolFrameReader`, `replay::dota2::Dota2FrameReader` — the roster binding plus the tally) folds them into `replay::tally::Tally` (towers taken per side, dragons and barons slain by NAME), `FrameSource` reads it at each frame, and `FrameFold::last_frame` hands every minute grid the fold's OWN frame value (one read for the settlement and the lanes); the family: `replay::tally_at_minute` (`TallyLine`, `TallyAtMinute` — ONE generic family over the kind tag `Towers` / `Dragons` / `Barons`, `TallyAtMinutePolicy` over the `TallyTitle` seam), `clearing::tally_at_minute`, `BinaryRouter>`, `pricing::feature::lol_tally` / `dota2::tally` (`lol/-v1`, `dota2/towers-v1` = the title grid + the live line, its team, the minutes to go, the team's count and margin), the run manifest's `family: FamilyLines::Towers / Dragons / Barons` (ONE wire field MF-11 `[minute, count, team]`, paired by FIELD), the `lol-tally-export --kind` / `dota2-tally-export` corpus arms, the SDK's `TallyTerms`, the `lol-towers` / `lol-dragons` / `lol-barons` / `dota2-towers` serves | In scope (`runtime/tests/tally_serve.rs` / `dota2_tally_serve.rs`) | The first quantity NO snapshot field carries: the game-state stream carries the events first, and three markets ride one family's code | | House-line families — the final total kills and the kill handicap over a line the HOUSE sets: settlement-contract SC-13 — the engine: `replay::house_line` (`HouseKind` / `Total` / `Handicap` — ONE generic family over the kind tag, `HouseLine` judged on the last frame's kills, `HouseTerms` = the manifest's `[open_minute, lo, hi]`, `HouseFold` — the frame fold plus the opening flag and the LAST frame — `HouseLinePolicy` creating the ONE market per match on the OPENING snapshot with the line the `LineSetter` seam chooses), `clearing::house_line::HouseLineOutcomeSource` (no setter, no model — the line read off the recorded descriptor, the outcome the final frame), the docs (SC-13, §6.9, state-layers §3, flows.md Flow 3, this edge); the product: the pricing setter over the run's house model (P(OVER) nearest ½ over the candidates), `/total-v1` / `<title>/handicap-v1`, the manifest's MF-12 `house_line`, the runtime bind (ONE setter value shared by the policy and the extractor), the corpus arms, the SDK's `HouseLineTerms`, the `lol-total` / `lol-handicap` / `dota2-total` / `dota2-handicap` serves | In scope (`replay/tests/house_line_policy.rs`, `clearing/tests/house_line_settlement.rs`, `runtime/tests/house_line_serve.rs` / `dota2_house_line_serve.rs`, `pricing/tests/house_line.rs`; the four house models gated on validation) | The manifest field the family rows name (`family` + its lines field) is the run's `board` (the race-family row). The first family whose line the operator does not pin — the line is DERIVED on the opening snapshot from the recorded house model and terms (core law 5), so creation moves off the envelope; the frame is the truth of the final count (the Dota2 completion's scoreboard runs above the last frame) | | Dota2 ingestion slice (event-catalog §6.11): second title's match-log root → canonical segments + one demonstration predicate | In scope (bounded) | The pre-registered test of the title-agnostic thesis — pass = zero stream-core diff. Ingestion only; it terminates at canonical segments, so it adds no node to the DAG above. Dota2 markets, lifecycle, settlement: the match-winner row below; live serving: the title dispatch row | | The match-winner market: the `<title>/match_winner` claim family LoL, Dota2 and synth share — `replay::match_winner::MatchWinnerPolicy` over the `WinnerTitle` seam each title module implements, `clearing::match_winner::MatchWinnerOutcomeSource`, `bookmaker::binary::BinaryRouter` over the shared `bookmaker::index::SideIndex` (cs2's router refactored onto it, routing unchanged), the projectors' forwarding sets (event-catalog §6.11.1/§6.14.1/§6.15.1), settlement-contract SC-7 | In scope (market/settlement; live serving in the title dispatch row) | ONE policy / outcome source / router for every match-outcome title, parameterized by the title's match envelope + completion records; the fold prices the family's `[0, 1000]` vector as data. Proven end-to-end on the REAL GRID LoL game (red won → the blue claim pays 0) and the REAL Dota2 capture with a radiant win (`crates/grid-edge/tests/match_winner_e2e.rs`) | | Runtime title dispatch: the run manifest's `title` (`cs2` omit-at-default / `lol` / `dota2` / `synth`) selects the plug-ins the `Scheduler` binds at every seam — projector, market policy, feature extractor (schema resolved through the title's registry), quote router, outcome source — statically (`runtime::scheduler::title`, one enum per seam; the manifest's `board` binds one such set PER FAMILY COLUMN over the one projector); the §7 wrapper path follows the title; the serve edges refuse another title's match log by its lineage; the betty FRAME projector (event-catalog §6.11 rule 11 / §6.11.1 — `roster_side/v1` leads the log as the frame block's match envelope) | In scope | The title-neutral Scheduler generalization. The CS2 path is byte-identical (the cross-version goldens hold); the headless serve of BOTH house models (the `baseline_v1` recipe bound to `lol/v2` — `340619b7…` — and to `dota2/v2` — `6e79129a…` — the per-title house models, named after their `<title>/baseline-v1` projections) over the corpus tools' own sealed real-fixture segments threads to settlement, byte-identical on re-run (`crates/trainset/tests/title_serve_e2e.rs`); a GRID-fed Dota2 serve stays undispatchable until an extractor reads `state/v1` | | The `synth` title (event-catalog §6.18; task 166 A): the market-dynamics simulation's synthetic evidence feed as a title — the `synth` crate (a title vocabulary outside `ingest`, records 3000–3002, the lineage; depended on by pricing, replay, runtime, projection and dev-fixtures), `replay::synth` (the `WinnerTitle` seam and the verbatim `synth_state` projector), `pricing::feature::synth` (`synth/v1`, one Q16 lane), the `Synth` arms of the runtime plugs, the registered `synth/match/v1` feed tag; and the `actors` crate — a fixture-emitter block OUTSIDE the DAG (law 8(d); depends on pricing, ingest, synth), home of the sampler, the logistic, the feed and outcome rules and the synth log emitter that `emit-fixture synth` drives; serve gate `crates/runtime/tests/synth_serve.rs` | In scope (task 166 A) | Nothing in the matching / pricing / settlement core moves: the title settles the existing match-winner family with no void path; the house is silent in every dynamics run; the actors' rules (part B) are blocks over the venue's public step | | The LoL growth-path slice (Riot MATCH-V5 real corpus, event-catalog §6.15.2/§6.15.3, model-plugin-layer §8/§18 `lol/v1`/`v2`): `riot-edge` + `lol/player_frame/v1` / `lol/state/v2` + the `lol/v1` feature schema + the `trainset` corpus builder/CLI + model-format-v2 `n_classes` + the training/gate-4 CLI infra (`lol-schema`, `admit` resolving `lol/v1`, `lol-backtest`) + `lol/v2` = v1's grid + the `lol/baseline-v1` projection for a CS2-style gate-4 baseline, with `lol-backtest` resolving the candidate's schema through the lol registry (no new DAG node — the same `LolFeatureExtractor` serves either schema) | In scope (markets/settlement in the match-winner row; live serving in the title dispatch row) | A SEPARATE batch pipeline from the thesis-test slice — sourced from Riot MATCH-V5, not GRID; DOES add a feature-extraction DAG node (`LolFeatureExtractor`, pricing layer) but its market/lifecycle/settlement node is the shared match-winner family (row above), not a per-title one; the corpus builder is operational tooling outside the DAG (same posture as the CS2 corpus builder) | | The Dota2 growth-path slice (betty-dota2 real archive, event-catalog §6.11 rules 6/9/10, model-plugin-layer §8/§18 `dota2/v1`/`v2`): `player_frame/v1` + `roster_side/v1` + the `dota2/v1` feature schema + the `trainset` corpus builder/CLI + the training/gate-4 CLI infra (`dota2-schema`, `admit` resolving `dota2/v1`, `dota2-backtest`; the same outside-the-DAG posture) + `dota2/v2` = v1's grid + the `dota2/baseline-v1` projection for a CS2-style gate-4 baseline, with `dota2-backtest` resolving the candidate's schema through the dota2 registry (no new DAG node — the same `Dota2FeatureExtractor` serves either schema) | In scope (mirrors the LoL slice) | A SEPARATE batch pipeline from the thesis-test slice above — sourced from the betty-dota2 archive, not GRID; DOES add a feature-extraction DAG node (`Dota2FeatureExtractor`, pricing layer), its market/lifecycle/settlement node is the shared match-winner family (row above); live serving is the title dispatch row (the `dota2` title serves the betty frame log); the corpus builder is operational tooling outside the DAG (same posture as the CS2/LoL corpus builders) | | OpenCS2 training-corpus slice (event-catalog §6.12): parquet archive → the SAME canonical CS2 match log via the quarantined opencs2-edge adapter | In scope | Unlike the dota2 slice it DOES feed the DAG — its output is the MLOG root everything folds over; training and gate 4 run on it. No new events, no new schema ids | | Cadence resampler (event-catalog §6.12 rule 12, `ingest::cs2::resample` + `d2-resample`): an OFFLINE segment→segment transform reducing a materialized match log's tick_state cadence to a coarser stride | In scope | Parquet-free (spine + `ingest::cs2` vocab only), so it runs on the serve box: `resample(materialize(S), N) == materialize(N)` byte-for-byte for S∣N, letting the box serve any run's cadence from one 64 Hz master. Not a DAG node — a pre-serve materialization aid producing the SAME MLOG root; no new events, no new schema ids | | Projections — the record catalog, `d2s cat`, `d2-cycle report` | In scope (read-only outlets; Parquet specified, not built) | Read-only outlets mint no record and no value; they make sealed streams and the cycle machine-readable for the external dashboard (law 4) — event-catalog §8; futarchy-system §11 W15 | | The scenario runner — `d2-scenario two-cycles <out_dir>`, the recorded two-cycle artifact (futarchy-system.md §7 "The scenario runner", §10 G-S1, §11 W16); `d2-scenario operators <out_dir>` (§7 "The operators artifact", §10 G-S2, §11 W17); `d2-scenario report <artifact_dir>`, the run report every verb also writes as `report.json` (§7 "The run report", §10 G-S3, §11 W18); `d2-scenario matrix <out_dir>`, the regression scenario matrix, each committed case an operators artifact with its own report (§7 "The scenario matrix", §10 G-S4, §11 W19) | In scope (a validation outlet; four verbs) | `crates/scenario` depends on `cycle` (as a library — the runner's own steps), `projection`, `dev-fixtures`, `futarchy`, `runtime`, `stream-core` and `verify`, and nothing depends on it; dev-fixtures is law 8(d)'s fixture emitter, and the scenario is its one dependant outside a `[dev-dependencies]` section — a fixture emitter's output through real code (law 3); it mints no record, no schema id and no value, and the dependency graph keeps no cycle: dev-fixtures depends on neither `cycle` nor `projection` | | ZK prover / proof stream | Out of scope | The real-money trust story; the design keeps its door open | | Product comprehension test (spec §10) | In scope, outside the system | Manual UX protocol — see flows.md Flow 6 note; it has no architecture component by nature | --- ## Stream lineage & commitment model Every derived stream records, as part of its output (not documentation): ```mermaid flowchart LR subgraph S["stream metadata (per segment)"] P["parent stream ids + starting offsets"] T["transformer version / model content hash"] H["hash chain: h(i) = H(h(i−1) ‖ canonical_bytes(event_i))"] R["state root slot at seal (reserved and sealed absent; merkleized fold state when a checkpoint producer exists)"] end S -->|"binds to"| M["Run Manifest<br/>seeds · model hashes · pinned lib versions · split definition"] ``` Consequences: - **Verification is mechanical** — re-run any node from its declared inputs, diff output hashes. A tap-of-a-tap stays reproducible after retraining because lineage pins the exact model hash. - **ZK readiness is free** — a prover binds input commitments to output state roots per round; proof-carrying checkpoints replace recompute-from-zero. Design rules that keep the door open: fixed-point transitions, canonical serialization, merkleized state roots (the footer slot is reserved and sealed absent — scope ledger above), quantized canonical model, transformers as small pure functions. ## Timelines, time travel, and forking Every stream is immutable and offset-addressed, so the full system state at any offset is recomputable — that is time travel. But a tick does **not** create a new timeline: per run there is exactly **one canonical spine**, and each `state_version` is a point on it. Every offset is a *legal fork point*; a new timeline exists only when an explicit tap-in diverges, in exactly three ways: 1. **Different transformer** — run `gbm_v2` over the same game-state stream from offset 0: a parallel probability timeline sharing the same parent (model A/B). 2. **Different inputs** — replay with a different order log (counterfactuals): only streams downstream of the changed input fork. 3. **Different starting offset** — tap in mid-history from a recomputed prefix (from a checkpoint once a checkpoint producer exists — the scope ledger; a proof-carrying ZK checkpoint in the verifiable-order-book design). ```mermaid flowchart LR ROOT[["match log — the unforkable root timeline"]] ROOT --> SPINE["run spine: game-state → features → probs → quotes → fills → settlement"] SPINE -->|"fork: gbm_v2 from offset 0"| F1["parallel probability timeline"] SPINE -->|"fork: alternate order log from offset 490"| F2["counterfactual market timeline"] F1 -.->|"shares all ancestry by reference"| SPINE F2 -.->|"copy-on-write: stores only parent id, fork offset, own events"| SPINE ``` Fork semantics (part of the stream contract): - **Copy-on-write, never copy** — a fork stores (parent stream id, fork offset, its own new events). Shared ancestry is referenced, not duplicated. One spine per run plus cheap explicit forks — never a timeline per tick. - **Forks propagate strictly downstream** through the DAG. The exchange lane has a feedback loop (bookmakers fold their own market events — fills and quote echoes), so forking the order log also forks the quote streams from that offset onward. The game-state lane has no feedback — trading can never change what happened in the round — so the match log is the one timeline nothing can fork. - **Determinism bounds divergence** — same inputs + same transformer can never diverge; a "fork" with nothing changed is byte-identical to its parent. ## Determinism rules (enforced, not assumed) 1. No hidden state — transformer state is a fold of declared inputs; snapshots are derivable and discardable, never authoritative. 2. Banned inside transformers: wall clock, unseeded RNG, network calls, hash-order iteration, multi-threaded float inference (models run quantized fixed-point, deterministic mode). The sanctioned impure edges — the gateway, the ingest adapters and the live capture — may read a wall clock where nothing read from it enters a record, a stamp or an id, allow-listed at the call site with a comment naming the edge (clippy.toml); the clock may decide WHEN a recorded record is served (the demo replay host's pacing, gateway-protocol §8, `gateway/src/driver/pace.rs`) or WHETHER the capture appends a feed-health record (the stall detector, event-catalog §6.13 rule 12 (d), `grid-edge/src/net/events_ws.rs`), never any value in one; two sites exist, each counted by its crate's one-site gate. 3. Events are immutable; schemas are versioned — evolve by adding versions, never by rewriting. Corrections are new revision events, not edits. 4. Lineage is part of every output stream.