Compare commits
2 commits
55a475b1a9
...
4b40a537a6
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b40a537a6 | |||
| d4d25b903e |
5 changed files with 581 additions and 1 deletions
1
Makefile
1
Makefile
|
|
@ -237,6 +237,7 @@ difficulty:
|
|||
panels:
|
||||
@cargo run --release -q -p games-ground --example attack-value
|
||||
@cargo run --release -q -p games-ground --example regulation
|
||||
@cargo run --release -q -p games-ground --example perfect-recall
|
||||
|
||||
# CB-WP-0022 T05: the design-finding register, reported over
|
||||
# specs/GroundRules.md. Shows the QUEUE by default; the log of closed
|
||||
|
|
|
|||
243
games/ground/examples/perfect-recall.rs
Normal file
243
games/ground/examples/perfect-recall.rs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
//! **Does this engine satisfy perfect recall?** (CB-WP-0041 T01)
|
||||
//!
|
||||
//! Perfect recall — every player remembers their own past actions and
|
||||
//! observations — is the assumption **CFR and exploitability both rest
|
||||
//! on** ([`CB-RES-0009`]). Nobody had checked whether ours holds.
|
||||
//!
|
||||
//! Formally: for a seat `i`, any two histories in the same information
|
||||
//! set of `i` must agree on the sequence of `i`'s own past actions.
|
||||
//!
|
||||
//! **What this can and cannot say.** It samples histories and looks for a
|
||||
//! pair that lands in the same information set with different own-action
|
||||
//! prefixes. So:
|
||||
//!
|
||||
//! > **It can prove perfect recall FAILS. It cannot prove it holds.**
|
||||
//!
|
||||
//! A clean run means "no violation found in this sample", which is a
|
||||
//! weaker statement than "the property holds" and is reported as such.
|
||||
//! Saying otherwise would be an ACCOUNT failure over an INSTRUMENT that
|
||||
//! cannot support it ([`Taxonomy.md`] §2.2).
|
||||
//!
|
||||
//! [`CB-RES-0009`]: ../../../research/CB-RES-0009-extensive-form-is-the-lingua-franca.md
|
||||
//! [`Taxonomy.md`]: ../../../specs/Taxonomy.md
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use cb_game_runtime::{Project, ScenarioGame, Setup, Viewer};
|
||||
use cb_kernel::PlayerId;
|
||||
use games_ground::bot::{play, Choice, Policy, RandomPolicy};
|
||||
use games_ground::{GroundCommand, GroundState};
|
||||
|
||||
/// One decision point: what the seat could see, and what it had done.
|
||||
struct Observed {
|
||||
/// **Reading A** — the information set taken to be the seat's
|
||||
/// CURRENT projection, which is what `project(Viewer::Player(seat))`
|
||||
/// returns and what the page renders.
|
||||
observation: String,
|
||||
/// **Reading B** — the information set taken to be the seat's whole
|
||||
/// OBSERVATION HISTORY: every view it has seen and every action it
|
||||
/// has taken, in order.
|
||||
///
|
||||
/// OpenSpiel draws exactly this distinction —
|
||||
/// `ObservationString` versus `InformationStateString` — and the
|
||||
/// second exists because the first does not give perfect recall.
|
||||
info_state: String,
|
||||
/// The seat's own past actions, in order — the thing perfect recall
|
||||
/// says must be determined by the information set.
|
||||
own_actions: Vec<String>,
|
||||
}
|
||||
|
||||
/// Wraps a policy and records what each seat saw before it moved.
|
||||
struct Recorder<P: Policy> {
|
||||
inner: P,
|
||||
log: Rc<RefCell<Vec<(PlayerId, Observed)>>>,
|
||||
own: Rc<RefCell<BTreeMap<PlayerId, Vec<String>>>>,
|
||||
hist: Rc<RefCell<BTreeMap<PlayerId, Vec<String>>>>,
|
||||
}
|
||||
|
||||
impl<P: Policy> Policy for Recorder<P> {
|
||||
fn name(&self) -> &'static str {
|
||||
"recorder"
|
||||
}
|
||||
fn choose(
|
||||
&mut self,
|
||||
state: &GroundState,
|
||||
seat: PlayerId,
|
||||
legal: &[GroundCommand],
|
||||
may_pass: bool,
|
||||
) -> Choice {
|
||||
// The information set is what this seat can see — nothing more.
|
||||
let view = state.project(Viewer::Player(seat));
|
||||
let observation = serde_json::to_string(&view).expect("view serialises");
|
||||
let own_actions = self.own.borrow().get(&seat).cloned().unwrap_or_default();
|
||||
// The observation history: what this seat has seen and done, in
|
||||
// order, up to and including now.
|
||||
let mut hist = self.hist.borrow_mut();
|
||||
let h = hist.entry(seat).or_default();
|
||||
h.push(observation.clone());
|
||||
let info_state = h.join("\u{1e}");
|
||||
drop(hist);
|
||||
self.log.borrow_mut().push((
|
||||
seat,
|
||||
Observed {
|
||||
observation,
|
||||
info_state,
|
||||
own_actions,
|
||||
},
|
||||
));
|
||||
|
||||
let choice = self.inner.choose(state, seat, legal, may_pass);
|
||||
let taken = match &choice {
|
||||
Choice::Command(i) => format!("{:?}", legal[*i]),
|
||||
Choice::Pass => "pass".to_string(),
|
||||
};
|
||||
self.own
|
||||
.borrow_mut()
|
||||
.entry(seat)
|
||||
.or_default()
|
||||
.push(taken.clone());
|
||||
// The action is part of what the seat remembers.
|
||||
self.hist.borrow_mut().entry(seat).or_default().push(taken);
|
||||
choice
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Random play, because the question is about the SHAPE of the
|
||||
// information partition and a greedy policy visits a narrow slice of
|
||||
// it. A policy that always makes the same choice cannot produce two
|
||||
// histories in one information set with different prefixes.
|
||||
let seeds = 0..400u64;
|
||||
let mut points = 0usize;
|
||||
let mut observation_violations: Vec<String> = Vec::new();
|
||||
let mut info_state_violations: Vec<String> = Vec::new();
|
||||
let mut witness: Option<(String, Vec<String>, Vec<String>)> = None;
|
||||
|
||||
for players in [2u8, 3, 4, 6] {
|
||||
// Each reading maps its key to the own-action prefix first
|
||||
// seen with it. A second, different prefix is a violation.
|
||||
let mut by_observation: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
||||
let mut by_info_state: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
||||
|
||||
for seed in seeds.clone() {
|
||||
let Ok(st) = GroundState::setup(
|
||||
&Setup {
|
||||
players,
|
||||
preset: format!("standard-{players}p"),
|
||||
patch: Default::default(),
|
||||
},
|
||||
seed,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let log = Rc::new(RefCell::new(Vec::new()));
|
||||
let own = Rc::new(RefCell::new(BTreeMap::new()));
|
||||
let hist = Rc::new(RefCell::new(BTreeMap::new()));
|
||||
let mut ps: Vec<Box<dyn Policy>> = (0..players)
|
||||
.map(|i| {
|
||||
Box::new(Recorder {
|
||||
inner: RandomPolicy::new(seed ^ u64::from(i) ^ 0x9E37),
|
||||
log: log.clone(),
|
||||
own: own.clone(),
|
||||
hist: hist.clone(),
|
||||
}) as Box<dyn Policy>
|
||||
})
|
||||
.collect();
|
||||
let Ok(_) = play(st, &mut ps) else { continue };
|
||||
|
||||
for (seat, obs) in log.borrow().iter() {
|
||||
points += 1;
|
||||
// The key includes WHO is looking: two seats with
|
||||
// identical views are different information sets.
|
||||
let a = format!("{seat:?}|{}", obs.observation);
|
||||
match by_observation.get(&a) {
|
||||
None => {
|
||||
by_observation.insert(a, obs.own_actions.clone());
|
||||
}
|
||||
Some(prior) if *prior == obs.own_actions => {}
|
||||
Some(prior) => {
|
||||
if observation_violations.is_empty() {
|
||||
witness = Some((
|
||||
obs.observation.clone(),
|
||||
prior.clone(),
|
||||
obs.own_actions.clone(),
|
||||
));
|
||||
}
|
||||
observation_violations.push(format!("{players}p seed {seed} {seat:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
let b = format!("{seat:?}|{}", obs.info_state);
|
||||
match by_info_state.get(&b) {
|
||||
None => {
|
||||
by_info_state.insert(b, obs.own_actions.clone());
|
||||
}
|
||||
Some(prior) if *prior == obs.own_actions => {}
|
||||
Some(prior) => {
|
||||
info_state_violations.push(format!(
|
||||
"{players}p seed {seed} {seat:?}: {prior:?} vs {:?}",
|
||||
obs.own_actions
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("perfect recall — CB-WP-0041 T01\n");
|
||||
println!(" decision points sampled {points}\n");
|
||||
println!(" Reading A — information set = the seat's CURRENT projection");
|
||||
println!(
|
||||
" violations {}",
|
||||
observation_violations.len()
|
||||
);
|
||||
println!(" Reading B — information set = the seat's OBSERVATION HISTORY");
|
||||
println!(
|
||||
" violations {}\n",
|
||||
info_state_violations.len()
|
||||
);
|
||||
|
||||
if let Some((view, a, b)) = &witness {
|
||||
println!(" Reading A witness — one view, two own-action prefixes:");
|
||||
println!(" A: {a:?}");
|
||||
println!(" B: {b:?}");
|
||||
println!(" shared view: {}\n", &view[..view.len().min(220)]);
|
||||
}
|
||||
|
||||
// **Reading B is the load-bearing claim.** If observation histories
|
||||
// do not give perfect recall, no EFG can be built from this engine
|
||||
// and Track B closes.
|
||||
assert!(
|
||||
info_state_violations.is_empty(),
|
||||
"perfect recall FAILS even under observation histories: {} violation(s). \
|
||||
No extensive-form game can be built from this engine, and every \
|
||||
equilibrium concept is unsound here.\n{}",
|
||||
info_state_violations.len(),
|
||||
info_state_violations.first().cloned().unwrap_or_default()
|
||||
);
|
||||
|
||||
// **Reading A failing is the FINDING, not an error.** It is why
|
||||
// OpenSpiel separates ObservationString from InformationStateString.
|
||||
assert!(
|
||||
!observation_violations.is_empty(),
|
||||
"Reading A found no violation. Either the sample is too small or the \
|
||||
projection carries more history than it appears to — either way the \
|
||||
conclusion below is unsupported and must be re-derived."
|
||||
);
|
||||
|
||||
println!(" RESULT");
|
||||
println!(" The current projection is an OBSERVATION, not an information");
|
||||
println!(
|
||||
" state: {} sampled pairs share a view while disagreeing about",
|
||||
observation_violations.len()
|
||||
);
|
||||
println!(" what the seat itself had done. Perfect recall FAILS on that");
|
||||
println!(" reading, and an EFG keyed on `project()` would be unsound.");
|
||||
println!();
|
||||
println!(" Keyed on the observation HISTORY, no violation was found.");
|
||||
println!(" **That is not a proof** — this check can falsify perfect");
|
||||
println!(" recall and cannot establish it. A clean run means no");
|
||||
println!(" counterexample was drawn from this sample.");
|
||||
}
|
||||
|
|
@ -178,7 +178,7 @@ id = "CB-REV-0002/7"
|
|||
name = "variant panels"
|
||||
target = "panels"
|
||||
cadence = "all"
|
||||
checks = "both H1 measurement harnesses run; a short cell fails, and so does a cell whose games did not reach an outcome"
|
||||
checks = "the measurement harnesses run; a short cell fails, a cell whose games did not reach an outcome fails, and perfect recall is re-derived on both readings"
|
||||
notes = """
|
||||
Registered because the second adversarial review asked what the harness
|
||||
would report if the work silently stopped, and the answer was "green, and
|
||||
|
|
|
|||
175
workplans/CB-WP-0041-the-extensive-form-foundation.md
Normal file
175
workplans/CB-WP-0041-the-extensive-form-foundation.md
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
---
|
||||
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.
|
||||
161
workplans/CB-WP-0042-h2-scoped-problem-stress.md
Normal file
161
workplans/CB-WP-0042-h2-scoped-problem-stress.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
---
|
||||
id: CB-WP-0042
|
||||
kind: product
|
||||
title: "H2 — scoped problem stress"
|
||||
status: ready
|
||||
---
|
||||
|
||||
# Purpose
|
||||
|
||||
```
|
||||
structural tier M (touches a canonical interface: edition loading
|
||||
becomes variant-parameterised, and the kernel gains
|
||||
per-Problem state)
|
||||
chaos d8 = 4 → no override
|
||||
declared tier M
|
||||
```
|
||||
|
||||
**Declaration 2 of chaos window 4.**
|
||||
|
||||
## Why, and it is a direct answer to ours
|
||||
|
||||
We reported that H1-A behaves as a **solve-rate tax**: a flat +1 to every
|
||||
seat while any Problem is unclaimed, so the pressure scales with the
|
||||
number of Problems while the intended effect does not.
|
||||
|
||||
`ground-game` responded with **H2 — scoped problem stress**. Unclaimed
|
||||
Problems now tick **only the seats in their scope**:
|
||||
|
||||
| scope | who takes the +1 |
|
||||
|---|---|
|
||||
| `global` | all seats |
|
||||
| `personal` | the Problem's **owner** |
|
||||
| `bond` | the owner's **Bond network** — owner plus every seat reachable over Bond edges only, never Rivalry; degree 0 falls back to personal |
|
||||
|
||||
Assigned by hidden priority: **0 (Surface) global, 1 personal, 2 personal,
|
||||
3 bond, 4 personal** — which is a seat-band dial without a difficulty
|
||||
card, since 2p never has the bond card in play.
|
||||
|
||||
**It does not stack with H1.** `replaces_experiments: [h1-problem-stress]`,
|
||||
base is `ground-darvo-r0`, and there is **no ATTACK self-soothe**.
|
||||
|
||||
## Why this is bigger than H1 was
|
||||
|
||||
H1 was two arithmetic deltas on existing state. H2 needs three things we
|
||||
do not have:
|
||||
|
||||
1. **Variant-scoped edition data.** H2 overrides `Problems.csv` with a
|
||||
new `stress_scope` column. `edition.rs` `include_str!`s r0's copy at
|
||||
compile time — **the data is currently a constant, not a parameter.**
|
||||
2. **Per-Problem ownership**, assigned at setup. That is **new state**,
|
||||
so it reaches the state hash and every recording.
|
||||
3. **Bond-network reachability** — a graph traversal over Bond edges
|
||||
only, with a degree-0 fallback.
|
||||
|
||||
## Task: variant-scoped edition data
|
||||
|
||||
```task
|
||||
id: CB-WP-0042-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
**Controls:**
|
||||
- **the baseline's data is bit-for-bit what it was**, and its state hashes
|
||||
do not move — the control that protected CB-WP-0038 and the one that
|
||||
invalidates every prior measurement if it fails;
|
||||
- the H2 package is **vendored with digests** like every other borrowed
|
||||
file, and `edition-check` covers it — sibling discovery walks the disk
|
||||
now, so an undigested file fails (CB-REV-0003 #8);
|
||||
- **`stress_scope`'s assignment is read from the file, not hardcoded.**
|
||||
The delta states the priority→scope mapping; **it is the edition's to
|
||||
state and ours to read**, and F25 exists because we hardcoded numbers
|
||||
the edition already carried.
|
||||
|
||||
## Task: ownership at setup
|
||||
|
||||
```task
|
||||
id: CB-WP-0042-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
`H2-OWN`: ascending hidden priority among in-play non-global Problems,
|
||||
starting at Lead, stepping clockwise. Global Problems have no owner.
|
||||
|
||||
**Controls:**
|
||||
- **deterministic**, and asserted so — the delta says "deterministic for
|
||||
sims" and a seeded-but-unstated order would be untestable;
|
||||
- **the baseline carries no owners and its hash does not move.** New
|
||||
state that is `None` under baseline must serialise as it did before, or
|
||||
every recorded scenario breaks;
|
||||
- **exactly one owner per eligible Problem**, checked at every seat count,
|
||||
because the assignment walks two sequences at once and off-by-one is
|
||||
the obvious failure.
|
||||
|
||||
## Task: scoped pressure
|
||||
|
||||
```task
|
||||
id: CB-WP-0042-T03
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
`H2-A`, at Round End, before the clamp and the DARVO arm check — the same
|
||||
ordering H1-A needed, and the same trap.
|
||||
|
||||
**Controls:**
|
||||
- **each scope fails on its own**, by mutation: global hitting only the
|
||||
owner, personal hitting everyone, bond stopping at the owner;
|
||||
- **`stacking: true` is tested** — two open bond Problems must tick the
|
||||
network **twice**, and a careless implementation applies it once;
|
||||
- **Rivalry edges are not traversed.** The delta says Bond only, and a
|
||||
traversal that follows any relation is the single most likely defect;
|
||||
- **degree 0 falls back to personal**, which is the branch a test forgets;
|
||||
- ordering asserted as in CB-WP-0038: pressure before the arm check.
|
||||
|
||||
## Task: SOLVE is not restricted by scope
|
||||
|
||||
```task
|
||||
id: CB-WP-0042-T04
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
`H2-SOLVE`: any seat with a matching suit may claim; the owner need not be
|
||||
the solver, and altruistic clearing is **intended**.
|
||||
|
||||
**Control:** our engine has no owner concept today, so this is already
|
||||
true — which makes it a **regression test, not a feature**. T02 adds
|
||||
ownership, and the risk is that ownership silently becomes a permission.
|
||||
The test must fail if it does.
|
||||
|
||||
## Task: measure, and report what fails
|
||||
|
||||
```task
|
||||
id: CB-WP-0042-T05
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Same instrument as H1, same panel, both variants in one run.
|
||||
|
||||
**Controls:**
|
||||
- **greedy and `reactive` both**, because H1's whole verdict turned on
|
||||
which policy was asked (CB-EV-0031);
|
||||
- **report against ground-game's own criteria** for H2, from their design
|
||||
note — **read it, do not reuse H1's §3.2 from memory**;
|
||||
- **the bond-scope hypothesis is the interesting one and must be measured
|
||||
directly**: their claim is that a shared tick makes a Bond network
|
||||
*jointly motivated* to solve that card. Whether a policy that does not
|
||||
model other seats can express that at all is an open question, and the
|
||||
honest answer may be "our panel cannot test this hypothesis";
|
||||
- **`make panels` runs it**, or the figures come from an ungated binary
|
||||
again (CB-REV-0002 #7).
|
||||
|
||||
## Not in this workplan
|
||||
|
||||
- **No stacking with H1.** The package forbids it explicitly.
|
||||
- **No felt-play.** H2's central claim is about *motivation* — a bonded
|
||||
pair caring about each other's card — and 200-game aggregates measure
|
||||
dynamics, not that ([`Taxonomy.md`](../specs/Taxonomy.md) §4).
|
||||
Loading…
Add table
Add a link
Reference in a new issue