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

106 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 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 |