Some checks failed
ci / check (push) Failing after 4s
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>
175 lines
6.6 KiB
Markdown
175 lines
6.6 KiB
Markdown
---
|
||
id: CB-WP-0041
|
||
kind: product
|
||
title: "The extensive-form foundation"
|
||
status: active
|
||
---
|
||
|
||
# Purpose
|
||
|
||
```
|
||
structural tier M (states what the kernel claims about itself as a
|
||
game-theoretic object, and prepares — but does not
|
||
build — a capability port)
|
||
chaos d8 = 5 → no override
|
||
declared tier M
|
||
```
|
||
|
||
**Declaration 1 of chaos window 4.** Window 3 closed at 12 declarations
|
||
with one override that changed nothing, and **its verdict is still owed**
|
||
([`ChaosRollHistory.md`](../specs/ChaosRollHistory.md)).
|
||
|
||
## Why
|
||
|
||
[CB-RES-0009](../research/CB-RES-0009-extensive-form-is-the-lingua-franca.md)
|
||
found that the **extensive-form game is the interchange format** between
|
||
describing a game and analysing it — Ludii's universality result grounds
|
||
its language in EFGs, and OpenSpiel's CFR, best-response and
|
||
exploitability consume them.
|
||
|
||
**And that we already have most of one**, under other names:
|
||
|
||
| EFG component | ours |
|
||
|---|---|
|
||
| histories | the journal — `Applied { actor, command, events }` in order |
|
||
| actions | `bot::legal_commands(state, seat)` |
|
||
| **information sets** | **`state.project(Viewer::Player(seat))`** |
|
||
| payoffs | `Outcome` / `score()` |
|
||
| terminal | `outcome.is_some()` |
|
||
|
||
**This workplan does not build the port.** It answers what is true of the
|
||
engine today, because a port built on an unchecked assumption is worse
|
||
than no port — and one of the three gaps invalidates every equilibrium
|
||
concept if it turns out badly.
|
||
|
||
## Task: does the engine satisfy perfect recall?
|
||
|
||
```task
|
||
id: CB-WP-0041-T01
|
||
status: done
|
||
priority: high
|
||
```
|
||
|
||
**Perfect recall** — every player remembers their own past actions and
|
||
observations — is the assumption **CFR and exploitability both rest on**.
|
||
Nobody has checked whether ours holds.
|
||
|
||
Formally, for a seat `i` and any two histories `h`, `h'` in the same
|
||
information set of `i`: the *sequence of `i`'s own actions and
|
||
information sets* along `h` and `h'` must be identical.
|
||
|
||
**Controls:**
|
||
- **derived from the journal and the projection**, not asserted — the
|
||
check must be able to say NO;
|
||
- **a positive control**: a deliberately forgetful projection must fail
|
||
it, or the check proves nothing;
|
||
- **stated per seat count**, since the partition depends on how many
|
||
seats there are to hide from;
|
||
- **the answer may be that we do NOT have perfect recall**, and that is
|
||
reported as plainly as the other outcome. It would be a real finding
|
||
and would make Track B's adoption unsound as it stands.
|
||
|
||
**Done 2026-08-08. 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 | information set is… | violations |
|
||
|---|---|---|
|
||
| **A** | the seat's **current projection** — what `project(Viewer::Player(seat))` returns and the page renders | **22** |
|
||
| **B** | the seat's **observation history** — every view seen and action taken, in order | **0** |
|
||
|
||
**Reading A fails, and the witness is concrete**: two histories reach a
|
||
byte-identical view — round 3, Select, same hand, same claimed Problem —
|
||
where the seat had played `SOLVE, GROUND—OU(protect)` in one and
|
||
`SUPPORT, SOLVE` in the other. **The view does not tell the seat what it
|
||
did.**
|
||
|
||
**The mechanism is that our state is a snapshot, not 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.
|
||
|
||
**This is precisely OpenSpiel's `ObservationString` vs
|
||
`InformationStateString` split**, arrived at here by measurement rather
|
||
than by reading it off. `project()` is an *observation*.
|
||
|
||
**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, not quietly kept.
|
||
|
||
**What this cannot say.** It samples; it can falsify perfect recall and
|
||
cannot establish it. Reading B's zero means *no counterexample was
|
||
drawn*, which is weaker than "the property holds" and is printed as such.
|
||
|
||
## Task: say precisely what our chance is
|
||
|
||
```task
|
||
id: CB-WP-0041-T02
|
||
status: todo
|
||
priority: high
|
||
```
|
||
|
||
`setup` shuffles, deals and draws Lead from a seeded RNG, and the
|
||
mid-game reshuffle derives from seed and round. **So a clay-borg game is
|
||
one chance realisation, not a game with chance nodes**, and the panels
|
||
approximate the distribution by sampling seeds.
|
||
|
||
**Controls:**
|
||
- **state it, do not fix it.** Monte Carlo over seeds is legitimate and
|
||
is what we do; the defect would be calling it an EFG;
|
||
- **name every point where chance enters**, from the code, not from
|
||
memory;
|
||
- **say what an explicit chance player would cost** — that is the input to
|
||
T04's decision, and guessing it is how a port gets built on a hope.
|
||
|
||
## Task: state the simultaneity encoding, and check it
|
||
|
||
```task
|
||
id: CB-WP-0041-T03
|
||
status: todo
|
||
priority: medium
|
||
```
|
||
|
||
Commit/reveal **is** the textbook EFG encoding of simultaneous moves:
|
||
sequence them, and hide the earlier move in an information set.
|
||
|
||
**Control:** it is not enough to say so. **The projection must actually
|
||
hide another seat's selection before Reveal**, and a test must fail if it
|
||
stops doing that. That property is load-bearing for every claim in §1 of
|
||
the research note and is currently only implied by
|
||
`SelectionView::Hidden`.
|
||
|
||
## Task: decide whether to build the port at all
|
||
|
||
```task
|
||
id: CB-WP-0041-T04
|
||
status: todo
|
||
priority: medium
|
||
```
|
||
|
||
`decisions/ADR-*.md`, written **after** T01–T03 and not before.
|
||
|
||
**It must be able to conclude "no".** Options include exporting an EFG,
|
||
adopting OpenSpiel's API directly for analysis only, or deciding the gaps
|
||
are too expensive and Track B borrows vocabulary rather than machinery.
|
||
|
||
**Controls:**
|
||
- **the decision cites T01's answer**, because if perfect recall fails,
|
||
most of the option space closes;
|
||
- **it states what it would cost to be wrong**;
|
||
- **a port is declared separately, at its own tier.** Creating a capability
|
||
port is a tier-L trigger and this workplan is M — it may not smuggle one
|
||
in.
|
||
|
||
## Not in this workplan
|
||
|
||
- **No EFG export, no OpenSpiel integration, no equilibrium computation.**
|
||
- **No answer to "is exploitability meaningful for a co-operative game
|
||
with a shared threshold"** — that is the other open question from
|
||
CB-RES-0009 §6, it is a question about game theory rather than about our
|
||
engine, and it wants its own pass.
|