clay-borg/specs/GameDesign.md
tegwick d7279483f6
Some checks failed
ci / check (push) Failing after 4s
CB-WP-0030: a control for the wrong-subject family
Seven claims in this project have been wrong the same way — the arithmetic
right, the subject wrong. Four reached a document or another repo.

ADR-0018 states the rule: a measured claim names the variable it depends
on and shows the measurement moving with it. GameDesign §1.4 adds it as a
fourth admissibility clause; the existing three are untouched.

Honest yield is 3 clear + 1 partial of 7. It misses a count never made and
a claim that is not a number, and D3 names those so nobody stops looking.
The tempting fix — closing facts-check's 62 untagged literals — would have
caught 1 of 7 and is deferred as separate work rather than bundled.

loop-lint gains check_sensitivity_stated over arithmetic register rows,
mutation-proven red on F17 and green when restored, with four self-test
controls. It checks presence, never adequacy, and says so in its output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 11:35:41 +02:00

215 lines
9.5 KiB
Markdown

# GameDesign — findings about the game, and the trials that produce them
v1.0 — CB-WP-0022 T04, 2026-08-05. Normative. Implements
[ADR-0012](../decisions/ADR-0012-the-design-instrument.md); INTENT
property 4 (*Instrument*) is what this spec discharges.
**This is not a register.** The register is
[`GroundRules.md §Underdetermined`](GroundRules.md) — ADR-0012 D2. This
document says what may go in it, what a reproduction must show, how a
finding moves and dies, and how a trial game is run so *"we played it and
X happened"* is replayable rather than remembered.
---
## 1. Admissibility
> A finding is admissible when its reproduction **exists**, **has the
> ruled shape**, and **can fail**. All three. (ADR-0012 D3.)
### 1.1 Exists
One of: a scenario under `scenarios/ground/`, an arithmetic check that
prints the contradiction, a `.cbreplay` bundle or recorded scenario the
reader can replay, or a **named test**`crate::module::test_name`, so it
can be run.
The register entry names the artifact **by path**. A finding whose
artifact path does not resolve is not admissible, and §4's check stats the
file rather than pattern-matching the string — `design-baseline.py`'s
`has_reproduction` matched `startswith("scenarios/")` and its own positive
control was green against a file that had been deleted (ADR-0012 D8).
### 1.2 Has the ruled shape
Per `ground-game`, GROUND-WP-0004 T02:
> An arithmetic finding ships a **row-level table** — Surface and each
> hidden priority listed **separately** — never *"sum of file"*, never
> *"deal depth N"*.
**A reproduction must show the quantity the prose claims.** This is the
clause that exists because passing artifacts accompanied wrong
descriptions:
| wrong premise | what shipped | what a row-level table would have shown |
|---|---|---|
| *"12 in the file"* | a sum | which rows, at which priorities |
| *"4/6/9 against 5/7/9"* | a scenario, green | 6/9/12 — the deal, per row |
| *"SOLVE offered on a face-down Problem"* | a condition named | which filter actually fired |
**Requirement.** Where a finding asserts a number, the register entry
quotes the number **and** the artifact prints the rows it came from. A
finding stating a total without its rows is inadmissible even if the total
is right — *"12"* was arithmetically defensible and still wrong about the
game.
### 1.3 Can fail
The artifact must be capable of going red, and the register records its
**current colour**. A reproduction that has gone **green is not a closed
finding** — it is a finding whose claim is now in question, and §4 reports
it as needing resolution to `applied` or `withdrawn`.
**A reproduction has a `role`, and only one of them alarms.** This
distinction was not in ADR-0012; **T05's backfill produced it**, when the
first report cried wolf over U2.
| role | what it is | green means |
|---|---|---|
| `counterexample` | demonstrates the defect — the model-checker shape | **alarm.** It should be red while the finding is open |
| `default` | encodes a provisional choice so it is testable and visible | **expected.** It passes because our default is implemented, which says nothing about whether the owner agrees |
U2's scenario is green because the default it documents is implemented.
GR-E01's was a counterexample that went green when the edition landed, and
sat that way for four days. **Same colour, opposite meaning** — a register
that cannot tell them apart either alarms constantly or never.
**Rationale, and it is the expensive one.** `gr-e01-threshold-…-2p.yaml`
went green when the edition landed on 2026-08-04. The finding remained
admissible, remained in the register, and remained queued for transmission
to `ground-game` for four days, because nothing treated a passing artifact
as a signal. It was stopped by an adversarial review, not by a control.
### 1.4 It must move when its subject moves
> **A measured claim names the variable it depends on, and shows the
> measurement moving with it.** A number that does not move when its
> subject moves is measuring something else.
ADR-0018. This is the fourth clause and the first three are unchanged —
each was bought by an earlier mistake and a rewrite would drop what they
paid for.
**Why it exists.** Seven claims in this project have been wrong the same
way: the arithmetic right, the subject wrong. Two of them were caught only
because someone varied something —
- a per-node cost that **fell** as branching **rose** (no per-node cost
can), and
- a win rate attributed to *the game* that collapsed to 0% under a second
policy on the same deals.
**Both were already printed when they shipped.** Nobody looked at whether
the number moved the way its subject demanded.
**Where it binds:** register findings whose claim is arithmetic, and
anything leaving the repo. **Not every number everywhere** — a rule that
binds everywhere is obeyed nowhere.
**What it does not catch, stated so nobody stops looking:** a count that
was never made (there is no sensitivity to state), and a claim that is not
a number. **No syntactic gate detects a wrong subject in general.** It is
a semantic error; `loop-lint` checks that a sensitivity line is *present*,
never that it is *adequate*.
## 2. Kinds
Five (ADR-0012 D4). **No kind exists without a finding that instantiates
it**; a sixth appearing during backfill means the taxonomy was invented
rather than observed, and T07 must say so.
`underdetermined` · `inconsistent` · `inert` · `degenerate` · `unplayed`
## 3. States, owners, and death
```
raised ──► reported ──► ruled ──► applied
└──────────► withdrawn ◄──────────┘
```
- `applied` means **the source changed** — dataset or rules text — and our
provisional default was deleted. Not *"a ruling was recorded."*
- **The queue is `raised` + `reported`. The log is `applied` +
`withdrawn`.** `make design` reports the queue by default; the log only
on request. If the default view mixes them, the queue property is gone.
- **`withdrawn` is reported downstream, not deleted.** A claim retracted
silently is how three wrong premises survived.
### 3.1 Notes
A finding without a reproduction is a **note** (ADR-0012 D6). A note:
1. **has no `reported` state** — it may not cross the repo boundary;
2. **expires at 30 days**, flagged by §4 on the same mechanism that ages
provisional items (`rule-coverage.py:54`, `PROVISIONAL_WARN_DAYS`);
3. is promoted only by acquiring a reproduction meeting §1 — there is no
path from `note` to `reported` that skips it.
## 4. Metrics
`make design` reports these. Every number is computed **over the
register**, never from a maintained list (ADR-0012 D8).
| metric | target | notes |
|---|---|---|
| findings with a resolving reproduction | **100%** | denominator includes `withdrawn`; notes counted separately, never folded in |
| **reproductions that have gone green while open** | **0** | the §1.3 alarm. Non-zero is a queue that needs reading, not a passing suite |
| arithmetic findings whose artifact prints rows | **100%** | §1.2 |
| median days `raised``reported` | the number this exists to fix | the baseline it must beat is **4 days** to first *read* |
| open findings by kind, with age | reported, not targeted | |
| notes past 30 days | **0** | |
| findings closed by a ruling vs still open | reported, with age | |
**Falsifier for this spec.** If a finding reaches `ground-game` whose
prose describes a quantity its own artifact does not compute, §1.2 has
failed and the next revision must **generate the description from the
artifact** rather than let it be written alongside.
**No baseline rate is quoted here.** CB-RES-0007's 33% was withdrawn as a
measured figure (ADR-0012 D8); the first honest denominator is the one
T05's backfill produces.
## 5. The trial protocol
The part with no precedent in this repo. A **trial game** is a recorded
session plus an observation log — so a claim from play is replayable.
**It must cost almost nothing or it will not be done.** The engine already
records: `cb-play --record FILE` writes the finished game as a scenario
YAML (`main.rs:30`), and `games/ground/src/record.rs` is trustworthy
because its round trip is the test.
A trial is therefore:
```
cb-play --players N --serve PORT --record trials/<date>-<slug>.yaml
```
plus a sibling `trials/<date>-<slug>.md` carrying, and nothing more:
- **who played which seats**, and which were bots (with policy);
- **what was observed** — in the player's own words, not cleaned up;
- **the seed**, which the scenario already carries;
- **which observations became findings or notes**, by register id, or
explicitly none.
**Rules.**
1. An observation is a **note** until it has a reproduction (§3.1). A
trial log is not a finding source by itself — it is where findings are
*noticed*.
2. **The recording is the artifact for `unplayed` findings.** GR-E03/E04
were "never played to the end"; a trial that plays them to the end both
closes that finding and produces the reproduction for anything found on
the way.
3. A trial whose recording does not replay is a **bug in the engine**,
reported as one, not a design finding.
**Why the log is prose and stays prose.** The maintainer's *"I felt it was
too easy but then we lost, so who knows"* is exactly the observation this
protocol exists to catch: real, unreproducible as stated, and the seed of
[CB-WP-0025](../workplans/CB-WP-0025-could-we-have-won.md). Forcing it
into a schema at the moment of observation would lose it. §3.1 is what
stops it aging into an apparent finding.