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>
9.1 KiB
| id | kind | title | status | state_hub_workstream_id |
|---|---|---|---|---|
| CB-WP-0030 | meta | A number that does not move: a control for the wrong-subject family | done | e6a0dad9-9b45-41fb-92ab-07c8b5690366 |
Purpose
structural tier M (changes how the loop constrains its own operation —
it adds an admissibility requirement, ADR-0006 D5)
chaos d8 = 7 → no override
declared tier M
Declaration 1 of chaos window 3, opened 2026-08-07 at d8 by ADR-0017.
The family, with every instance
Seven claims in this project have been wrong in the same way. CB-EV-0019 §1 named it: this project's arithmetic errors are not in the arithmetic.
| # | pass | the claim | what was wrong | caught by |
|---|---|---|---|---|
| 1 | CB-WP-0021 | csv costs 21,613 against 3,798 of headroom |
AM-4a never sees that code | re-deriving in ADR-0011 |
| 2 | → ground-game | "12 in the file" | a sum with no deal table | the recipient |
| 3 | CB-WP-0018 | SOLVE offered on a face-down Problem | the wrong condition named | implementing the ruling |
| 4 | CB-WP-0025 | 112–161 µs per node | the timer bracketed whole games | adversarial review |
| 5 | CB-WP-0025 | the game is too easy at 5–6 seats | a win rate attributed to the game | adversarial review |
| 6 | CB-WP-0029 | 2 Focus/Blame conflicts | compared other players' discs | re-reading the check |
| 7 | ADR-0017 | window 2 had zero overrides | never counted; cited 4× | writing the verdict |
What will not work, measured before proposing anything
facts-check has a real gap and it is not this one. It reports 62
untagged literal copies across 7 registry keys and fails on none of
them — a drift surface it names and does not close.
Closing it would have caught at most one of the seven. Instance 1's denominator was a registry fact and was correct; the error was that AM-4a does not cover the code. Instances 2, 4, 5, 6 were fresh measurements — no registry key existed, and a provenance tag reproduces a wrong subject faithfully.
A tag proves a number came from somewhere. It cannot prove the somewhere was the right place.
The untagged surface is worth closing on its own merits, and bundling it here would let this pass claim a victory it did not win. Named as separate work, not done here.
What did work, and it is one thing
Read the caught by column. Every instance was caught when the number was computed a second time by something with a different purpose — a later pass, a reviewer, the recipient, or the author re-reading.
Two of them, 4 and 5, share a sharper property: the claim was tested against a variation.
- #4's cost fell as branching rose. A per-node cost cannot do that. The tell was in the published output.
- #5's win rate was attributed to the game; a second policy on the same deals gave 0% where the first gave 100%.
And #1 has it too: vary which crates are in scope and the budget changes, which is the whole error.
So the control is to generalise the thing this project already builds after each instance — RetrospectiveAnalysis §4.1's policy panel, CB-WP-0029's attack-value sweep — and require it before:
A measured claim must name the variable it depends on, and show the measurement moving with it. A number that does not move when its subject moves is measuring something else.
Honest yield: 3 or 4 of 7, and it catches the two that travelled furthest. It does not catch #7 (a count never made) or #3 (not a number). Claiming it closes the family would be the family's own error applied to itself.
Task: decide the rule and its limits
id: CB-WP-0030-T01
status: done
priority: high
state_hub_task_id: "4be3630e-4c67-4ef9-a2f6-f689208b26b1"
decisions/ADR-0018-*.md.
- State the rule and where it binds: register findings that carry arithmetic, and anything leaving the repo.
- State the yield honestly, instance by instance, including what it misses. A control whose reach is overstated is worse than none, because it stops people looking.
- Decide whether it is a gate or a discipline. A sensitivity claim is
prose;
loop-lintcan check presence, never adequacy. Say which half is mechanical. - Do not bundle the untagged-facts surface. Name it as separate.
Done 2026-08-07. ADR-0018.
D1 states what it does not do, first, because the tempting fix was the wrong one: closing the 62 untagged literals would have caught 1 of 7, and instance 1's denominator was a registry fact that was correct. Deferred as separate work rather than bundled.
D3 gives the yield instance by instance: three clear, one partial, three
missed. The two it catches outright are the two that travelled furthest,
both bound for ground-game. The misses are named so nobody stops
looking — a count never made has no sensitivity, and instance 3 was not a
number.
D5 splits it honestly: the lint sees presence, never adequacy, and its output says so — otherwise a green run reads as "the claim was verified" and the control becomes a way of not looking.
Task: the rule, in the spec that governs findings
id: CB-WP-0030-T02
status: done
priority: high
state_hub_task_id: "cefcc917-91ef-4dbb-ab92-743266874b81"
specs/GameDesign.md §1 already requires a reproduction that exists,
has the ruled shape, and can fail. This adds the fourth clause,
because §1.2's row-level rule was itself born from instance 2 and covers
only sums.
Controls:
- the clause names the variable and the expected direction, or it is a sentence with no content;
- the existing three clauses are unchanged — this is an addition, and a rewrite would silently drop what earlier instances bought.
Done 2026-08-07. GameDesign.md §1.4, with the two instances that
motivate it stated in it: a per-node cost that fell as branching
rose, and a win rate attributed to the game that collapsed to 0% under
a second policy. Both were already printed when they shipped.
Task: make the mechanical half mechanical
id: CB-WP-0030-T03
status: done
priority: high
state_hub_task_id: "b773bc85-1f7b-4563-a0b5-3d5007162846"
loop-lint fails when a register row of an arithmetic kind lacks a
sensitivity line.
Controls:
- the check must fail on a real row, demonstrated by mutation — a lint that cannot go red is decoration (ADR-0006 D3, ADR-0017 D2);
- it checks presence, not adequacy, and its output says so, so nobody reads a green lint as "the claim was verified";
- existing register rows are brought under it or explicitly exempted with a reason — a rule that only binds future rows leaves the six instances unprotected.
Done 2026-08-07. check_sensitivity_stated in loop-lint.
It fired on three real rows immediately — F12, F15, F21 — and all three
were notes. A note is by definition a finding without a
reproduction (§3.1), so there is no measurement to be sensitive about;
requiring one would be asking for a sensitivity statement about nothing.
That exclusion belongs in the check, not in three hand-written
exemptions, and putting it there is the difference between a rule and a
pile of special cases.
Mutation-proven on a real row. Strip every sensitivity word from F17's prose and the check goes red; restore it and green. The first mutation attempt failed to fire because F17's text still said "one number varied" — which is the check working, and worth recording as the moment it was nearly mis-verified.
Four self-test controls: it catches a bare claim, clears one that names its variable (without which it would fire on everything), skips non-arithmetic kinds, and skips notes.
Task: evidence
id: CB-WP-0030-T04
status: done
priority: medium
state_hub_task_id: "7206661a-a046-47bb-8f73-4b56328151af"
evidence/CB-EV-0028-*.md.
- Re-run the seven instances against the rule and report how many it would have caught. If the answer is below 3, the rule is not the one.
- What the untagged surface is worth, since it was measured here and deferred.
- Whether a lint that checks presence is worth having at all, or whether it trains the eye to stop at green.
Done 2026-08-07. CB-EV-0028.
Yield 3 clear + 1 partial of 7 — above the falsifier, and the two it catches outright are the two that travelled furthest. Row 6 is recorded as partly rather than as a catch, because the variation would have prompted the question, not tested it.
The untagged surface is worth 1 of 7 against this family and is still worth closing for drift — recorded as separate, unclosed work.
The presence-only lint is worth having on one condition: it must never grow a claim it cannot cash. The day its output implies verification, ADR-0006 D3 applies and it should be deleted.
§6 records a vocabulary defect in this very workplan — it shipped
kind: process against product|meta|mixed and make self-tests caught
it. The pass that added a gate was caught by an older one.