--- id: CB-WP-0029 kind: product title: "The tokens on the table: components you can count, and a supply the engine may not respect" status: done state_hub_workstream_id: "fb3d3c80-3798-4d0f-91ce-b8ea45943328" --- # Purpose ``` structural tier M (imports another edition file under AM-4's budgets and touches the rendering the coverage gate is written against) chaos d8 = 4 → no override declared tier M ``` **Declaration 12 of chaos window 2 — the last.** The window closes with this pass and its verdict is due; see §The window closes here. ## The ask > *"Let's do the token objects first."* The engine models every token correctly. It shows them as **numbers on a seat card** — `protect 2`, `blamed by P3`, `freedom READY`. A real table has objects you count at a glance. ## What `Tokens.csv` turns out to carry Nine token types, and the columns are more interesting than the names: | token | qty | sides | what the engine has | |---|---:|---|---| | Stress marker | 6 | Single | `stress: u8` — a marker on a 0–5 track | | Freedom | 6 | **Double** (READY/spent) | `freedom_ready: bool` | | DARVO stage | 6 | Single | `darvo: DarvoStage` on an OFF/DENY/ATTACK/REVERSE track | | Relation link | **12** | Single | *"two per player"* — GR-L01's two slots | | Focus / Blame | 6 | **Double** (FOCUS/BLAME) | `focus` map **and** `blame_from` list | | Protection | 6 | Single | `protection: u8` | | Denied | **5** | Single | `denied: bool` per Problem | | Lead | 1 | Single | `lead: PlayerId` | | Round | 1 | Single | `round: u8` | **`quantity` is a component limit, and a simulation does not have one unless it is told.** `protection` is `saturating_add(1)` at `lib.rs:986` with **no upper bound** — the edition ships six Protection tokens for up to six players. T03 is about whether that matters. **`sides` is the second interesting column.** Freedom and Focus/Blame are *one double-sided token each*, not two states. The engine holds Focus in a map and Blame in a list; whether those can disagree with a single physical token is worth checking. ## The window closes here Window 2 opened 2026-08-03 at d8 and runs to twelve declarations. **This is the twelfth.** Its retirement condition — *retire if an override changes nothing twice running* — has been **untestable throughout**: eleven declarations, zero 8s, zero overrides. CB-EV-0024, CB-EV-0025 and CB-EV-0026 each said so. **Recording the verdict is a change to how the loop constrains itself and is therefore its own tier-M pass**, not a task here. This workplan names it as due so it is not lost between passes, which is how the ten rulings went uncollected. ## Task: decide what a token is, here ```task id: CB-WP-0029-T01 status: done priority: high state_hub_task_id: "a7aad543-1c38-43e3-b7d2-d2127b4dc849" ``` `decisions/ADR-0016-*.md` (tier M merges survey and decision). - **Vendor `Tokens.csv`?** ADR-0015's test applies: does it carry text a player reads? `front_text` (READY, DENIED, PROTECTION…) and `use` do. `shape` and `size` are print instructions and do not. - **What is a token in the renderer?** The engine has no `Token` type and should probably not gain one: a token is a *view* of state the aggregate already holds. Deciding otherwise means a second source of truth for Stress. - **Where do they sit?** Player tokens belong with their seat; Denied belongs on its Problem; Lead and Round belong on the table. **The overhead view is what makes that placement meaningful** — a token drawn in a list is a number with a picture. - **Does `quantity` bind?** The decision T03 needs: is the component count a rule the engine must enforce, an artifact of physical production, or undetermined? **Do not answer it here from taste** — T03 measures first. **Done 2026-08-07.** [ADR-0016](../decisions/ADR-0016-the-tokens-on-the-table.md), four decisions, **written after T03 measured**. **D2 — a token is a VIEW, not a type.** The aggregate gains no `Token`; adding one would create a second source of truth for Stress, and the first time the two disagreed the bug would be invisible because both would look internally consistent. `Tokens.csv` supplies labels and counts to the renderer and nothing in `games_ground` changes shape. **D3 — `quantity` does not bind, and the reason is not the measurement.** A component limit the rules do not state is not a rule. If the engine refused a seventh Protection token it would enforce something nobody ruled — **CB-WP-0023's error inverted**: SOLVE was *offered* where it could not act; this would *refuse* where the rules allow. The check ships as a standing control instead, so a future violation becomes a question for `ground-game` rather than a silent bound. **D4 — the tracks are the decision that matters.** `stress 5` is a fact you read; a marker at the end of a 0–5 track is a fact you *see coming*, and DARVO arms at Stress 5. ## Task: tokens as objects ```task id: CB-WP-0029-T02 status: done priority: high state_hub_task_id: "3776359b-942a-4c9f-b9d5-8e54afffa391" ``` Draw them, on the table built in CB-WP-0028. - **Stress on a 0–5 track**, not the number `5`. The track is the thing that makes "one more Attack and I trigger DARVO" visible before it happens. - **DARVO on its OFF→DENY→ATTACK→REVERSE track**, which is the game's namesake mechanic and currently the least visible thing on the page. - **Freedom as a two-sided disc** — READY or spent, using the edition's own `front_text`. - **Protection, Blame and Focus as counted objects** by their seat. - **Denied on its Problem**, not as the word `denied` in a corner. **Controls:** - **every existing coverage probe still passes.** They name facts, and CB-WP-0027 and CB-WP-0028 both showed a fact-naming probe survives a reflow. A third confirmation is worth having; - **the token text comes from the edition**, asserted as a substring of the vendored file — the rule ADR-0015 established and the thing that stops a hand-copied label drifting; - a seat with **no** tokens renders as a seat with no tokens, not as a seat missing its area. The empty case is the one that silently vanishes. **Done 2026-08-07.** Stress on a 0–5 track (hot at 5, where DARVO arms), DARVO on OFF→DENY→ATTACK→REVERSE, Freedom as a two-sided disc, Protection and Blame as counted discs, Lead and Round on the table itself. **Two tests broke and both were fixture defects, not regressions.** `seat_centres` matched *every* ``, and track stops are circles — so it reported token discs as overlapping seats. Seats now carry `class="seat"` and the helper keys on that. **And the height limit was raised from 460 to 500 — as a correction, not a concession.** The 460 had no derivation. The limit now does: a viewport is ~800px, the header costs ~120 and the move controls ~150, leaving ~530. The version that broke dragging declared 620. **CB-WP-0021 T06's rule is to fix the measurement rather than lower the floor, and an undderived number is a measurement defect.** ## Task: does the engine respect the supply? ```task id: CB-WP-0029-T03 status: done priority: high state_hub_task_id: "5fce7cec-91e1-4dba-90b7-1f7f1a463dfc" ``` **Measure before deciding.** For each token with a `quantity`, ask whether the aggregate can exceed it: - **Protection (6)** — `saturating_add(1)` with no bound. Can one seat hold three? Can the table hold seven? **Write the check, run it, report the number.** - **Denied (5)** — five tokens, and a scenario deals at most five Problems. Probably safe; *probably* is not an answer. - **Relation links (12, two per player)** — GR-L01 enforces two slots, so this one is likely already respected. Confirm rather than assume. - **Focus/Blame (6, double-sided)** — one token per player that flips. Can a seat hold Focus *and* have Blame from the same source at once? If so, the model and the component disagree. **Whatever this finds is a finding, not a fix.** A component limit the rules do not state is `underdetermined` and belongs to `ground-game`; the engine must not start enforcing a bound nobody ruled. **CB-WP-0023 exists because SOLVE was offered where it could not act** — inventing a limit is the same error in the other direction. **Controls:** - each answer is a runnable check, per GameDesign §1 — a claim about the supply is arithmetic and needs its reproduction; - **the register gets a row per token that can exceed its quantity**, or a stated "none found", because a survey that reports nothing and leaves no trace is indistinguishable from one that was not run. **Done 2026-08-07. Nothing found — and the first version of the check was wrong.** 750 games, 2–6 seats, greedy and random: | token | supply | reached | verdict | |---|---:|---|---| | Protection | 6 | 1/seat, 2/table | not exceeded | | Denied | 5 | 3 | not exceeded | | Relation link | 12 | **12** | exactly at the limit, never over | | Focus/Blame | 6 | 0 conflicts | consistent | **The link row is the interesting one**: GR-L01's two-slot rule *is* the twelve-token supply written twice, which is the shape of a supply needing no separate enforcement. **The Focus/Blame check reported 2 conflicts and was wrong.** It compared a seat's own placed Focus against its *own* `blame_from` — but that list holds **other players'** discs, so they are different tokens. Corrected: 0. **Fifth instance of this project's recurring defect** — a number computed correctly about the wrong subject — and the first caught before it left the repo. Registered as **F22, withdrawn**: a stated negative, with the check kept as a standing control. ## Task: evidence ```task id: CB-WP-0029-T04 status: done priority: medium state_hub_task_id: "29858777-6f8f-4d8e-90d0-cb193b7f4d29" ``` `evidence/CB-EV-0027-*.md`. - **Whether the supply is exceeded**, with the numbers. - **Whether the tracks changed what the maintainer could see coming** — the Stress track exists so a player can see DARVO approaching, and that is a claim about play, not about pixels. - **What the reflow cost the coverage gate**, third data point. - **The window-2 verdict is due** (§The window closes here) — name it as outstanding if the closing pass has not run. - **Quote CB-WP-0028's cost by re-running the instrument.** **Done 2026-08-07.** [CB-EV-0027](../evidence/CB-EV-0027-the-tokens-on-the-table.md). - **The supply is never exceeded**, and the link row explains why a supply can need no enforcement: GR-L01's two slots per seat *are* the twelve tokens, the same constraint written twice. - **The Focus/Blame check was wrong and I caught it** — fifth instance of the wrong-subject family, and **the first caught before leaving the repo**. One data point, not a trend; the family still has no control. - **The height limit went 460 → 500 as a correction.** 460 had no derivation; 500 does. An underived limit is a measurement defect, and replacing it is CB-WP-0021 T06's rule applied, not evaded. - **Whether the tracks change what a player sees coming is untested** and only play tests it. - **Chaos window 2 is closed: twelve declarations, zero overrides.** Its retirement condition was untestable from first to last. **The verdict is the next thing this loop owes itself**, and it is a tier-M pass because recording it changes how the loop constrains its own operation.