clay-borg/decisions/ADR-0020-we-do-not-build-the-port.md
tegwick 2806b3acb7
Some checks failed
ci / check (push) Failing after 3s
CB-WP-0041 done: ADR-0020 refuses the port, and T02 is why
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>
2026-08-08 15:35:03 +02:00

5.2 KiB
Raw Permalink Blame History

ADR-0020: we do not build an extensive-form port, and here is what to do instead

status: accepted date: 2026-08-08 decided by: agent, under the standing loop authorization tier: M (a decision not to create a capability port; creating one would have been L). chaos d8 = 7 → no override. Declaration 3 of chaos window 4. references: CB-WP-0041, CB-RES-0009, specs/Positioning.md §4 Track B, simulators/openspiel.md

Context

CB-RES-0009 found the extensive-form game is the interchange format between describing a game and analysing it, and that clay-borg already has most of one. CB-WP-0041 T01T03 then asked what is actually true of the engine, and the answers changed the decision.

D1 — the three findings this rests on

T01. Perfect recall fails on the projection and holds on the history. 22 violations in 44,938 sampled decision points when the information set is taken to be project(Viewer::Player(seat)); none when it is the seat's observation history. project() is an observation, not an information state — OpenSpiel's own ObservationString / InformationStateString split, found here by measurement.

T02. All chance is one root node, and the reshuffle is correlated. Three chance points, all reading the root seed; the Problems deal is not chance at all. The reshuffle permutation is a pure function of (seed, round) because K5 requires replay to be deterministic, where a real table would reshuffle independently.

T03. Commit/reveal is the EFG simultaneity encoding, and now checked in both directions rather than assumed.

D2 — we do not build an EFG export port

Refused, and the reason is T02, not T01.

T01 looked like the blocker and is not: it is a constraint with a known answer — key information sets on observation histories. That is a specification, and a cheap one.

T02 is the blocker. Making chance explicit means the reshuffle becomes a genuine chance node, and that breaks the purity that makes every recording in this repo replay. Our scenarios, the replay bundles, record.rs's round trip, the trial notes' state hashes — all of them rest on chance being a function of state (GameKernel K5).

A port would trade the property this project is built on for a property it has never needed. Replay is load-bearing for the evidence discipline; equilibrium computation is, so far, a research interest.

And the port is not the cheap part anyway. Enumeration is impossible — 24 Solution cards give 24! root branches — so any analysis samples chance, which is what our seed sweeps already do.

D3 — what we do instead, in order

  1. Write the mapping down, do not implement it. The EFG correspondence is now established and tested; it belongs in the taxonomy as a stated relationship, so a later pass starts from a specification rather than a memory.
  2. When Track B opens, take the question to OpenSpiel, not our kernel. Express a small GROUND-shaped game against its API and ask whether exploitability says anything useful for a co-operative game with a shared threshold. That is a question about game theory, not about our engine, and answering it in our engine would cost the replay property to learn something a toy model answers.
  3. If the answer is yes, revisit this ADR. The cost in D2 is real but it is not infinite: an EFG export that samples chance rather than representing it would preserve K5 and might be enough.

D4 — what this decision would look like if it were wrong

If exploitability turns out to be the instrument that settles design questions — if it answers "does ATTACK ever pay" in a way three review rounds of policy sweeps could not — then refusing the port will look like protecting a process property at the cost of the product's purpose.

The tell would be: OpenSpiel answering, on a toy model, a question our panels have been unable to answer. Watch for it. That is D3 step 2, and it is deliberately the next thing.

Consequences

  • No EFG port, no OpenSpiel dependency, no equilibrium computation.
  • The AM-4 dependency budget is untouched, which is worth stating because adopting OpenSpiel would have been a large, non-Rust dependency.
  • Track B's first move is a question, not a build.
  • The three properties T01T03 established are now regression-tested, so a later port starts from checked ground rather than re-deriving them.

What was rejected

rejected why
build an EFG export now breaks K5 replay purity for a research interest (D2)
key information sets on project() measured wrong — 22 perfect-recall violations (T01)
adopt OpenSpiel as an engine dependency large non-Rust dependency, for a question a toy model answers
declare Track B blocked it is not blocked, it is specified: the constraint is known and the next step is a question
say nothing and revisit later the three findings would be re-derived from memory, which is how this project produces wrong-subject errors