# 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](../workplans/CB-WP-0041-the-extensive-form-foundation.md), [CB-RES-0009](../research/CB-RES-0009-extensive-form-is-the-lingua-franca.md), [`specs/Positioning.md`](../specs/Positioning.md) §4 Track B, [`simulators/openspiel.md`](../simulators/openspiel.md) ## Context [CB-RES-0009](../research/CB-RES-0009-extensive-form-is-the-lingua-franca.md) 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 T01–T03 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 T01–T03 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 |