CB-WP-0041 done: ADR-0020 refuses the port, and T02 is why
Some checks failed
ci / check (push) Failing after 3s
Some checks failed
ci / check (push) Failing after 3s
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>
This commit is contained in:
parent
81f9339b2d
commit
2806b3acb7
5 changed files with 372 additions and 4 deletions
106
decisions/ADR-0020-we-do-not-build-the-port.md
Normal file
106
decisions/ADR-0020-we-do-not-build-the-port.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# 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 |
|
||||||
|
|
@ -2256,6 +2256,159 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// **Commit/reveal IS the extensive-form encoding of simultaneous
|
||||||
|
/// moves** (CB-WP-0041 T03), and this checks it rather than
|
||||||
|
/// asserting it.
|
||||||
|
///
|
||||||
|
/// An EFG has no simultaneity: the textbook encoding sequences
|
||||||
|
/// the moves and puts the later mover in an information set that
|
||||||
|
/// cannot see the earlier one. GROUND's Select step does exactly
|
||||||
|
/// that — seats choose in order, and no seat may see another's
|
||||||
|
/// selection until Reveal.
|
||||||
|
///
|
||||||
|
/// **The property is load-bearing.** If a seat can see another's
|
||||||
|
/// pending selection, the encoding is not simultaneous, the
|
||||||
|
/// information partition is wrong, and every equilibrium concept
|
||||||
|
/// computed on it answers a different game.
|
||||||
|
#[test]
|
||||||
|
fn a_pending_selection_is_hidden_until_reveal() {
|
||||||
|
use cb_game_runtime::{Project, Viewer};
|
||||||
|
|
||||||
|
let mut s = setup(4, Variant::Baseline, 9);
|
||||||
|
let seats: Vec<PlayerId> = s.players.keys().copied().collect();
|
||||||
|
// Every seat commits.
|
||||||
|
for seat in &seats {
|
||||||
|
s.selections.insert(
|
||||||
|
*seat,
|
||||||
|
Selection {
|
||||||
|
action: Action::Investigate,
|
||||||
|
target: None,
|
||||||
|
problem: s.problems.keys().next().copied(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before Reveal: a seat sees its own and nobody else's.
|
||||||
|
s.step = RoundStep::Select;
|
||||||
|
for viewer in &seats {
|
||||||
|
let v = s.project(Viewer::Player(*viewer));
|
||||||
|
for (seat, shown) in &v.selections {
|
||||||
|
let visible = matches!(shown, crate::view::SelectionView::Shown(_));
|
||||||
|
assert_eq!(
|
||||||
|
visible,
|
||||||
|
seat == viewer,
|
||||||
|
"before Reveal, {viewer:?} could see {seat:?}'s selection — \
|
||||||
|
the Select step is not simultaneous and the information \
|
||||||
|
partition is wrong"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After Reveal: public, which is the other half of the
|
||||||
|
// encoding — the information sets must MERGE, or the reveal
|
||||||
|
// never happened.
|
||||||
|
s.step = RoundStep::Reveal;
|
||||||
|
for viewer in &seats {
|
||||||
|
let v = s.project(Viewer::Player(*viewer));
|
||||||
|
assert!(
|
||||||
|
v.selections
|
||||||
|
.values()
|
||||||
|
.all(|x| matches!(x, crate::view::SelectionView::Shown(_))),
|
||||||
|
"after Reveal, {viewer:?} still cannot see every selection"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **All chance derives from the root seed** (CB-WP-0041 T02).
|
||||||
|
///
|
||||||
|
/// The claim T02 rests on, made checkable rather than asserted:
|
||||||
|
/// a game is determined by `(seed, players, mode, variant)`, so
|
||||||
|
/// in extensive-form terms the tree has **a single chance node at
|
||||||
|
/// the root** rather than chance distributed through it.
|
||||||
|
///
|
||||||
|
/// Three chance points exist and all three read that seed:
|
||||||
|
/// the setup deck shuffle, the setup Lead draw, and the mid-game
|
||||||
|
/// reshuffle permutation (`seed ^ round`). The **Problems deal is
|
||||||
|
/// not chance at all** — `edition::deal` is a pure function of the
|
||||||
|
/// vendored CSV.
|
||||||
|
#[test]
|
||||||
|
fn a_game_is_determined_by_its_seed() {
|
||||||
|
for players in [2u8, 3, 6] {
|
||||||
|
// **The dealt configuration, NOT the state hash.**
|
||||||
|
// `GroundState` carries `seed` as a field, so hashing the
|
||||||
|
// state makes "different seeds differ" true by
|
||||||
|
// construction — a wrong-subject error, caught by
|
||||||
|
// mutating the shuffle away and watching this stay green.
|
||||||
|
let at = |seed: u64| {
|
||||||
|
let s = setup(players, Variant::Baseline, seed);
|
||||||
|
let hands: Vec<Vec<Suit>> = s
|
||||||
|
.players
|
||||||
|
.values()
|
||||||
|
.map(|p| p.hand.iter().map(|c| c.suit).collect())
|
||||||
|
.collect();
|
||||||
|
format!("{:?}|{:?}|{:?}", hands, s.lead, s.solution_deck)
|
||||||
|
};
|
||||||
|
// Same seed, same game — twice, because "deterministic"
|
||||||
|
// that only holds once is not determinism.
|
||||||
|
assert_eq!(at(11), at(11), "{players}p: setup is not reproducible");
|
||||||
|
// And the full state still round-trips, which is the
|
||||||
|
// replay property this rests on.
|
||||||
|
assert_eq!(
|
||||||
|
cb_events::state_hash_hex(&setup(players, Variant::Baseline, 11)),
|
||||||
|
cb_events::state_hash_hex(&setup(players, Variant::Baseline, 11)),
|
||||||
|
);
|
||||||
|
// Different seeds must actually differ, or the seed is
|
||||||
|
// not the chance node and this claim is empty.
|
||||||
|
let distinct: std::collections::BTreeSet<String> = (0..12u64).map(at).collect();
|
||||||
|
assert!(
|
||||||
|
distinct.len() > 1,
|
||||||
|
"{players}p: every seed produced the same game, so the root \
|
||||||
|
chance node carries no information"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **The reshuffle is correlated with the root seed, and a real
|
||||||
|
/// table's is not** (CB-WP-0041 T02).
|
||||||
|
///
|
||||||
|
/// `draw_solution` reshuffles the discard with
|
||||||
|
/// `ChaChaRng::from_seed(seed ^ round)` — deliberate, so replay
|
||||||
|
/// never re-derives it (GameKernel K5), and the comment says so.
|
||||||
|
///
|
||||||
|
/// **The consequence is a modelling one, not a defect.** At a
|
||||||
|
/// table the reshuffle is an independent random event; here it is
|
||||||
|
/// determined by the initial shuffle and the round number. An
|
||||||
|
/// extensive-form game built from this engine inherits that
|
||||||
|
/// correlation, and would be modelling a *restriction* of the
|
||||||
|
/// game as played.
|
||||||
|
#[test]
|
||||||
|
fn the_reshuffle_permutation_is_a_function_of_seed_and_round() {
|
||||||
|
let deck: Vec<SolutionCard> = [Suit::Clarify, Suit::Repair, Suit::Boundary]
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|suit| std::iter::repeat_n(SolutionCard { suit }, 3))
|
||||||
|
.collect();
|
||||||
|
let shuffled = |seed: u64, round: u8| {
|
||||||
|
let mut order = deck.clone();
|
||||||
|
let mut rng = ChaChaRng::from_seed(Seed(seed ^ u64::from(round)));
|
||||||
|
rng.shuffle(&mut order);
|
||||||
|
order
|
||||||
|
};
|
||||||
|
// Same seed and round: the same permutation, always.
|
||||||
|
assert_eq!(shuffled(7, 2), shuffled(7, 2));
|
||||||
|
// And it moves with BOTH inputs, or the correlation claim is
|
||||||
|
// about something that does not vary.
|
||||||
|
assert_ne!(
|
||||||
|
shuffled(7, 2),
|
||||||
|
shuffled(8, 2),
|
||||||
|
"the reshuffle does not depend on the seed"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
shuffled(7, 2),
|
||||||
|
shuffled(7, 3),
|
||||||
|
"the reshuffle does not depend on the round"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// **H1-A lands before the DARVO arm check** (review M5).
|
/// **H1-A lands before the DARVO arm check** (review M5).
|
||||||
///
|
///
|
||||||
/// The delta orders it "+1 Stress, then clamp, then DARVO arm
|
/// The delta orders it "+1 Stress, then clamp, then DARVO arm
|
||||||
|
|
|
||||||
|
|
@ -89,3 +89,19 @@ separately because it was a materially different pass: CB-WP-0036 was
|
||||||
re-scoped from L to M after the maintainer moved the animation work out of
|
re-scoped from L to M after the maintainer moved the animation work out of
|
||||||
the repo, and a changed declaration is a new declaration or the roll is
|
the repo, and a changed declaration is a new declaration or the roll is
|
||||||
not binding on what was actually built.
|
not binding on what was actually built.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Window 4 — opened 2026-08-08 at d8
|
||||||
|
|
||||||
|
| # | pass | roll | override |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | CB-WP-0041 | d8 = 5 | — |
|
||||||
|
| 2 | CB-WP-0042 | d8 = 4 | — |
|
||||||
|
| 3 | ADR-0020 | d8 = 7 | — |
|
||||||
|
|
||||||
|
**Window 3's verdict remains owed** and is now two windows behind. One
|
||||||
|
override in twelve, changing nothing, is the second consecutive window
|
||||||
|
meeting ADR-0017 D2's retirement condition — *"a full window's overrides
|
||||||
|
all change nothing"* — and the condition asks for two consecutive such
|
||||||
|
windows. **It should be evaluated, not restated again.**
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,29 @@ can only ever report upward.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 4.1 The MODEL stratum has a formal name now
|
||||||
|
|
||||||
|
The **extensive-form game** is what our MODEL stratum corresponds to, and
|
||||||
|
the correspondence is established rather than assumed
|
||||||
|
([CB-WP-0041](../workplans/CB-WP-0041-the-extensive-form-foundation.md)):
|
||||||
|
|
||||||
|
| EFG component | ours | checked by |
|
||||||
|
|---|---|---|
|
||||||
|
| histories | the journal | — |
|
||||||
|
| actions | `bot::legal_commands` | — |
|
||||||
|
| **information sets** | the seat's **observation history**, *not* `project()` | `perfect-recall` |
|
||||||
|
| chance | **a single root node**; the reshuffle is correlated with it | `a_game_is_determined_by_its_seed` |
|
||||||
|
| simultaneity | commit/reveal, the textbook encoding | `a_pending_selection_is_hidden_until_reveal` |
|
||||||
|
| payoffs | `Outcome` / `score()` | — |
|
||||||
|
|
||||||
|
**`project()` is an observation, not an information state**, and the
|
||||||
|
distinction cost 22 measured violations to find. It is the same split
|
||||||
|
OpenSpiel draws between `ObservationString` and `InformationStateString`.
|
||||||
|
|
||||||
|
**No port was built** — [ADR-0020](../decisions/ADR-0020-we-do-not-build-the-port.md)
|
||||||
|
refuses it, because making chance explicit would break the replay purity
|
||||||
|
the evidence discipline rests on.
|
||||||
|
|
||||||
## 5. Worked examples from this repo
|
## 5. Worked examples from this repo
|
||||||
|
|
||||||
Because a taxonomy with no instances was invented rather than observed
|
Because a taxonomy with no instances was invented rather than observed
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
id: CB-WP-0041
|
id: CB-WP-0041
|
||||||
kind: product
|
kind: product
|
||||||
title: "The extensive-form foundation"
|
title: "The extensive-form foundation"
|
||||||
status: active
|
status: done
|
||||||
state_hub_workstream_id: "dacfa1fe-81c3-4c97-b81e-ec66d95a611c"
|
state_hub_workstream_id: "dacfa1fe-81c3-4c97-b81e-ec66d95a611c"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ drawn*, which is weaker than "the property holds" and is printed as such.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CB-WP-0041-T02
|
id: CB-WP-0041-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "e223bf7d-6e4e-4377-8eac-a63e83a60d35"
|
state_hub_task_id: "e223bf7d-6e4e-4377-8eac-a63e83a60d35"
|
||||||
```
|
```
|
||||||
|
|
@ -130,11 +130,52 @@ approximate the distribution by sampling seeds.
|
||||||
- **say what an explicit chance player would cost** — that is the input to
|
- **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.
|
T04's decision, and guessing it is how a port gets built on a hope.
|
||||||
|
|
||||||
|
**Done 2026-08-08.** Three chance points, all reading the same root seed:
|
||||||
|
|
||||||
|
| where | what |
|
||||||
|
|---|---|
|
||||||
|
| `setup` | shuffles the Solution deck (`ChaChaRng::from_seed(seed)`), hands dealt off the top |
|
||||||
|
| `setup` | draws the **Lead** (`rng.draw(seats)`) from the same stream |
|
||||||
|
| `draw_solution` | reshuffles the discard when the deck empties, with `ChaChaRng::from_seed(seed ^ round)` |
|
||||||
|
|
||||||
|
**The Problems deal is not chance at all** — `edition::deal` is a pure
|
||||||
|
function of the vendored CSV, so every game gets the same board.
|
||||||
|
|
||||||
|
**So in extensive-form terms the tree has a single chance node at the
|
||||||
|
root.** `a_game_is_determined_by_its_seed` pins it.
|
||||||
|
|
||||||
|
**And that test was wrong first.** 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** (hands, Lead, deck order), and the
|
||||||
|
same mutation fails it. **A wrong-subject error inside T02's own control**,
|
||||||
|
caught by the mutation rather than by reading.
|
||||||
|
|
||||||
|
**The reshuffle is correlated with the root seed, and a real table's is
|
||||||
|
not.** The permutation is a pure function of `(seed, round)` — deliberate,
|
||||||
|
so replay never re-derives it (GameKernel K5), and the code says so.
|
||||||
|
`the_reshuffle_permutation_is_a_function_of_seed_and_round` pins that it
|
||||||
|
moves with **both** inputs.
|
||||||
|
|
||||||
|
**The consequence is a modelling one, not a defect**: at a table the
|
||||||
|
reshuffle is an independent random event. An EFG built from this engine
|
||||||
|
inherits the correlation and models a *restriction* of the game as played.
|
||||||
|
|
||||||
|
**The cost of an explicit chance player, stated for T04:**
|
||||||
|
- the root node is **not enumerable** — 24 Solution cards give 24!
|
||||||
|
orderings — so any EFG over this must **sample** chance, which is what
|
||||||
|
external-sampling MCCFR does and what our seed sweeps already do by
|
||||||
|
hand;
|
||||||
|
- making the reshuffle a genuine chance node **breaks the K5 purity that
|
||||||
|
makes replay deterministic**. That is the real cost and it is a
|
||||||
|
tension, not a line of code: our recordings replay *because* chance is
|
||||||
|
a function of state.
|
||||||
|
|
||||||
## Task: state the simultaneity encoding, and check it
|
## Task: state the simultaneity encoding, and check it
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CB-WP-0041-T03
|
id: CB-WP-0041-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "c95b32a1-5006-4acd-abc9-537543119344"
|
state_hub_task_id: "c95b32a1-5006-4acd-abc9-537543119344"
|
||||||
```
|
```
|
||||||
|
|
@ -148,11 +189,18 @@ stops doing that. That property is load-bearing for every claim in §1 of
|
||||||
the research note and is currently only implied by
|
the research note and is currently only implied by
|
||||||
`SelectionView::Hidden`.
|
`SelectionView::Hidden`.
|
||||||
|
|
||||||
|
**Done 2026-08-08.** `a_pending_selection_is_hidden_until_reveal` checks
|
||||||
|
**both halves** at four seats: before Reveal each seat sees its own
|
||||||
|
selection and no other, and after Reveal the information sets **merge** —
|
||||||
|
because an encoding that hides forever is not commit/reveal either.
|
||||||
|
|
||||||
|
Mutation-proven: make selections public and it fails naming the seats.
|
||||||
|
|
||||||
## Task: decide whether to build the port at all
|
## Task: decide whether to build the port at all
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CB-WP-0041-T04
|
id: CB-WP-0041-T04
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "3010fb0d-84cc-4fa8-8c23-726ee946cd79"
|
state_hub_task_id: "3010fb0d-84cc-4fa8-8c23-726ee946cd79"
|
||||||
```
|
```
|
||||||
|
|
@ -171,6 +219,28 @@ are too expensive and Track B borrows vocabulary rather than machinery.
|
||||||
port is a tier-L trigger and this workplan is M — it may not smuggle one
|
port is a tier-L trigger and this workplan is M — it may not smuggle one
|
||||||
in.
|
in.
|
||||||
|
|
||||||
|
**Done 2026-08-08.**
|
||||||
|
[ADR-0020](../decisions/ADR-0020-we-do-not-build-the-port.md) — **and the
|
||||||
|
answer is no.**
|
||||||
|
|
||||||
|
**The blocker is T02, not T01**, which inverts what the workplan expected.
|
||||||
|
Perfect recall looked like the risk and turned out to be 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 that breaks the K5 purity every recording, replay bundle and
|
||||||
|
trial-note hash in this repo 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 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,
|
||||||
|
a question three rounds of policy sweeps could not — and makes watching
|
||||||
|
for it the next action rather than a hope.
|
||||||
|
|
||||||
## Not in this workplan
|
## Not in this workplan
|
||||||
|
|
||||||
- **No EFG export, no OpenSpiel integration, no equilibrium computation.**
|
- **No EFG export, no OpenSpiel integration, no equilibrium computation.**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue