Policy::choose takes the whole GroundState -- every face-down Problem's
suit and value, every seat's hand -- which is exactly what project()
exists to withhold. No shipped policy reads it, but the first thing a
competitive policy must do is VALUE a Problem, and value is the hidden
field. The trap goes live on the first line of the F27 work.
Established behaviourally rather than by narrowing the trait: vary only
what the seat cannot see, and the choice must not move. That binds every
policy including ones written later and outside this crate, without
their cooperation. The mirror of ADR-0013 D1 -- same kernel, two
searches, opposite permissions, discriminated by WHEN the question is
asked; a policy plays from inside an information set, so retrospective
permission would be strategy fusion.
Running the control found two defects IN THE CONTROL:
1. The rearrangements rotated hidden values 2<->3 together, leaving max
invariant -- so the deliberate peeker, which ranks by the largest
hidden value, was not caught. A control whose variation is invariant
under the statistic a violator reads is not a control.
2. It accused `random` of peeking, because it reused one policy instance
and compared a first call against a fourth. It takes a constructor
now, so every variant is judged from identical policy state.
Both are a difference in output read as evidence about hidden state --
the wrong-subject family, found twice inside a control written to detect
wrong subjects.
Three mutations, three red. The control is proven against a deliberate
violator before being trusted about compliant policies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
catalog.rs reads ground-game's schema 2 and refuses an unknown schema by
name: a reader that silently accepted schema 1 would answer questions
about aspects over a file that has none.
config.rs holds the two types ADR-0022 chose. Configuration is identity
and round-trips anything the catalog names, including modules with no
kernel path; Rules is behaviour, exhaustive, no catch-all. resolve() is
the boundary and emits the two distinct errors -- "known module with no
kernel path (status: proposed)" vs "not a module the catalog has" --
which is the entire reason this shape was chosen over a per-aspect enum.
Nothing about aspects, defaults or module status is written in our
source; all of it is read. The tests find the proposed module by
searching for status: proposed rather than naming one, so implementing
it upstream makes the test look elsewhere instead of going stale.
Four mutations, four red. scoped_plus_attack_soothe now resolves -- the
combination the three-armed enum could not express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
status.py resolves `## Task: <text>` immediately above a task block;
`## Task T00:` resolved to nothing and the gate said so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The maintainer's observation decided the design: aspects partition the
GAME, strata partition our apparatus, and they are orthogonal. A module
is one coordinate change in aspect space with an obligation in every
stratum. So aspect identity must NOT be Rust types -- an aspect
ground-game adds would make clay-borg fail to parse a configuration
rather than fail to run it, welding the two coordinate systems at the
one place they must stay independent.
Chosen: identity as data (Configuration round-trips anything the catalog
names), behaviour exhaustive (Rules, no catch-all), resolve() between.
Decisive argument: the catalog ALREADY ships modules with a rules_delta
and status: proposed, so a per-aspect enum would report them as "unknown
module" -- indistinguishable from a typo, a false statement about the
edition, and this project's signature failure shape. Two facts need two
errors. Federating design authority is permanent, so the representation
must outlive the implementation.
Legacy ids alias forever through the catalog's own legacy_experiment_id,
on the standard-Np precedent: 26 recordings name them and the expansion
is exact, so there is nothing to deprecate.
T00 done: the schema-2 mirror had arrived with no digests (19 files) and
edition-check was red. Digests are now generated by WALKING editions/,
not typed -- two reviews already found hand-written lists that made
their own controls vacuous, and a mirror that grows a directory is what
breaks a maintained list. PROVENANCE-catalog.md was a file inside the
mirrored tree that upstream does not have; folded into our own
PROVENANCE.md, since provenance about the mirror does not belong inside
the thing it describes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The modes were already implemented; nothing had ever COMPARED them. The
scenarios were not implemented at all: edition::deal has taken a
scenario_id since it was written and the only caller passed the literal
"SCN_01", so 15 of 20 Problem cards had never been dealt by anything.
The seam was the whole mechanism and it sat unused, with nothing red
because nothing asked.
Scenario is now state (serde default SCN_01, so all 26 recordings replay
unchanged), selected by preset `scn-03-4p` with `standard-Np` still
meaning SCN_01, and by --scenario/SCENARIO= accepting ids, numbers or
titles, validated against the edition rather than a pattern.
The threshold now comes off the Scenario card, closing F25's hardcoded
5/7/9. The first version of that control was worthless and mutation said
so: all four scenarios print 5/7/9, so reverting to the bands left it
green. Split threshold_from() so it can be handed a card that disagrees.
The header read `scoring CommonProblem` where the Mode card is titled
COMMON PROBLEM, PERSONAL EDGE -- the defect CB-WP-0034 deleted from the
move buttons, still standing on the line that says what winning means.
The coverage probe was matching that Debug output and went red when it
was fixed: third instance (CB-WP-0024, CB-WP-0034). Page now carries the
premise, the mode's rules text, and the tiebreak.
scenario-panel plays 4x3x3. Findings: SCN_01 and SCN_02 are the same
board (identical cells, pinned by a characterisation test); SCN_04 is
the hard board at 2p (52% vs 67/73%, the only deck needing two Repair);
and group success is EXACTLY equal across all three modes in all 36
cells, because greedy never reads state.mode -- filed F27, the two
competitive modes are scoring lenses over cooperative play.
F28: SHARED GROUND's mastery subtracts penalties from the claimed COUNT
where the mode card's shared score is claimed VALUE. Raised, not fixed;
scoring is ground-game's to rule on.
Also fixes design.py reporting a backticked path as no reproduction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CB-WP-0045 left "nothing explains what a scope does" open and gave a
FALSE reason: that scoping is our Variant so no printed sentence exists.
The H2 package ships Rules_Text.csv -- twenty-two passages of
player-facing rules -- and nothing in clay-borg had ever read the file.
A wrong reason for an open item is worse than an open item; it retires
the question. Filed as F26 against ground-game: a package that adds a
FILE is invisible where one that adds a column is not.
The scope rule now renders under the table in the edition's own words,
only when a non-global scope is in play, matched by heading rather than
row number, and absent (never paraphrased) if the edition drops it.
The trial log stamps its variant on the begin marker -- a session
property, not an eighth column -- read off state.variant rather than the
--variant flag, because a bare `state.variant = v` leaves H2 inert and a
flag-stamped log would put false provenance on real player words. An
unstamped log reports `unrecorded`, never `ground-darvo-r0`.
Six mutations, six red. The sixth is the finding: every trials.py fixture
built its marker out of BEGIN, so nine checks followed BEGIN away from
what hotseat.rs writes and stayed green while real logs broke. A fixture
built from the constant under test cannot test the constant -- the
control is now a literal, asserted from both sides.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three reports from the 2026-08-08 sessions. H2 lands ("that seemed more
interesting"), two legibility gaps do not.
The number was only a FALLBACK for a missing title, so it showed exactly
when it was least useful. That was survivable while Problems sat in one
ordered row -- position WAS the number. CB-WP-0043 scattered them by
scope and took the implicit index with it, with nothing going red because
no test named the number.
DARVO.csv's mandatory_effect and Actions.csv's GROUND rules_text were
both vendored, both used only as tripwires, and neither ever reached the
player -- F18's shape again. The modes now explain themselves at the
point of choice, and NOT when no mode is on offer.
Tests assert the edition's own words verbatim (ADR-0015), the number over
every key in view.problems, and both halves of the mode explanation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reported as "I did a session and noted no changes" — correct, and the
fault was ours twice.
make ground passes no --variant, so the session was baseline, and
CB-WP-0043 deliberately leaves the baseline layout untouched because a
variant that redraws the baseline invalidates every prior look at it.
But the deeper defect is that nothing said so. The page named the round,
the step, the Lead, the scoring mode and the viewer, and never which rules
it was playing — GroundView did not even carry the variant. There was no
way from the screen to tell baseline from H1 from H2. A session that
cannot say which rules it is playing cannot report a rules change; the
player did the right thing and the instrument had nothing to tell them.
Now: variant on GroundView, `rules <id>` in the page header beside the
scoring mode, `rules <id>` in the inspector because a replay that cannot
say which rules produced it is the same defect in the other tool, and
make ground VARIANT=h2 so the capability is reachable.
Both coverage probes caught the new field independently — the render
crate's and cb-play's — the second time in two passes that they have
turned an addition into a legibility requirement instead of letting it be
silent state.
Verified by fetching the served page rather than by reading the code:
make ground VARIANT=h2 prints "rules: h2" and the page carries
"rules h2-scoped-problem-stress" with the scope labels; the baseline says
"rules ground-darvo-r0" and keeps its row.
Still open: nothing explains what a scope DOES, and the trial log header
does not record the variant either — the same defect one artifact along.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The picture had become a lie. A row across the middle says "these are the
table's" — true under the baseline, false under H2, where three of five
cards fall on particular seats.
Global goes to the middle, personal beside its owner, and bond on the mean
midpoint of the owner's Bond edges. A bond Problem whose owner has no Bond
is drawn as personal, because that is the rule — bond_network falls back
to the owner at degree 0 — and the picture must agree with the arithmetic.
The baseline page is untouched: the scoped layout is taken only when a
non-global scope exists, so every prior look at the baseline still holds.
The scope reaches the view as a separate marker rather than a field on
ProblemView, because the delta says "place owner marker on the card" — a
token beside a card — and it is public while the card is face down, which
a ProblemView variant could not express.
The coverage probe forced a real improvement. It demanded a text token for
the new fields and a POSITION is not a token — which is the probe being
right: position alone is invisible to text_of and to a screen reader, and
illegible when two anchors coincide. So each scoped Problem now says whose
it is: everyone's, P1's alone, P2's Bond network, and "P1's alone — no
Bond to share it" at degree 0.
The fixture gained a third Problem. With two, one of the three placement
rules was unexercised and the probe unsatisfiable — a fixture that cannot
reach a branch is how a rule ships untested.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The H2 panel measured wins, variance and DARVO but not ATTACK selection —
which is F17's actual question. attack-value now runs H2 too.
The shape is the finding. Under H1 a seat that sometimes attacks loses
everything at 3p and above. Under H2 rank-75 wins 112/173/199 against
greedy's 120/175/199, while attacking and arming DARVO. So H2 makes
occasional ATTACK affordable — it does not make it pay. rank-75 never
beats greedy in any cell, and rank-95 (always attack) still wins 0
everywhere in all three variants, so "not always-attack-optimal" holds.
F17 therefore stands: ATTACK earns its place in no mode. What changed is
that choosing it is no longer catastrophic. Whether affordable is what the
design wants is ground-game's judgement.
And a hazard: with_variant() exists because H2 assigns Problem owners at
setup and `state.variant = v` leaves them unassigned, so scoped pressure
ticks nobody and H2 measures as INERT. Three call sites had the bare
write, including cb-play's driver.
No published figure is affected, and that was checked rather than assumed:
h2-panel used the builder, and the two harnesses with the bare write had
only ever run baseline and H1, neither of which has a setup step; the
driver has never played H2. All three fixed, and
a_bare_variant_write_leaves_h2_inert now states the difference so a
regression is caught by a named test rather than by a reader wondering why
H2 did nothing.
The builder was not enough — the field is public, so the old form still
compiles. Worth knowing before the next variant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measured against ground-game's own §3 criteria, read from their design
note rather than reused from H1.
Criterion 1 met with room: greedy SHARED wins 120 at 3p and 175 at 4p,
against H1's 0 and 0, restoring 73% and 92% of baseline.
Criterion 3 met, and it was H1's clearest failure. Under H1 the
unregulated seat armed DARVO constantly and never won; under H2 it arms
and wins 13/13/57. "Non-zero for some policy that still sometimes wins" is
exactly the shape H1 could not produce.
Criterion 2 met at 2p/4p/6p and missed at 3p — 1.50 against baseline's
1.57 — reported as a miss because that is what this sample says. The
mechanism is visible: H1-greedy's spread is 0.00 at 3p+, because a flat
tax on every seat creates no variance at all. That is the clearest
statement of why scoping was the right correction.
Criterion 5 is the best evidence in the pass. Forcing every scope to
global and changing nothing else reproduces H1's collapse exactly — 120 to
0 at 3p, 175 to 0 at 4p — so the scoping is what saves it, not any other
difference between the packages.
Criterion 4 came out backwards and the prediction held. The workplan said
this panel might be unable to test it, because no policy here models
another seat or knows what a scope is; bond claim rates are LOWER than
personal at 3p and 4p, driven by suit availability rather than incentive.
Reported as untested with an incidental figure pointing the wrong way,
not as a refutation.
Wired into make panels. First pass declared after ADR-0021, so no chaos
roll is recorded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
H2 is ground-game's answer to our H1 reading — that a flat +1 to every
seat is a solve-rate tax scaling with the number of Problems. Unclaimed
Problems now tick only the seats in scope: global (all), personal (the
owner), bond (the owner's Bond network over Bond edges only, degree 0
falling back to personal), assigned by hidden priority so 2p never has the
bond card in play.
T01: the package is vendored with digests, and H2's Problems.csv is r0's
with one column added and NOTHING else changed — checked, not assumed,
because the delta claims deal_and_thresholds unchanged and a silent
difference would make every H2-vs-baseline comparison a comparison of two
boards as well as two rule sets. Scopes are read from the column, not
derived from the priority in Rust: F25 exists because we hardcoded numbers
the edition already carried.
T02: owner and scope are new ProblemState fields, both Option and both
skipped when None, so a baseline state serialises without them and every
recorded scenario's hash is untouched — asserted on the JSON, not assumed.
with_variant() replaces the bare field write, because state.variant = v
would leave owners unassigned: a silently wrong game rather than a failing
one.
T03: every named defect is mutation-proven — traversing Rivalry edges,
applying stacking once, a degree-0 owner ticking everyone, personal
hitting everyone. The degree-0 mutation MISSED first: the fallback lives
inside bond_network and the mutation broke the None-owner arm instead, a
different branch. It stayed green until aimed at the path the test
exercises. A mutation that misses is not evidence the test works.
T04: ownership is not a permission. Filtering SOLVE to the owner turns it
red, which is the regression this task exists for — the engine had no
owner concept before T02 added one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The condition is met and the tally was verified rather than recalled.
ADR-0017 D2 named window 3 as the decider; windows 2 and 3 each produced
exactly one override and each changed nothing. Every roll in window 3 was
cross-checked against the workplan that made it, because the last time
this project tallied chaos rolls from memory it was wrong and asserted the
wrong figure four times (F23). All twelve agree.
But meeting the condition is not evidence. P(an override changes nothing)
is 1/3, and each window had exactly one override, so the condition fires
on a 1/9 coincidence. ADR-0017 restated it to be REACHABLE and made it
weak in the process; reachability was checked and discriminating power was
not.
Worse, it measures the wrong subject. It asks whether an override changed
the tier; the question is whether changing the tier helped. Under it, a
die that always changed the tier could never be retired however useless
its changes were.
The real ground is stronger. Four overrides across roughly forty
declarations, and the mechanism's value has never once been demonstrated.
The one substantive intervention dropped CB-WP-0011 from a structural L to
S, and that work then needed CB-WP-0016 and CB-WP-0017 to fix defects a
human found by playing. Not offered as causation — a tier is process
weight, not a guarantee — but it is the only evidence we have about an
override's consequences and it points the wrong way.
And the purpose has no live evidence of need: 17 M, 11 S, 4 L across every
workplan, with the only two structural/declared mismatches being the
window-1 overrides themselves. Tier declaration has not ossified.
So: retired, with nothing replacing it. Adding a successor to guard
against ossification that has not occurred would invent a gate for an
instance we do not have. The revival trigger is stated: tiers collapsing
toward one value, or a pass declaring below its structural tier to dodge a
review.
InnerLoop loses the chaos paragraph, loop-lint loses the chaos-recorded
check — a check that outlives its rule becomes an obstruction — and its
self-test now asserts the opposite: a note with no roll must pass.
ChaosRollHistory is closed. Window 4 ends incomplete at four declarations,
the last of which is this one, rolled because the rule was still in force.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
T02 — all chance derives from one root seed. Three chance points, all
reading it: the setup deck shuffle, the setup Lead draw, and the reshuffle
permutation. The Problems deal is not chance at all. So in extensive-form
terms the tree has a single chance node at the root.
That test was wrong first, and the mutation caught it. It compared state
hashes — and GroundState carries `seed` as a field, so "different seeds
differ" was true by construction. Mutating the shuffle away left it green.
It now compares the dealt configuration, and the same mutation fails it: a
wrong-subject error inside the control written for T02.
The reshuffle is a pure function of (seed, round) because K5 requires
deterministic replay, where a real table reshuffles independently. That is
a modelling restriction, not a defect, and it is now pinned.
T03 — commit/reveal checked in both directions: before Reveal each seat
sees its own selection and no other; after Reveal the information sets
merge, because an encoding that hides forever is not commit/reveal either.
T04 — ADR-0020 refuses the EFG port, and the blocker is T02 rather than
T01, which inverts what the workplan expected. Perfect recall looked like
the risk and is a constraint with a known answer: key on observation
histories. Making chance explicit is the expensive one — the reshuffle
would become a real chance node and break the K5 purity that every
recording, replay bundle and trial-note hash depends on. A port would
trade the property this project is built on for one it has never needed.
Track B's first move is therefore a question, not a build: take "is
exploitability meaningful for a co-operative game with a shared threshold"
to OpenSpiel on a toy model, where answering it costs nothing. D4 states
what being wrong looks like — OpenSpiel settling on a toy what three
rounds of policy sweeps could not — and makes watching for it the next
action.
Taxonomy §4.1 records the EFG correspondence with the test that checks
each row, so a later pass starts from a specification rather than a memory.
Chaos window 4 at three declarations. Window 3's verdict is now two
windows behind and should be evaluated rather than restated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The answer is "it depends what you call an information set", and the
distinction is the result. 44,938 decision points, random play, 2/3/4/6
seats.
Reading A — information set = the seat's current projection, which is what
project(Viewer::Player(seat)) returns and what the page renders: 22
violations. Reading B — information set = the seat's observation history,
every view seen and action taken in order: 0.
The Reading A witness is concrete. Two histories reach a byte-identical
view — round 3, Select step, same hand, same claimed Problem — where the
seat had played SOLVE then GROUND-OU(protect) in one and SUPPORT then
SOLVE in the other. The view does not tell the seat what it did, because
our state is a snapshot rather than a history: selections clear each round
and effects coincide, so a player cannot reconstruct their own past from
the present. In a real game the player's memory supplies it; in the state,
nothing does.
That is precisely OpenSpiel's ObservationString vs InformationStateString
split, arrived at here by measurement rather than read off. project() is
an observation, not an information state.
So Track B is not closed, it is constrained, and usefully: an
extensive-form game built from this engine must key information sets on
observation histories, never on project().
Both directions are asserted — Reading B empty AND Reading A non-empty —
because if the sample stops finding Reading A violations the conclusion is
unsupported and must be re-derived rather than quietly kept. And the check
samples, so it can falsify perfect recall and cannot establish it: Reading
B's zero means no counterexample was drawn, which is printed as such.
Wired into make panels, so it is re-derived by the gate rather than by
hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0041 answers what is true of the engine as a game-theoretic object before
anything is built on it. CB-RES-0009 found the EFG is the interchange
format between describing a game and analysing it, and that we already
have most of one — the journal is the history, Outcome the payoff, and
project(Viewer::Player(seat)) the information partition. Three gaps
remain, and perfect recall is first because CFR and exploitability both
assume it and nobody has checked ours. The workplan deliberately builds no
port: creating a capability port is a tier-L trigger and this is M, so
T04 decides whether to build one and declares it separately. T01's control
includes that the answer may be NO, which would make Track B's adoption
unsound as it stands.
0042 implements H2, which is ground-game's direct answer to our H1
reading. Unclaimed Problems now tick only the seats in scope — global,
personal (the owner), or bond (the owner's Bond network over Bond edges
only, degree 0 falling back to personal) — assigned by hidden priority, so
2p never has the bond card in play. It explicitly does not stack with H1.
H2 is bigger than H1 was: it needs variant-scoped edition data (H2
overrides Problems.csv with a stress_scope column, and ours is an
include_str! constant), per-Problem ownership which is new state reaching
the hash and every recording, and Bond-network reachability. The controls
name the likely defects in advance: traversing Rivalry edges, applying
stacking once, forgetting the degree-0 fallback, and ownership silently
becoming a permission to SOLVE.
Chaos window 4 opens: d8 = 5 and d8 = 4, no overrides. Window 3's verdict
is still owed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two questions from the maintainer — is there a game-theory mapping to
Ludii's language, and is that language formal enough to derive one from.
Yes, no, and the no does not matter.
The mapping is proven, not to be invented: "The Ludii Game Description
Language is Universal" shows the language can represent an equivalent game
for any finite, non-deterministic, imperfect-information game, extending
earlier work limited to finite deterministic fully-observable
extensive-form games. EFG is also OpenSpiel's object, so the same
formalism connects description to analysis: Ludii -> EFG <- OpenSpiel.
Ludii's syntax is formal and unusually so — a class grammar derived
automatically from its source. Its semantics are its Java: a ludeme means
what its class does, and Ludii effectively makes Java the game description
language. So there is no independent calculus to extract. The formality
lives in the universality RESULT, not in a definition of meaning. GDL has
the semantics and pays for it in speed — six times on Gomoku, twenty on
Amazons and Hex, over two hundred on Chess.
Conclusion: do not derive a language from Ludii; target the EFG directly.
And we are closer than the tracks assumed. The journal is the history,
Outcome is the payoff, legal_commands gives the actions — and
project(Viewer::Player(seat)) IS the information partition, built so a
player is not shown another's hand and unremarked as exactly the machinery
imperfect information needs.
Three gaps: chance is folded into a seed so a game is one realisation
rather than a game with chance nodes; perfect recall is unasserted, which
CFR and exploitability both assume; and commit/reveal is the standard EFG
encoding of simultaneity but is never stated as such. Perfect recall is
checkable from the journal today and is now Track B's first task — if it
fails, every equilibrium concept we might quote is unsound here.
Also re-vendored the catalog twice: ground-game added H2 — scoped problem
stress, applying End Stress by personal/bond/global scope instead of flat
to everyone, which is a direct response to our reading that H1's tax
scales with the Problems while its intended effect does not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight profiles on a common schema, each marking what was checked against a
source this session and what is background recollection. Three are marked
unverified in full — Machinations, the play substrates, most of RBG — and
say so rather than reading as evaluations. Written straight after three
review rounds whose entire yield was claims outrunning what had been
checked, so the confidence rule is the first thing in the README.
The survey changed the plan, which is what a survey is for.
Track C was described in Positioning as open ground. It is not: Browne
published 57 criteria for game quality, and Ai Ai already computes
designer-facing measures — drama, lead changes, branching factor,
completion, duration — from played games. The track becomes adopt, credit
and find the gap. The gap looks real: those measures presume a leader, and
SHARED GROUND has none — Modes.csv gives its tiebreak as "Not applicable".
Track B probably adopts rather than builds. OpenSpiel implements CFR,
best-response and exploitability over games that are simultaneous-move,
imperfect-information and co-operative, which is all four of GROUND's
awkward properties. "Does ATTACK ever pay" is a best-response question,
and we spent three review rounds refining a two-policy sweep for it. The
first Track B task is now one question — is exploitability meaningful for
a co-operative game with a shared threshold — not a build.
The cost of not surveying earlier is therefore measurable, and is recorded
rather than glossed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
edition-check's freshness comparison — newly covering the sibling packages
after CB-REV-0003 #3 — caught catalog.yaml and VARIANT.md moving upstream
within hours of the H1 report landing. ground-game marked the variant
status: measured, added a measurement_ref to their own report of it, and
rewrote the baseline summary around our finding that ATTACK is the sole
inbound pressure.
The check earned itself on its first real use: before round 3 those two
files were recorded and never compared, so this would have gone unnoticed
and our vendored catalog would have quietly disagreed with theirs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The maintainer could not tell whether "error", "failure", "finding" or
"correction" referred to the game's design, our formalisation of it, the
code, the measuring apparatus, or the sentences we wrote. Three review
rounds produced twenty-odd defect statements spanning five systems, all
called errors. The confusion was ours.
specs/Taxonomy.md, grounded in named canon rather than invented here: six
strata from Sargent's problem entity / conceptual model / computerized
model, extended where a simulation-V&V frame stops — we also own an
instrument and an account. The two relations are what was missing:
GAME<->MODEL is validation, MODEL<->ENGINE is verification, and nearly
every argument about "our bug or their gap" was that distinction going
unnamed.
Fault/error/failure from Avizienis et al., applied within a stratum, plus
the rule that explains the review history: a failure in one stratum is a
fault in the next. And it finally defines the family ADR-0018 could only
point at — a wrong-subject error is an ACCOUNT failure with no INSTRUMENT
fault, which is why tests never catch them.
MDA supplies the game-facing layers and one hard limit: our panels measure
dynamics, our trial logs sample aesthetics, and a win rate does not answer
"is it fun".
specs/Positioning.md names the field fairly — Ludii is the closest
relative and the right benchmark — and the four differentiators, each
already built rather than aspired to. Clay-borg is a design-evidence
instrument; anyone can produce the number. Three tracks named and none
started: a second game, game theory as the lens on dynamics, and
assimilated knowledge about why games work.
Track A is the falsifier for the whole positioning: every abstraction here
has exactly one instance, which by our own rule may mean invented rather
than observed.
Chaos window 3 closes at 12 declarations with one override that changed
nothing. Its verdict is due and is deliberately not written here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pattern is now measured over three rounds: 5 fatal, then 3 (2 from the
previous round's corrections), then 4 (3 from them). The corrections are
not getting safer.
FATAL 1: round 2's short-cell assertion went into regulation.rs only.
attack-value.rs — which produced every number in CB-EV-0030's DARVO table
— still just warned, and the gate registered to close the finding claimed
the property for both.
FATAL 2, the sharpest of the three rounds: counting games proves they
STARTED. Stopping the engine after one round gives 200 games, all-zero
columns and exit 0 — byte for byte the signature CB-EV-0030 says the
instrumentation distinguishes from a real result. Both harnesses now
require every counted game to have reached an outcome over five rounds.
FATAL 3: round 2's `.csv` filter was applied to all three loops, so
catalog.yaml and rules_delta.yaml — whose missing digests were round 1's
finding — were recorded and then never compared, and never checked against
upstream at all. Only the parser loop filters now.
FATAL 4: five of six tiebreak comparators had no coverage. GR-E04's
tiebreak never executes in any scenario. All four are now covered and
mutation-verified; the Blame key needed compensating claims to be
reachable at all, since Blame also lowers the coalition score.
SERIOUS: "peak held" computed the same number as "peak assigned" for every
possible input — the real gap was that START_STRESS was an unchecked
constant, now read off the dealt state; cadence="none" was a pure
loophole, removed; sibling discovery swapped a hand-written list for
hand-written globs and missed metadata.json and VARIANT.md, both named in
the package's own changed_files — now walked, and it found them
immediately; and "~72,000 games" was unsourced, make panels runs 17,600.
Also separated two kinds of number that were presented alike: seats×games
is invariant, 363 and 29 vary 7.1%-11.5% across samples.
Round 4 owed. The conclusion is not that the work is nearly right — it is
that author-made corrections to measurement work should be assumed
defective until a fresh reader has attacked them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit added the cadence rule and left `make all` red: the
gate-registry self-tests expect exactly one finding each, and their
fixture gates declared no cadence, so they got two. I committed without
re-running the gate to the end.
Fixtures now declare a cadence, and the rule has three controls of its
own: a cadence=all gate absent from `make all` is caught, a gate with no
declared cadence is caught, and declaring `manual` clears it — without
that last one the rule would fire on every manual gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CB-REV-0002 #7 found the H1 harnesses were run by no gate. The fix created
a `panels` target and registered it — and did not add it to `make all`. So
the gate existed, passed lint, and ran nowhere: the same defect one level
up, committed in the fix for it.
The registry checked that a gate's target EXISTS. It never checked that
the gate RUNS, and six of ten registered gates are absent from `make all`.
Some are legitimately manual — cost-budget needs spend data, gate-review
is periodic. So a gate may be manual, but it must SAY so: every entry now
declares cadence = all | manual | none, loop-lint requires it, and a gate
claiming "all" that is not in `all:` fails. Both rules mutation-verified.
panels is cadence = all and now runs there, so the figures in CB-EV-0030
and CB-EV-0031 are regenerated by the gate rather than by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three FATAL, five SERIOUS. The substance of round 1's corrections held —
Reactive is genuinely one arm different, the five replacement controls are
non-inert, the inert metric is right, the numbers reproduce. What failed
were the CLAIMS about them, and two defects the corrections introduced.
FATAL 1: the fix for round 1's #11 did not fix it. The assertion was
`games + setup_fails == 200`, and a refused setup increments setup_fails
while skipping games — so the sum is invariant under exactly the failure
it claimed to catch. Injecting setup failures gave exit 0 over 196-game
columns. Now asserts games == GAMES, verified to exit 101.
FATAL 2: the correction to the selective-column FATAL was itself
selective. "81-1000 per cell, baseline AND H1" and "31-1000" twelve lines
apart, both taken from the baseline row; under H1 rank-75 arms are
59/0/0/0. Every cell is now printed rather than summarised, and the
corrected verdict is the opposite of the one it replaced: under rank-75,
H1 REDUCES DARVO arms to zero at 3p and above.
FATAL 3: "DARVO arms 2 per seat per game" is 1 per seat per game, exactly,
at every band.
SERIOUS: the tiebreak oracle asserted only that the winner set CHANGED, so
reversing the tiebreak left it green; the #13 defect's impact was claimed
and never measured (72,000 games: zero divergences — real in principle,
witnessed only by a constructed board); a 29-of-363 citation pointed at a
file that did not contain it (round 1's reviewer did report it, and it was
never transcribed — the record was wrong, not the number); the harnesses
were run by NO GATE, so every published figure came from a manual run of
an ungated binary, including the assertion added for #1; and edition-check's
sibling handling — added by the last correction — was self-certifying,
crashed instead of failing, and counted Markdown lines as coverage. Now
discovered on disk, and it found a real gap on its first run: Rules_Text.csv
vendored with no digest.
Also: "peak Stress held" was dead code kept quiet by `let _ = held;` — the
numbers were right by coincidence.
make panels is now a registered gate. Round 3 is owed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
H1-B on the DARVO extra Attack: the delta says the extra Attack shares
the Attack resolution "so it can self-soothe too if Stress >= 4".
CB-WP-0038 asserted it because the code shares resolve_attack; nothing
tested it, and CB-EV-0031's withdrawn mechanism story ran through that
exact path. Now tested and mutation-verified.
Round-5 pressure did not reach the score, and this was a real defect
rather than a reporting one. end_round_events scored from `self` while
H1-A's pressure went into `work`, and score() reads Stress for the GR-E03
and GR-E04 tiebreaks — so the final round's pressure was invisible to the
two modes CB-EV-0030 reports on. Fixed. The test uses the case that bites:
uniform pressure preserves an ordering, so it takes the clamp at 5 to
collapse a gap and change who wins.
Inert arms reported separately: a DARVO arm at the End of Round 5 can
never advance a stage, and criterion 1 is about DARVO mattering. 29 of 363
at 2p, none above — matching the reviewer's independent figure, so
criterion 1 stands as met.
That fix produced one more wrong-subject error, caught before reporting:
the first inert-arm metric tested `g.rounds >= 5`, a property of the GAME
rather than the EVENT, so it marked every arm in every completed game
inert and briefly read as "criterion 1 fails after all". An arm is inert
when no RoundEnded follows it.
regulation.rs no longer skips setup failures silently: they are counted,
and a short cell fails an assertion rather than printing a number a reader
has to notice — which is the credit CB-EV-0030 §3 took and half earned.
All thirteen challenges closed. Re-review is owed before any of this
travels: the corrections were made by the author of the errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Thirteen challenges, five FATAL, all five conceded. Nothing had reached
ground-game, which is the only reason this is a correction and not a
retraction.
The worst: `Reactive` was not "greedy with one preference changed". It
differed in five, including SpendFreedom — ranked 95 unconditionally
against greedy's `95 if gated else 0` — so the seat burned its Freedom
token in round one of every game. A second change to the exact mechanism
under study, and every number in CB-EV-0031 was measuring it. The pass
claimed ADR-0018's one-varying-parameter discipline in its own workplan
while violating it. GreedyPolicy::rank is now public and the policy
delegates, overriding one match arm, so the control is structurally true.
Withdrawn entirely: "H1-B suppresses DARVO in the attacker". Disabling
H1-B under the corrected policy changes the arm count by exactly zero.
The pass hedged the wrong variable — it disclaimed "the number 2" and
defended "the direction", and the direction is what failed. The
supporting inference was invalid anyway: final Stress cannot show who
armed, because DarvoEnded resets the stage and REVERSE gives its owner -2.
Corrected: criterion 1 was failed on the greedy column while the pass's
own printed table showed 31-1000 arms in the other columns — the
selective-column move, in the file that names it. "Peak Stress was 1" was
a maximum over StressSet payloads, not held state (true: 2); the baseline
game count was 1,600 not 3,200; and "a reckless policy plays identically
to a careful one" is refuted by this repo's own rank-95 policy.
Inert controls replaced, each verified red against the reviewer's own
mutation: the baseline hash test compared two identically-constructed
states (serde(skip) on variant left 57/57 green); the `unchanged:` test
checked 3 of 7 entries and passed with SOLVE made illegal; H1-A's ordering
and H1-B's OU-cancel path had no test at all.
edition-check now covers catalog.yaml and rules_delta.yaml, whose digests
CB-WP-0038 claimed and never recorded — the review found it and reported
it unverified rather than absent, which was the right call.
Still open: H1-B on the DARVO extra-Attack path is untested, regulation.rs
still skips setup failures silently, and round-5 arms are counted though
they can never act.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CB-EV-0030 concluded H1's DARVO arm rate was still 0. That was true of the
panel, and the panel was greedy-family throughout. GreedyPolicy ranks
`Ground if gated => 100`, so it grounds the instant the stress gate bites,
Stress plateaus at 3, and the arm at 5 is unreachable by construction. "H1
does nothing" was really "H1 does nothing to a seat that already manages
its Stress" — and H1 was written for the seat that does not.
`reactive` is greedy with exactly one preference changed: GROUND demoted
below ATTACK. Under it, H1's criteria 1 and 2 are MET — DARVO arms 400
times per cell, ATTACK is chosen 3 times per seat per game. Criterion 3
fails harder: reactive wins nothing at any seat count.
The larger finding is about the baseline. Greedy and reactive play
IDENTICALLY under baseline, and peak Stress across 3,200 baseline games
was 1 — against a starting value of 2. The gate at 4, the DARVO arm at 5
and the Freedom token are all unreachable, and a policy built to be
reckless with Stress is indistinguishable from one built to husband it.
That is a deeper account of F17 than F17 has. Not raised as a finding yet:
it wants the plural panel first.
A constant was investigated rather than reported: darvo was exactly 400 in
every cell while atk scaled with seats. Six-player final Stress is
[5,5,4,4,4,4] every seed — H1-B holds the attacker at 4, below the arm,
and pushes its targets to 5. The self-soothe suppresses DARVO in the
aggressor and concentrates it in the attacked. The direction follows from
H1-B's arithmetic; the number 2 is partly an artifact of reactive's
first-legal targeting, and is labelled as such.
Still unreviewed: tier L review outstanding on CB-WP-0038, and nothing
here reaches ground-game until it runs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ground-game packages hypotheses as selectable rules variants — a catalog,
a rules_delta.yaml, and prose — and their note is explicit that CSV text
alone is not executable here. So the kernel gains a Variant in game state:
in the state, therefore in the hash, therefore in the recording, because a
scenario replayed under a different variant would diverge silently.
Baseline is bit-for-bit what it was, asserted across seat counts and
seeds. A variant system that perturbs the baseline invalidates every
measurement this repo has.
H1-A and H1-B implemented from rules_delta.yaml and mutation-proven on
their own defects: "unclaimed" misread as face-up-and-unsolved, and the
attacker's Stress read after the attack's effects. Their `unchanged:` list
is asserted rather than trusted — that list is their claim about their own
experiment.
Measured, and three of their four criteria fail. DARVO arm rate is still
0 under greedy; ATTACK selection does not rise and falls for the rank-75
policy; group success collapses from 165/190/200 to 0 at 3/4/6 seats.
The mechanism is not the assumed one: greedy answers the pressure by
regulating, Stress plateaus at 3, so it never reaches the gate at 4 or the
arm at 5 — H1-A acts as a solve-rate tax and H1-B is unreachable under
competent play.
A harness defect was caught before the claim: sweep discarded refused
games silently and never reported its count, so "nobody won" and "nothing
played" printed identically. Reporting H1 as unwinnable on that basis
would have been the ADR-0018 family aimed at another repo's design. All
200 games ran in every cell; the zeros are real.
Chaos d8 = 8 — the window's first override, redrew L against a structural
L, so it changed nothing. Window 3 recorded in ChaosRollHistory.
NOT REVIEWED: tier L owes a separate-agent adversarial review, and no H1
result may reach ground-game until it has run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reading Player_Mats.csv and Glossary.csv — which O4 had forbidden
declaring about until they were read — found a rule. Player_Mats'
choice_rule column IS GR-R03: at Stress 4-5 choose ATTACK or GROUND unless
you spend a ready Freedom token. The engine's stress_gated and
allowed_under_stress_gate match it exactly, and now have a tripwire.
So Ornamentation.md gains §1.3: the unit is a column, not a file.
Classifying Player_Mats as ornamentation would have thrown a rule away
with the colour swatches. A mixed artifact is normal — a player mat is a
rule printed on a decorated card in the box too. O4 is rewritten and
re-grounded from provisional to rule accordingly.
O5, O6, O7 declared with falsifiers. O7's is not theoretical:
back_design_id is unread in four vendored files, and hidden information is
what card backs are for.
CB-EV-0029 answers T04's question per rule rather than as a count — yes,
every checkable clause — and states the reading that result does NOT
support: it shows GroundRules.md was a faithful secondary source for the
clauses with behaviour to check, not that the engine is correct.
10 of 19 files vendored. Nine still unread, and Rules_Text.csv is the one
that matters: every clause checked here was checked against our own
transcription of it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>