ADR-0023 + CB-WP-0049 T01: a policy is bound by what its seat can see
Some checks failed
ci / check (push) Has been cancelled
Some checks failed
ci / check (push) Has been cancelled
Policy::choose takes the whole GroundState -- every face-down Problem's suit and value, every seat's hand -- which is exactly what project() exists to withhold. No shipped policy reads it, but the first thing a competitive policy must do is VALUE a Problem, and value is the hidden field. The trap goes live on the first line of the F27 work. Established behaviourally rather than by narrowing the trait: vary only what the seat cannot see, and the choice must not move. That binds every policy including ones written later and outside this crate, without their cooperation. The mirror of ADR-0013 D1 -- same kernel, two searches, opposite permissions, discriminated by WHEN the question is asked; a policy plays from inside an information set, so retrospective permission would be strategy fusion. Running the control found two defects IN THE CONTROL: 1. The rearrangements rotated hidden values 2<->3 together, leaving max invariant -- so the deliberate peeker, which ranks by the largest hidden value, was not caught. A control whose variation is invariant under the statistic a violator reads is not a control. 2. It accused `random` of peeking, because it reused one policy instance and compared a first call against a fourth. It takes a constructor now, so every variant is judged from identical policy state. Both are a difference in output read as evidence about hidden state -- the wrong-subject family, found twice inside a control written to detect wrong subjects. Three mutations, three red. The control is proven against a deliberate violator before being trusted about compliant policies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
036658ac76
commit
c6607e8233
3 changed files with 515 additions and 0 deletions
|
|
@ -0,0 +1,92 @@
|
|||
# ADR-0023: a policy is bound by what its seat can see
|
||||
|
||||
status: accepted
|
||||
date: 2026-08-08
|
||||
decided by: agent, under the standing loop authorization
|
||||
tier: S (states a rule that already held in practice, and makes it
|
||||
checkable; no interface moved)
|
||||
references: [ADR-0013](ADR-0013-the-retrospective-search.md) (why the
|
||||
retrospective search may see everything), [CB-RES-0009](../research/CB-RES-0009-extensive-form-is-the-lingua-franca.md)
|
||||
(information sets), [specs/GameDesign.md](../specs/GameDesign.md) §1.4
|
||||
(sensitivity), F27
|
||||
|
||||
## Context
|
||||
|
||||
`Policy::choose` takes `&GroundState`. `GroundState` carries every
|
||||
face-down Problem's suit and value and every seat's hand.
|
||||
`GroundState::project` exists precisely to withhold those:
|
||||
`ProblemView::FaceDown` carries nothing but the fact that a Problem is
|
||||
there.
|
||||
|
||||
**So the trait hands a bot exactly the information the projection was
|
||||
written to hide.** No shipped policy reads it — `GreedyPolicy::rank`
|
||||
touches only `stress`, `claimed_by` and the legal set — but nothing
|
||||
prevents it, and nothing would notice.
|
||||
|
||||
This stops being latent immediately. CB-WP-0049 writes policies whose job
|
||||
is to **value Problems** in order to test F27, and a Problem's value is
|
||||
the field the projection hides. A seat that ranks a claim by the value of
|
||||
a face-down card is playing a game nobody can play, and every number
|
||||
measured with it would be about that game.
|
||||
|
||||
## D1 — a policy may not depend on what its seat cannot see
|
||||
|
||||
A measurement made with a peeking policy is not a measurement of GROUND.
|
||||
|
||||
**This is the mirror of ADR-0013 D1**, and the pairing is the point. The
|
||||
retrospective search is *allowed* to see everything, because after the
|
||||
game there is one world and a line found in it was executable in the only
|
||||
world there was. A policy plays **during** the game, from inside an
|
||||
information set, so the same permission would be a different claim
|
||||
entirely — the classic strategy-fusion error.
|
||||
|
||||
Same repository, two searches over the same kernel, opposite permissions,
|
||||
and the discriminator is **when the question is asked**.
|
||||
|
||||
## D2 — checked behaviourally, not by the type system
|
||||
|
||||
The obvious enforcement is to change `choose` to take `&GroundView`. We do
|
||||
not do that first, because a **behavioural control catches more**:
|
||||
|
||||
> Vary only what the seat cannot see, and assert the policy's choice does
|
||||
> not move.
|
||||
|
||||
- It binds **every** policy, including ones written later and ones written
|
||||
outside this crate, without their cooperation.
|
||||
- It survives a policy that gets the view and reconstructs hidden state by
|
||||
other means.
|
||||
- It is the sensitivity discipline the project already runs on
|
||||
(GameDesign §1.4): name the variable, vary it, watch the number.
|
||||
|
||||
A type change makes one route unavailable; the control makes the property
|
||||
false-if-violated. **The type change is still worth doing** and is not
|
||||
refused here — it is simply not the thing that establishes the property.
|
||||
|
||||
## D3 — the control must be shown to fail
|
||||
|
||||
A peek control that no policy can fail is decoration (ADR-0006 D3). The
|
||||
test carries a deliberately peeking policy — one that ranks by the value
|
||||
of face-down Problems — and asserts **that** policy is caught. A control
|
||||
proven only against compliant policies has not been proven at all.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Every policy is covered by the peek control, and a new one that peeks
|
||||
goes red on arrival rather than on review.
|
||||
- Hidden-information variation becomes a fixture: the same helper that
|
||||
proves policies blind can measure how much a line depended on what was
|
||||
hidden.
|
||||
- Policies that must reason about unseen Problems have to do it as
|
||||
*inference from what is visible* (a face-down Problem exists, and the
|
||||
scenario states the deal), which is the honest form of the same
|
||||
reasoning.
|
||||
- Bots remain unable to test claims about mechanisms they do not attend to
|
||||
— an orthogonal limit, recorded as F27, and **not** addressed by this.
|
||||
|
||||
## What was rejected
|
||||
|
||||
| rejected | why |
|
||||
|---|---|
|
||||
| changing `choose` to `&GroundView` first | closes one route; the property still would not be checked, and outside policies could still peek |
|
||||
| trusting review | four reviews on this repo found five, three, four and two FATAL items; peeking is invisible in a diff that looks like ranking |
|
||||
| allowing peeking for "stronger" bots | a stronger bot at a game nobody can play measures that game; ADR-0013 D1's permission is retrospective and does not transfer |
|
||||
|
|
@ -1099,3 +1099,230 @@ mod tests {
|
|||
// _every_seat_count` in lib.rs is the same arithmetic, inverted rather
|
||||
// than removed, and carries why the numbers changed.
|
||||
}
|
||||
|
||||
/// **A policy is bound by what its seat can see** ([ADR-0023]).
|
||||
///
|
||||
/// `Policy::choose` takes the whole `GroundState`, which carries every
|
||||
/// face-down Problem's suit and value and every seat's hand — exactly
|
||||
/// what `project` exists to withhold. Nothing in the type system stops a
|
||||
/// policy reading it, so this establishes the property behaviourally:
|
||||
/// **vary only what the seat cannot see, and the choice must not move.**
|
||||
///
|
||||
/// This binds every policy, including ones written later and ones written
|
||||
/// outside this crate, without their cooperation.
|
||||
///
|
||||
/// [ADR-0023]: ../../../decisions/ADR-0023-a-policy-is-bound-by-what-its-seat-can-see.md
|
||||
#[cfg(test)]
|
||||
pub mod blindness {
|
||||
use super::*;
|
||||
|
||||
/// Every rearrangement of what `seat` cannot see, as states that are
|
||||
/// **identical in that seat's projection**.
|
||||
///
|
||||
/// Concretely: permute the suits and values of every face-down
|
||||
/// Problem, and permute the other seats' hands. A seat that plays the
|
||||
/// game can tell none of these apart.
|
||||
pub fn hidden_rearrangements(state: &GroundState, seat: PlayerId) -> Vec<GroundState> {
|
||||
let mut out = Vec::new();
|
||||
// **The multiset must move, not only the arrangement.** The first
|
||||
// version rotated every hidden value 2<->3 together, which leaves
|
||||
// `max` and (with equal counts) `sum` invariant — so a policy
|
||||
// ranking by the largest hidden value was NOT caught. A control
|
||||
// whose variation is invariant under the statistic a violator
|
||||
// reads is not a control. These force all-2, all-3 and an
|
||||
// alternating split, so any symmetric function of the hidden
|
||||
// values moves across the family.
|
||||
for shift in 0..=3u8 {
|
||||
let mut alt = state.clone();
|
||||
for (i, p) in alt.problems.values_mut().enumerate() {
|
||||
if p.face_up {
|
||||
continue;
|
||||
}
|
||||
// A different card under the same back.
|
||||
p.value = match shift {
|
||||
0 => 2,
|
||||
1 => 3,
|
||||
2 => 2 + (i as u8 % 2),
|
||||
_ => 3 - (i as u8 % 2),
|
||||
};
|
||||
p.suit = rotate_suit(p.suit, shift + 1);
|
||||
}
|
||||
for (other, ps) in alt.players.iter_mut() {
|
||||
if *other == seat {
|
||||
continue;
|
||||
}
|
||||
for c in ps.hand.iter_mut() {
|
||||
c.suit = rotate_suit(c.suit, shift);
|
||||
}
|
||||
}
|
||||
out.push(alt);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn rotate_suit(s: crate::Suit, by: u8) -> crate::Suit {
|
||||
use crate::Suit::*;
|
||||
let order = [Clarify, Repair, Boundary, Change];
|
||||
let i = order.iter().position(|x| *x == s).unwrap_or(0);
|
||||
order[(i + by as usize) % order.len()]
|
||||
}
|
||||
|
||||
/// Does `policy` choose the same thing across every rearrangement?
|
||||
///
|
||||
/// Returns the disagreement if there is one, so the caller can report
|
||||
/// *what* moved rather than only that something did.
|
||||
/// **Takes a constructor, not a policy.** `choose` may advance
|
||||
/// internal state — `RandomPolicy` draws from its own stream — so
|
||||
/// reusing one instance compares a first call against a fourth and
|
||||
/// reports every stateful policy as a peeker. The first version did
|
||||
/// exactly that and accused `random`. Each variant is judged from an
|
||||
/// identical starting policy, which is the only way the difference
|
||||
/// between two runs is the state and nothing else.
|
||||
pub fn is_blind<P: Policy>(
|
||||
mut make: impl FnMut() -> P,
|
||||
state: &GroundState,
|
||||
seat: PlayerId,
|
||||
) -> Result<(), String> {
|
||||
let legal = legal_commands(state, seat);
|
||||
if legal.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let base = make().choose(state, seat, &legal, false);
|
||||
for alt in hidden_rearrangements(state, seat) {
|
||||
// **The same legal set, deliberately.** Rearranging hidden
|
||||
// cards can change which moves are legal, and a policy that
|
||||
// picks a different INDEX into a different list has not
|
||||
// necessarily seen anything. Holding the list fixed varies
|
||||
// only the state, which is the variable under test.
|
||||
let mut p = make();
|
||||
let got = p.choose(&alt, seat, &legal, false);
|
||||
if got != base {
|
||||
return Err(format!(
|
||||
"{} chose {:?} and then {:?} over the same legal moves, \
|
||||
with only hidden cards rearranged",
|
||||
p.name(),
|
||||
base,
|
||||
got
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod blindness_tests {
|
||||
use super::blindness::*;
|
||||
use super::*;
|
||||
use cb_game_runtime::{ScenarioGame, Setup};
|
||||
|
||||
fn deal(players: u8, seed: u64) -> GroundState {
|
||||
GroundState::setup(
|
||||
&Setup {
|
||||
players,
|
||||
preset: format!("standard-{players}p"),
|
||||
patch: Default::default(),
|
||||
},
|
||||
seed,
|
||||
)
|
||||
.expect("preset")
|
||||
}
|
||||
|
||||
/// **A policy that peeks IS CAUGHT** (ADR-0023 D3).
|
||||
///
|
||||
/// The control is proven against a deliberate violator before it is
|
||||
/// trusted about compliant ones. A peek control that nothing can fail
|
||||
/// is decoration (ADR-0006 D3) — and this is the shape of policy
|
||||
/// CB-WP-0049 is about to write, ranking a claim by a Problem's
|
||||
/// value, which is the field the projection hides.
|
||||
#[test]
|
||||
fn the_peek_control_catches_a_peeking_policy() {
|
||||
struct Peeker;
|
||||
impl Policy for Peeker {
|
||||
fn name(&self) -> &'static str {
|
||||
"peeker"
|
||||
}
|
||||
fn choose(
|
||||
&mut self,
|
||||
state: &GroundState,
|
||||
_seat: PlayerId,
|
||||
legal: &[GroundCommand],
|
||||
_may_pass: bool,
|
||||
) -> Choice {
|
||||
// Rank by the value of the highest FACE-DOWN Problem —
|
||||
// information the seat does not have.
|
||||
let hidden: u8 = state
|
||||
.problems
|
||||
.values()
|
||||
.filter(|p| !p.face_up)
|
||||
.map(|p| p.value)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
Choice::Command((hidden as usize) % legal.len())
|
||||
}
|
||||
}
|
||||
let st = deal(3, 7);
|
||||
let seat = PlayerId(0);
|
||||
assert!(
|
||||
st.problems.values().any(|p| !p.face_up),
|
||||
"the fixture must actually hide something"
|
||||
);
|
||||
let err = is_blind(|| Peeker, &st, seat)
|
||||
.expect_err("a policy reading face-down values must be caught");
|
||||
assert!(err.contains("peeker"), "{err}");
|
||||
}
|
||||
|
||||
/// And the shipped policies are blind.
|
||||
#[test]
|
||||
fn every_shipped_policy_is_blind_to_what_its_seat_cannot_see() {
|
||||
for seed in [1u64, 7, 42] {
|
||||
for players in [2u8, 4, 6] {
|
||||
let st = deal(players, seed);
|
||||
for seat in st.players.keys().copied() {
|
||||
is_blind(|| GreedyPolicy, &st, seat)
|
||||
.unwrap_or_else(|e| panic!("{players}p seed {seed}: {e}"));
|
||||
// Random is seeded from its own stream, so it is
|
||||
// blind for a different reason — included because
|
||||
// "every shipped policy" must mean every one.
|
||||
is_blind(|| RandomPolicy::new(seed), &st, seat)
|
||||
.unwrap_or_else(|e| panic!("{players}p seed {seed}: {e}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The rearrangements must actually rearrange.
|
||||
///
|
||||
/// A control that varies nothing passes for every policy, including
|
||||
/// the peeker — so the fixture's own sensitivity is asserted rather
|
||||
/// than assumed.
|
||||
#[test]
|
||||
fn the_rearrangements_change_the_hidden_cards() {
|
||||
let st = deal(4, 3);
|
||||
let alts = hidden_rearrangements(&st, PlayerId(0));
|
||||
assert!(!alts.is_empty());
|
||||
let hidden = |s: &GroundState| -> Vec<(u8, String)> {
|
||||
s.problems
|
||||
.values()
|
||||
.filter(|p| !p.face_up)
|
||||
.map(|p| (p.value, format!("{:?}", p.suit)))
|
||||
.collect()
|
||||
};
|
||||
assert!(
|
||||
alts.iter().any(|a| hidden(a) != hidden(&st)),
|
||||
"no rearrangement changed a hidden card"
|
||||
);
|
||||
// And they leave the VISIBLE game alone, or the test would be
|
||||
// varying two things at once.
|
||||
let seen = |s: &GroundState| -> Vec<(u8, String)> {
|
||||
s.problems
|
||||
.values()
|
||||
.filter(|p| p.face_up)
|
||||
.map(|p| (p.value, format!("{:?}", p.suit)))
|
||||
.collect()
|
||||
};
|
||||
for a in &alts {
|
||||
assert_eq!(seen(a), seen(&st), "a rearrangement moved a face-up card");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
196
workplans/CB-WP-0049-a-seat-that-plays-its-own-objective.md
Normal file
196
workplans/CB-WP-0049-a-seat-that-plays-its-own-objective.md
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
---
|
||||
id: CB-WP-0049
|
||||
kind: product
|
||||
title: "A seat that plays its own objective"
|
||||
status: in_progress
|
||||
---
|
||||
|
||||
# Purpose
|
||||
|
||||
```
|
||||
structural tier M (adds a measurement instrument whose output changes
|
||||
what published panels mean)
|
||||
declared tier M
|
||||
```
|
||||
|
||||
Closes the gap [CB-WP-0048](CB-WP-0048-a-configuration-not-a-variant.md)
|
||||
opened: configurations are now selectable and **nothing can judge them**.
|
||||
|
||||
## The finding this exists to answer
|
||||
|
||||
**F27.** `scenario-panel` measured 4 scenarios × 3 modes × 3 seat bands
|
||||
and found group success *exactly* equal across all three scoring modes in
|
||||
all 36 cells. The arithmetic is right and the reason is simple:
|
||||
`GreedyPolicy` never reads `state.mode`. The same games are played and
|
||||
only the winner set is carved differently.
|
||||
|
||||
So **the two competitive modes are scoring lenses over cooperative
|
||||
play**, and the same will be true of every aspect module: a bot that does
|
||||
not attend to a mechanism cannot test a claim about that mechanism's
|
||||
incentives. CB-RES-0010 §5.4's last bullet is F27 restated from the
|
||||
design side.
|
||||
|
||||
This is now the binding constraint on the whole programme. `scoped_plus_attack_soothe`
|
||||
is selectable today (CB-WP-0048 T01) and no instrument in this repo can
|
||||
say whether it is any good.
|
||||
|
||||
## The prerequisite nobody had noticed
|
||||
|
||||
`Policy::choose` takes `&GroundState`, which carries **every face-down
|
||||
Problem's suit and value and every seat's hand**. `project` exists to
|
||||
withhold exactly those.
|
||||
|
||||
No shipped policy reads them. But the first thing a competitive policy
|
||||
must do is **value a Problem**, and value is the field the projection
|
||||
hides — so the trap becomes live on the first line of the work this
|
||||
workplan is about. [ADR-0023](../decisions/ADR-0023-a-policy-is-bound-by-what-its-seat-can-see.md).
|
||||
|
||||
## Task: a policy is bound by what its seat can see
|
||||
|
||||
```task
|
||||
id: CB-WP-0049-T01
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Per ADR-0023: established **behaviourally**, not by changing the trait —
|
||||
vary only what the seat cannot see and the choice must not move. That
|
||||
binds every policy, including ones written later and outside this crate,
|
||||
without their cooperation.
|
||||
|
||||
**Done 2026-08-08.** `bot::blindness` provides `hidden_rearrangements`
|
||||
and `is_blind`; `GreedyPolicy` and `RandomPolicy` are proven blind across
|
||||
three seeds × three seat bands × every seat.
|
||||
|
||||
### Two defects in the control itself, both caught by running it
|
||||
|
||||
**1. The first rearrangement was invariant under what a violator reads.**
|
||||
It rotated every hidden value 2↔3 *together*, which leaves `max`
|
||||
unchanged and (at equal counts) `sum` too — so the deliberate peeker,
|
||||
which ranks by the largest hidden value, **was not caught**. A control
|
||||
whose variation is invariant under the statistic a violator reads is not
|
||||
a control. The family now forces all-2, all-3 and two alternating splits,
|
||||
so any symmetric function of the hidden values moves across it.
|
||||
|
||||
**2. It accused `random` of peeking.** `is_blind` reused one policy
|
||||
instance, so it compared a first call against a fourth — and
|
||||
`RandomPolicy` advances its own stream. It takes a **constructor** now,
|
||||
so every variant is judged from an identical starting policy and the only
|
||||
difference between two runs is the state.
|
||||
|
||||
Both are the same mistake in different clothes: **a difference in the
|
||||
output was read as evidence about the hidden state**, when the first was
|
||||
insensitive to it and the second was caused by something else entirely.
|
||||
That is the wrong-subject family, found twice inside one control written
|
||||
to detect wrong subjects.
|
||||
|
||||
| mutation | what went red |
|
||||
|---|---|
|
||||
| rearrangements leave the multiset invariant | *"a policy reading face-down values must be caught"* |
|
||||
| rearrangements vary nothing at all | *"no rearrangement changed a hidden card"* + the peeker escapes |
|
||||
| one policy instance reused across variants | *"random chose Command(5) and then Command(2)"* |
|
||||
|
||||
**The control is proven against a deliberate violator** before being
|
||||
trusted about compliant policies (ADR-0023 D3) — a peek control nothing
|
||||
can fail is decoration.
|
||||
|
||||
## Task: an objective, taken from the game's own scoring
|
||||
|
||||
```task
|
||||
id: CB-WP-0049-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
What a seat is trying to maximise, per `ScoringMode`:
|
||||
|
||||
| mode | objective |
|
||||
|---|---|
|
||||
| SHARED GROUND | the group total |
|
||||
| COMMON PROBLEM | own claimed value − own Blame |
|
||||
| BONDED COALITIONS | own coalition's summed personal score |
|
||||
|
||||
**Derived from `GroundState::score`, never reimplemented.** All three are
|
||||
already computed there for the outcome; a second copy in the bot would be
|
||||
a second definition of winning, and the two would disagree the first time
|
||||
ground-game rules on F28.
|
||||
|
||||
**Controls:**
|
||||
- **the objective agrees with `Outcome` on a finished game**, for every
|
||||
mode — asserted against the real scoring rather than a fixture;
|
||||
- **a seat's objective is not the group's** under the competitive modes,
|
||||
shown on a concrete board rather than argued;
|
||||
- **the objective is blind** (T01's control applies to anything that
|
||||
reaches a policy).
|
||||
|
||||
## Task: a seat that plays it, and the F27 re-measurement
|
||||
|
||||
```task
|
||||
id: CB-WP-0049-T03
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
A policy that maximises its own objective, delegating to
|
||||
`GreedyPolicy::rank` wherever the objective is silent — **delegating, not
|
||||
copying**, which CB-WP-0039 learned the hard way when a re-typed "greedy
|
||||
with one preference changed" differed in five places and burned the
|
||||
Freedom token in round one of every game.
|
||||
|
||||
Then re-run `scenario-panel`.
|
||||
|
||||
**The result is not predicted here.** Either the modes now diverge — F27
|
||||
resolves, and the competitive modes are real — or they still do not, and
|
||||
that is a much more interesting finding about GROUND: that the game's
|
||||
scoring modes do not reach its decisions. Both outcomes are publishable
|
||||
and the workplan must not be written as though one is expected.
|
||||
|
||||
**Controls:**
|
||||
- **the panel's group-success column moves, or it is reported as not
|
||||
moving** — a null result stated as a null result;
|
||||
- **the competitive policy is blind** (T01);
|
||||
- **greedy's numbers are unchanged**, so the new policy is an addition
|
||||
and not a silent edit of the published baseline.
|
||||
|
||||
## Task: module-aware evaluation
|
||||
|
||||
```task
|
||||
id: CB-WP-0049-T04
|
||||
status: todo
|
||||
priority: normal
|
||||
```
|
||||
|
||||
A seat under `problem_stress.scoped` should prefer clearing a Problem
|
||||
whose scope includes it; under `attack_relief.self_soothe_ge4`, ATTACK
|
||||
becomes a Stress tool at the gate. Both are **visible** to the seat
|
||||
(`ProblemMarker` is in the projection), so neither needs hidden state.
|
||||
|
||||
**The composition risk is the same one schema 2 exists to retire.** One
|
||||
policy per module is `H2AwarePolicy` — the blob at a new layer. Evaluation
|
||||
should compose per aspect the way a configuration does, and if that is not
|
||||
achievable cheaply, the honest move is one policy that reads the resolved
|
||||
`Rules` rather than several that each assume one.
|
||||
|
||||
**Controls:**
|
||||
- **a module-aware seat differs from greedy ONLY where the module is
|
||||
live** — under the baseline the two must be identical, which is the
|
||||
control that separates "attends to the module" from "plays differently";
|
||||
- **blind** (T01).
|
||||
|
||||
## Not done here
|
||||
|
||||
- **The trait still takes `&GroundState`.** ADR-0023 D2 chose the
|
||||
behavioural control first, on the grounds that it binds policies the
|
||||
type system cannot reach. Narrowing `choose` to `&GroundView` remains
|
||||
worth doing and is not refused — it is simply not what establishes the
|
||||
property.
|
||||
- **Opponent modelling is out of scope.** These policies play their own
|
||||
objective; none of them models another seat playing theirs. A
|
||||
competitive mode where nobody anticipates a rival is still a weak test
|
||||
of that mode, and saying so is part of T03's result.
|
||||
- **No lookahead.** `search.rs` is retrospective and **may not be reused
|
||||
here**: ADR-0013 D1 permits it to see everything precisely because
|
||||
after the game there is one world. A policy plays from inside an
|
||||
information set, so the same permission would be strategy fusion. Same
|
||||
kernel, two searches, opposite permissions, and the discriminator is
|
||||
when the question is asked.
|
||||
Loading…
Add table
Add a link
Reference in a new issue