clay-borg/decisions/ADR-0016-the-tokens-on-the-table.md
tegwick 631fb41fc2 CB-WP-0029 T01-T03: components you can count, and a supply that does not bind
ADR-0016, Tokens.csv vendored, tracks and discs on the table, and a supply
audit that found nothing and says so.

T03 MEASURED BEFORE T01 DECIDED. 750 games, 2-6 seats, greedy and random:
Protection reaches 1 per seat and 2 on the table against a supply of 6;
Denied 3 of 5; relation links EXACTLY 12 OF 12 and never more; Focus/Blame
0 conflicts. 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.

AND THE FIRST VERSION OF THE FOCUS/BLAME CHECK 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. It reported 2
conflicts; corrected, it reports 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 rather than by a reviewer.

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 they disagreed the bug would be invisible because both would look
internally consistent.

D3: quantity does NOT bind, and the reason is not the measurement. A
component limit the rules do not state is not a rule. Refusing a seventh
Protection token 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, so a
future violation becomes a question for ground-game (does the box bound
the game, or do the rules?) rather than a bound the engine invented.
Registered as F22, withdrawn: a stated negative, because a survey that
finds nothing and leaves no trace cannot be told from one never run.

D4: Stress on a 0-5 track that turns red at 5 where DARVO arms, DARVO on
OFF/DENY/ATTACK/REVERSE, Freedom as the two-sided disc the edition says it
is, Protection and Blame counted, Lead and Round on the table.

Two tests broke on token discs and both were FIXTURE defects:
seat_centres matched every <circle> and track stops are circles. Seats now
carry class="seat".

The table height limit went 460 -> 500 as a CORRECTION, not a concession.
460 had no derivation; 500 does -- ~800px viewport less ~120 header and
~150 controls leaves ~530, and the version that broke dragging was 620.
CB-WP-0021 T06's rule is to fix the measurement rather than lower the
floor, and an underived number is a measurement defect.

make all: exit 0. 66 render tests, 26 cb-play.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 09:54:20 +02:00

132 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-0016: a token is a view, and the supply is not a rule we get to invent
status: accepted
date: 2026-08-07
decided by: agent, under the standing loop authorization
tier: M (structural M — imports another edition file under AM-4's budgets;
chaos d8=4 → no override). Tier M merges survey and decision.
references: [CB-WP-0029](../workplans/CB-WP-0029-the-tokens-on-the-table.md),
[ADR-0015](ADR-0015-the-cards-own-words.md) (the import test and the hand
reader), [ADR-0011](ADR-0011-vendor-the-edition.md),
[GameDesign.md](../specs/GameDesign.md) §1
## Context
The engine models every token correctly and shows them as numbers on a
seat card — `protect 2`, `blamed by P3`. `Tokens.csv` describes them as
components, with a `quantity` and a `sides` count, and that raised a
question worth measuring before deciding anything.
## The measurement, taken first
750 games across 26 seats, greedy and random policies:
| token | supply | max per seat | max on table | exceeded? |
|---|---:|---:|---:|---|
| Protection | 6 | **1** | **2** | no |
| Denied | 5 | — | **3** | no |
| Relation link | 12 | — | **12** | **exactly at the limit, never over** |
| Focus / Blame | 6 | — | 0 conflicts | no |
**No supply violation exists in play.**
**And the first version of that check was wrong.** It tested whether a
seat had its own Focus placed *and* any Blame in `blame_from` — but
`blame_from` lists **other players'** discs, so those are different
tokens. It reported 2 conflicts. Corrected, it reports 0. **Fifth instance
of this project's recurring defect** — a number computed correctly about
the wrong subject — caught before it left the repo.
**The link row is the interesting one.** Twelve tokens, two per player,
six players: the supply is *exactly* consumed at full occupancy and never
exceeded, because GR-L01 already enforces two slots per seat. **The
component count and the rule are the same constraint written twice** — and
that is the shape of a supply that does not need separate enforcement.
---
## D1 — vendor `Tokens.csv`
ADR-0015's test: does it carry text a player reads? `front_text` (READY,
DENIED, PROTECTION, FOCUS, LEAD, ROUND) and `use` do. `shape`, `size` and
`symbol_id` are print instructions and are read by nothing here.
Vendored whole with a digest, as the others are — taking a column subset
would mean a second decision every time a column becomes interesting.
## D2 — a token is a **view**, not a type
**The aggregate gains no `Token`.** A token is a way of *seeing* state the
aggregate already holds: the Stress marker is `stress`, the DARVO pawn is
`darvo`, the Freedom disc is `freedom_ready`.
Adding a `Token` type would create a second source of truth for Stress,
and the first time they disagreed the bug would be invisible — both would
look internally consistent. **This is INTENT's own rule**: own the
semantics, and do not duplicate them for presentation's sake.
So `Tokens.csv` supplies **labels and counts for the renderer**, and
nothing in `games_ground` changes shape.
## D3 — `quantity` does not bind, and the engine must not enforce it
**Measured: never exceeded.** But the reason for the decision is not the
measurement.
> **A component limit the rules do not state is not a rule.**
`GroundRules.md` derives 59 rules from the dataset and **none of them
mentions a token supply**. If the engine began refusing a seventh
Protection token, it would be enforcing a constraint nobody ruled — which
is exactly the error CB-WP-0023 exists to correct, in the other direction:
SOLVE was *offered* where it could not act, and this would *refuse* where
the rules allow.
**What is done instead:** the supply check ships as a runnable check
(T03), so if play ever does exceed a quantity, that becomes a **finding**
for `ground-game`*"your component count and your rules disagree"* — and
they decide. Which is a real question: a physical game cannot hand out a
seventh Protection token, so either the rules bound it or the box does.
**Not registered as a finding today**, because nothing was found. **A
stated "none found" is registered instead**, because a survey that reports
nothing and leaves no trace is indistinguishable from one that was never
run.
## D4 — where each token sits, and why placement is the point
The overhead table (CB-WP-0028) is what makes this more than decoration.
| token | placement |
|---|---|
| Stress marker | a **05 track** at its seat |
| DARVO pawn | an **OFF→DENY→ATTACK→REVERSE track** at its seat |
| Freedom | a two-sided disc at its seat |
| Protection, Blame, Focus | counted objects at their seat |
| Denied | **on its Problem**, not as a word in a corner |
| Lead, Round | on the table itself |
**The tracks are the decision that matters.** `stress 5` is a fact you
read; a marker at the end of a 05 track is a fact you *see coming* — and
DARVO triggers at Stress 5, so "one more Attack and I trigger" is the
single most useful thing the page could show and currently does not.
## Consequences
- `editions/ground-darvo-r0/Tokens.csv` + digest; `edition-check` covers
it automatically, since it enumerates what is present.
- `edition.rs` gains a `tokens()` reader over the existing `Table`.
- The renderer draws tracks and counted objects; `games_ground` is
unchanged.
- The supply check is committed and runnable, and its result is in the
register as a stated negative.
## What was rejected
| rejected | why |
|---|---|
| a `Token` type in the aggregate | a second source of truth for Stress; INTENT forbids it |
| enforcing `quantity` | a limit the rules do not state is not a rule (CB-WP-0023, inverted) |
| importing only some columns | a second decision every time a column becomes interesting |
| leaving the supply unchecked | *"probably safe"* is not an answer, and a negative that leaves no trace cannot be told from an unrun survey |
| keeping Stress as a number | the track is what makes DARVO visible before it fires |