CB-REV-0001: the adversarial review, and it was not approvable
Some checks failed
ci / check (push) Failing after 4s
Some checks failed
ci / check (push) Failing after 4s
Thirteen challenges, five FATAL, all five conceded. Nothing had reached ground-game, which is the only reason this is a correction and not a retraction. The worst: `Reactive` was not "greedy with one preference changed". It differed in five, including SpendFreedom — ranked 95 unconditionally against greedy's `95 if gated else 0` — so the seat burned its Freedom token in round one of every game. A second change to the exact mechanism under study, and every number in CB-EV-0031 was measuring it. The pass claimed ADR-0018's one-varying-parameter discipline in its own workplan while violating it. GreedyPolicy::rank is now public and the policy delegates, overriding one match arm, so the control is structurally true. Withdrawn entirely: "H1-B suppresses DARVO in the attacker". Disabling H1-B under the corrected policy changes the arm count by exactly zero. The pass hedged the wrong variable — it disclaimed "the number 2" and defended "the direction", and the direction is what failed. The supporting inference was invalid anyway: final Stress cannot show who armed, because DarvoEnded resets the stage and REVERSE gives its owner -2. Corrected: criterion 1 was failed on the greedy column while the pass's own printed table showed 31-1000 arms in the other columns — the selective-column move, in the file that names it. "Peak Stress was 1" was a maximum over StressSet payloads, not held state (true: 2); the baseline game count was 1,600 not 3,200; and "a reckless policy plays identically to a careful one" is refuted by this repo's own rank-95 policy. Inert controls replaced, each verified red against the reviewer's own mutation: the baseline hash test compared two identically-constructed states (serde(skip) on variant left 57/57 green); the `unchanged:` test checked 3 of 7 entries and passed with SOLVE made illegal; H1-A's ordering and H1-B's OU-cancel path had no test at all. edition-check now covers catalog.yaml and rules_delta.yaml, whose digests CB-WP-0038 claimed and never recorded — the review found it and reported it unverified rather than absent, which was the right call. Still open: H1-B on the DARVO extra-Attack path is untested, regulation.rs still skips setup failures silently, and round-5 arms are counted though they can never act. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
38106791a6
commit
041c0e7c3e
9 changed files with 489 additions and 101 deletions
|
|
@ -46,6 +46,27 @@ falsifiers rather than merely skipped
|
|||
the designer placed *outside* the core; importing it would break the claim
|
||||
that this engine plays the edition as printed (ADR-0015 D2).
|
||||
|
||||
## The catalog and the H1 experiment package
|
||||
|
||||
Vendored 2026-08-08 from `ground-game` `8886543` (CB-WP-0038). These are
|
||||
**outside this directory** — `editions/catalog.yaml` and
|
||||
`editions/experiments/h1-problem-stress/` — because the catalog selects
|
||||
*between* packages and does not belong inside one.
|
||||
|
||||
**Digests were claimed for these by CB-WP-0038 T01 and not recorded**;
|
||||
the adversarial review ([CB-REV-0001](../../reviews/CB-REV-0001-h1.md))
|
||||
reported it unverified and it was a real gap.
|
||||
|
||||
```
|
||||
sha256 eb21fa3237637790fef601fe6668a190a549715e9a78b7dfe47b40cd069b648e ../catalog.yaml
|
||||
sha256 f58e81f84ea2b0d16e39932261eb3f3d9890345cdf37ad6f0b3abc00636840be ../experiments/h1-problem-stress/rules_delta.yaml
|
||||
sha256 7b1cc0149122b855e827bc930576ed165bf7dd8d62707e845a9e514ce3521f8e ../experiments/h1-problem-stress/Actions.csv
|
||||
```
|
||||
|
||||
**`rules_delta.yaml` is the load-bearing one**: it is the executable
|
||||
statement of H1, and the kernel implements it. A silent change there is a
|
||||
silent change to what we measured.
|
||||
|
||||
## Digests
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -20,10 +20,18 @@ than trusted, and the ATTACK panel re-run for both rule sets.
|
|||
`ground-game`'s success criteria (design note §3.2), answered one by one.
|
||||
**Three of four fail.**
|
||||
|
||||
> **CORRECTED 2026-08-08 after adversarial review**
|
||||
> ([`CB-REV-0001`](../reviews/CB-REV-0001-h1.md)). Criteria 1 and 2 were
|
||||
> rendered as flat failures **on the strength of the greedy column
|
||||
> alone**, while this pass's own harness printed 31–1000 DARVO arms per
|
||||
> cell in the rank-75 and rank-95 columns. That is the selective-column
|
||||
> move this project says it exists to prevent, committed in the file that
|
||||
> says so. The verdicts below are restated per column.
|
||||
|
||||
| # | criterion | verdict |
|
||||
|---|---|---|
|
||||
| 1 | DARVO arm rate non-trivial, "not 0/500" | **fails** — still **0** under greedy at every seat count |
|
||||
| 2 | ATTACK selection rises for some subpopulation | **fails** — greedy still attacks **0** times; and for the rank-75 policy ATTACK *fell*, 604 → 180 at 2p |
|
||||
| 1 | DARVO arm rate non-trivial, "not 0/500" | **not a flat fail.** **0** under greedy; **81–1000 per cell** under rank-75 and rank-95, baseline *and* H1. It fails for a seat that regulates and passes for one that does not — see [CB-EV-0031](CB-EV-0031-a-seat-that-does-not-regulate.md) |
|
||||
| 2 | ATTACK selection rises for some subpopulation | **mixed.** Greedy attacks **0** times; rank-75's ATTACK *fell* 604 → 180 at 2p; the unregulated seat's rose 0 → 726 |
|
||||
| 3 | group success does not collapse | **fails hardest** — greedy's wins go **165 → 0** (3p), **190 → 0** (4p), **200 → 0** (6p) |
|
||||
| 4 | Bond/GROUND stay better than DARVO | holds, but **vacuously**: DARVO never fires |
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,17 @@ CB-WP-0039. Tier M (adds a policy to the panel every future measurement is
|
|||
reported against; chaos d8 = 2 → no override). **Declaration 11 of chaos
|
||||
window 3.** Closed 2026-08-08.
|
||||
|
||||
> Like [CB-EV-0030](CB-EV-0030-h1-measured.md), **this has not had the
|
||||
> tier-L adversarial review that pass owes, and none of it may reach
|
||||
> `ground-game` until that review has run.**
|
||||
> **CORRECTED 2026-08-08 after adversarial review, and most of the
|
||||
> original file was wrong.** The review is in
|
||||
> [`CB-REV-0001`](../reviews/CB-REV-0001-h1.md). The policy this pass
|
||||
> called *"greedy with one preference changed"* differed in **five**
|
||||
> places — including `SpendFreedom`, which it ranked 95 unconditionally
|
||||
> where greedy ranks it 0 unless the gate bites, so the seat burned its
|
||||
> Freedom token in round one of every game. **Every number below was
|
||||
> measuring that bug.** They have been re-derived against a policy that
|
||||
> delegates to `GreedyPolicy::rank` and overrides exactly one arm.
|
||||
>
|
||||
> Sections 1 and 2 are rewritten. **Section 3 is withdrawn entirely.**
|
||||
|
||||
**Delivered:** the `reactive` policy, and the measurement CB-EV-0030 §4
|
||||
named as the one it had not run.
|
||||
|
|
@ -24,24 +32,27 @@ unreachable **by construction**. So "H1 does nothing" was really "H1 does
|
|||
nothing *to a seat that already manages its Stress*" — and H1 was written
|
||||
for the seat that does not.
|
||||
|
||||
`reactive` is greedy with **one preference changed**: GROUND demoted below
|
||||
ATTACK. SHARED GROUND, 200 games per cell, all 200 ran in every cell:
|
||||
`reactive` now **delegates to `GreedyPolicy::rank` and overrides one arm**
|
||||
(`Ground if gated`), so "one preference differs" is structurally true
|
||||
rather than a claim. SHARED GROUND, 200 games per cell, all 200 ran, and
|
||||
**`peak` is now Stress held, not Stress assigned** (§2):
|
||||
|
||||
| | | greedy | | | | reactive | | |
|
||||
|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
||||
| **seats** | won | atk | darvo | peak | won | atk | darvo | peak |
|
||||
| 2p | 68 | 0 | 0 | 4 | **0** | 1200 | **400** | **5** |
|
||||
| 3p | 0 | 0 | 0 | 4 | **0** | 1800 | **400** | **5** |
|
||||
| 4p | 0 | 0 | 0 | 4 | **0** | 2400 | **400** | **5** |
|
||||
| 6p | 0 | 0 | 0 | 4 | **0** | 3600 | **400** | **5** |
|
||||
| 2p | 68 | 0 | 0 | 4 | **66** | 726 | 363 | **5** |
|
||||
| 3p | 0 | 0 | 0 | 4 | 0 | 1200 | 600 | **5** |
|
||||
| 4p | 0 | 0 | 0 | 4 | 0 | 1600 | 800 | **5** |
|
||||
| 6p | 0 | 0 | 0 | 4 | 0 | 2400 | 1200 | **5** |
|
||||
|
||||
**H1's mechanism works.** Criterion 1 (DARVO arm rate non-trivial) and
|
||||
criterion 2 (ATTACK selection rises for some subpopulation) are **met** —
|
||||
for the subpopulation H1 was aimed at. CB-EV-0030 reported both as
|
||||
failures, and that report was about greedy.
|
||||
**H1's mechanism does reach the unregulated seat.** DARVO arms **2 per
|
||||
seat per game**, scaling with the table — where the earlier figure was a
|
||||
flat 400 in every cell, which was an artifact of the broken policy.
|
||||
|
||||
**Criterion 3 still fails, and now worse.** Reactive wins **nothing**, at
|
||||
any seat count, under H1.
|
||||
**Criterion 3's verdict is corrected.** The original said reactive *"wins
|
||||
nothing, at any seat count"*. **False.** At 2p it wins **66 against
|
||||
greedy's 68** — indistinguishable. It wins nothing at 3p and above, which
|
||||
is also where greedy wins nothing.
|
||||
|
||||
## 2. The baseline finding, which is larger than H1
|
||||
|
||||
|
|
@ -49,40 +60,55 @@ Look at the baseline half of the same run. Greedy and reactive are
|
|||
**identical** — 132/165/190/200, zero attacks, zero DARVO, in all four
|
||||
bands.
|
||||
|
||||
**Peak Stress across 3,200 baseline games was 1.** Starting Stress is 2,
|
||||
so no seat's Stress was ever *set* above 1: it only ever went down.
|
||||
**Peak Stress held across the 1,600 baseline games was 2 — the starting
|
||||
value, never exceeded.** Stress only ever went down.
|
||||
|
||||
> **The baseline's entire Stress economy is unreachable under this panel.**
|
||||
> The gate at 4, the DARVO arm at 5, the Freedom token that buys past the
|
||||
> gate — none of it can engage, and a policy built to be reckless with
|
||||
> Stress plays identically to one built to husband it.
|
||||
**Three corrections to what this section originally said.**
|
||||
|
||||
That is a deeper account of F17 than F17 has. ATTACK is not merely
|
||||
unrewarding; **the pressure it is supposed to answer never exists.** H1 is
|
||||
an attempt to fix exactly this, and it is aimed at the right thing.
|
||||
1. It reported **1**, because `peak` was a maximum over `StressSet` event
|
||||
*payloads*. Starting Stress is written by `setup`, never by an event,
|
||||
so a table sitting at 2 all game reported 1 — and a table with no
|
||||
`StressSet` at all would have reported 0. **Wrong subject**: the metric
|
||||
answered *"highest value ever assigned"* while the prose claimed
|
||||
*"highest Stress reached"*.
|
||||
2. It said **3,200 baseline games**. The run is 3,200 *total*; the
|
||||
baseline half is **1,600**.
|
||||
3. It generalised to *"a policy built to be reckless with Stress plays
|
||||
identically to one built to husband it"*. **False, and refuted by this
|
||||
repo's own sibling harness**: `attack-value.rs`'s rank-95 policy is
|
||||
exactly that policy, and under **baseline** it drives Stress to 5 and
|
||||
arms DARVO 400–1000 times per cell.
|
||||
|
||||
## 3. What H1-B actually does, which is not what it was for
|
||||
**What survives, stated narrowly.** Under both policies measured here —
|
||||
neither of which ever selects ATTACK under baseline — **no Stress is ever
|
||||
added at all**. The baseline's Stress economy is reachable only through
|
||||
ATTACK, so a table that does not attack never meets the gate, the arm, or
|
||||
the Freedom token. That is still a real observation about F17, and it is
|
||||
**about ATTACK being the sole inbound pressure**, not about the economy
|
||||
being unreachable in general.
|
||||
|
||||
`darvo` is **exactly 400 in every cell** while `atk` scales cleanly with
|
||||
seats (1200 / 1800 / 2400 / 3600 = 3 per seat per game). A constant across
|
||||
seat counts is an artifact smell, so it was measured rather than reported.
|
||||
## 3. WITHDRAWN — "H1-B suppresses DARVO in the attacker"
|
||||
|
||||
Six-player final Stress, every seed: **`[5, 5, 4, 4, 4, 4]`**. Exactly two
|
||||
seats arm, whatever the table size.
|
||||
The original §3 argued that H1-B holds the attacker at Stress 4, below the
|
||||
arm, and pushes its targets to 5 — inferred from a constant `darvo = 400`
|
||||
and a final-Stress signature of `[5, 5, 4, 4, 4, 4]`.
|
||||
|
||||
**The self-soothe is doing it.** An attacker at Stress ≥ 4 takes −1 for a
|
||||
landed Attack, which holds it at 4 — *below the arm* — while its targets
|
||||
take +1 or +2 and climb to 5. **H1-B suppresses DARVO in the seat that
|
||||
attacks and concentrates it in the seat attacked.**
|
||||
**Withdrawn on two independent grounds, both found by the review.**
|
||||
|
||||
The hypothesis wanted ATTACK to be rational at high Stress. It is. The
|
||||
side effect is that the aggressor is the one seat protected from arming.
|
||||
1. **The constant was the broken policy.** With the one-arm-override
|
||||
policy, `darvo` scales with seats (363/600/800/1200). There is no
|
||||
constant to explain.
|
||||
2. **Disabling H1-B changes the arm count by exactly zero**, in every
|
||||
cell, under the corrected policy. The effect attributed to H1-B's
|
||||
arithmetic was an interaction with the undeclared preference deltas.
|
||||
|
||||
**Stated as a limitation, not a finding about H1:** `reactive` picks its
|
||||
Attack target by first-legal, not by any judgement, so the *concentration*
|
||||
on two seats is partly an artifact of crude targeting. The direction —
|
||||
attackers held at 4, targets pushed to 5 — follows from H1-B's arithmetic
|
||||
and does not depend on the targeting. **The number 2 does.**
|
||||
**And the inference was invalid even where the numbers held.** Final
|
||||
Stress does not record who armed: `DarvoEnded` resets the stage to `Off`
|
||||
and REVERSE applies −2 to its owner, so a seat can arm and finish below 5.
|
||||
The review demonstrated six seats arming behind the same `[5,5,4,4,4,4]`
|
||||
signature. **The original hedged the wrong variable** — it disclaimed "the
|
||||
number 2" and defended "the direction", and the direction is the part that
|
||||
failed.
|
||||
|
||||
## 4. What this does not say
|
||||
|
||||
|
|
|
|||
|
|
@ -40,31 +40,22 @@ impl Policy for Reactive {
|
|||
.players
|
||||
.get(&seat)
|
||||
.is_some_and(|p| p.stress >= 4 && !p.freedom_gate_lifted);
|
||||
// ONE ARM OVERRIDDEN, THE REST DELEGATED.
|
||||
//
|
||||
// The first version re-typed greedy's ranking and changed one
|
||||
// line of the copy. It differed in five places, and the review
|
||||
// found it: `SpendFreedom` ranked 95 unconditionally instead of
|
||||
// greedy's `95 if gated else 0`, so this seat spent its Freedom
|
||||
// token in round one of every game — a second change to the very
|
||||
// mechanism under study. Delegating makes "exactly one preference
|
||||
// differs" structurally true instead of a claim in a comment.
|
||||
let rank = |c: &GroundCommand| -> i32 {
|
||||
match c {
|
||||
GroundCommand::SelectAction {
|
||||
action, problem, ..
|
||||
} => match action {
|
||||
// THE ONE LINE. Greedy has `Ground if gated => 100`.
|
||||
Action::Ground if gated => 5,
|
||||
Action::Solve
|
||||
if problem
|
||||
.and_then(|p| state.problems.get(&p))
|
||||
.is_some_and(|p| p.claimed_by.is_some()) =>
|
||||
{
|
||||
20
|
||||
}
|
||||
Action::Solve => 90,
|
||||
Action::Investigate => 80,
|
||||
Action::Support => 70,
|
||||
Action::Ground => 5,
|
||||
Action::Attack => 10,
|
||||
},
|
||||
GroundCommand::SpendFreedom => 95,
|
||||
GroundCommand::ChooseGroundMode { .. } => 50,
|
||||
GroundCommand::RespondToSupport { .. } => 90,
|
||||
GroundCommand::ChooseDarvoTarget { .. } => 50,
|
||||
GroundCommand::Reveal | GroundCommand::Resolve | GroundCommand::EndRound => -1,
|
||||
action: Action::Ground,
|
||||
..
|
||||
} if gated => 5,
|
||||
other => GreedyPolicy::rank(state, seat, other),
|
||||
}
|
||||
};
|
||||
let mut best = 0;
|
||||
|
|
@ -77,6 +68,10 @@ impl Policy for Reactive {
|
|||
}
|
||||
}
|
||||
|
||||
/// `Scenarios.csv`: "All players start at Stress 2." Named rather than
|
||||
/// inlined so the peak metric cannot silently disagree with setup.
|
||||
const START_STRESS: u8 = 2;
|
||||
|
||||
struct Cell {
|
||||
games: u32,
|
||||
won: u32,
|
||||
|
|
@ -136,17 +131,28 @@ fn sweep(mode: ScoringMode, variant: Variant, players: u8, reactive: bool) -> Ce
|
|||
c.atk += 1;
|
||||
}
|
||||
}
|
||||
// Peak Stress **held**, not peak Stress *assigned*.
|
||||
//
|
||||
// The first version took a maximum over `StressSet` PAYLOADS.
|
||||
// Starting Stress is 2 and is written by `setup`, never by an
|
||||
// event, so a table that sat at 2 all game reported **1**, and a
|
||||
// table with no `StressSet` at all would report 0. CB-EV-0031 §2
|
||||
// built its headline claim on that number. Wrong subject: the
|
||||
// metric answered "highest value ever assigned", the prose said
|
||||
// "highest Stress reached".
|
||||
let mut held: std::collections::BTreeMap<PlayerId, u8> =
|
||||
g.state.players.keys().map(|s| (*s, START_STRESS)).collect();
|
||||
c.peak_stress = c.peak_stress.max(START_STRESS);
|
||||
for e in &g.events {
|
||||
if matches!(e, games_ground::GroundEvent::DarvoTriggered { .. }) {
|
||||
c.darvo += 1;
|
||||
}
|
||||
// How close does Stress actually get to the arm at 5? That is
|
||||
// the number H1's whole mechanism turns on, and CB-EV-0030
|
||||
// had to infer it from a single game.
|
||||
if let games_ground::GroundEvent::StressSet { stress, .. } = e {
|
||||
if let games_ground::GroundEvent::StressSet { player, stress } = e {
|
||||
held.insert(*player, *stress);
|
||||
c.peak_stress = c.peak_stress.max(*stress);
|
||||
}
|
||||
}
|
||||
let _ = held;
|
||||
}
|
||||
c
|
||||
}
|
||||
|
|
|
|||
|
|
@ -323,7 +323,20 @@ impl Policy for RandomPolicy {
|
|||
pub struct GreedyPolicy;
|
||||
|
||||
impl GreedyPolicy {
|
||||
fn rank(state: &GroundState, seat: PlayerId, cmd: &GroundCommand) -> i32 {
|
||||
/// The ranking, **public so a variant policy can override exactly one
|
||||
/// arm and inherit the rest** (CB-WP-0039, after review).
|
||||
///
|
||||
/// It was private, so `regulation.rs` re-typed an abridged copy and
|
||||
/// called it "greedy with one preference changed". It differed in
|
||||
/// five places — including `SpendFreedom`, which the copy ranked 95
|
||||
/// unconditionally where this ranks it 0 unless the gate is biting,
|
||||
/// so the "reactive" seat burned its Freedom token in round one of
|
||||
/// every game. **A second change to the exact mechanism the pass was
|
||||
/// studying**, and every number in CB-EV-0031 was measuring it.
|
||||
///
|
||||
/// Making this callable removes the possibility rather than testing
|
||||
/// for it: a caller that delegates cannot drift.
|
||||
pub fn rank(state: &GroundState, seat: PlayerId, cmd: &GroundCommand) -> i32 {
|
||||
let gated = state
|
||||
.players
|
||||
.get(&seat)
|
||||
|
|
|
|||
|
|
@ -2092,14 +2092,40 @@ mod tests {
|
|||
s
|
||||
}
|
||||
|
||||
/// **The load-bearing control.** A variant system that perturbs
|
||||
/// the baseline invalidates every measurement this repo has.
|
||||
/// **The load-bearing control**, rewritten after review found the
|
||||
/// first version inert.
|
||||
///
|
||||
/// It compared two states built by the *same* `setup` call, both
|
||||
/// then assigned `Variant::Baseline` — bitwise identical by
|
||||
/// construction, so the assertion could only fail if hashing were
|
||||
/// nondeterministic. It carried no information about variants at
|
||||
/// all. Adding `#[serde(skip)]` to `variant` — which is exactly
|
||||
/// the "a scenario replayed under the wrong variant diverges
|
||||
/// silently" failure the workplan named — left all 57 tests green.
|
||||
///
|
||||
/// This asserts the two properties that were claimed:
|
||||
/// **the variant reaches the hash**, and **selecting the baseline
|
||||
/// is not a change**.
|
||||
#[test]
|
||||
fn baseline_is_bit_for_bit_what_it_was() {
|
||||
fn the_variant_reaches_the_hash_and_baseline_is_not_a_change() {
|
||||
for players in [2u8, 3, 6] {
|
||||
for seed in 0..8u64 {
|
||||
let base = setup(players, Variant::Baseline, seed);
|
||||
let mut default_built = GroundState::setup(
|
||||
let h1 = setup(players, Variant::H1ProblemStress, seed);
|
||||
|
||||
// 1. The variant is IN the hash. `#[serde(skip)]` on
|
||||
// the field makes these equal, which is the whole
|
||||
// silent-divergence failure.
|
||||
assert_ne!(
|
||||
cb_events::state_hash_hex(&base),
|
||||
cb_events::state_hash_hex(&h1),
|
||||
"{players}p seed {seed}: the variant does not reach the state hash, \
|
||||
so a recording cannot say which rules it was played under"
|
||||
);
|
||||
|
||||
// 2. And selecting the baseline explicitly is not a
|
||||
// change from selecting nothing.
|
||||
let untouched = GroundState::setup(
|
||||
&Setup {
|
||||
players,
|
||||
preset: format!("standard-{players}p"),
|
||||
|
|
@ -2108,12 +2134,10 @@ mod tests {
|
|||
seed,
|
||||
)
|
||||
.expect("setup");
|
||||
// Untouched: whatever `setup` produces IS baseline.
|
||||
assert_eq!(default_built.variant, Variant::Baseline);
|
||||
default_built.variant = Variant::Baseline;
|
||||
assert_eq!(untouched.variant, Variant::Baseline, "the default moved");
|
||||
assert_eq!(
|
||||
cb_events::state_hash_hex(&base),
|
||||
cb_events::state_hash_hex(&default_built),
|
||||
cb_events::state_hash_hex(&untouched),
|
||||
"{players}p seed {seed}: selecting the baseline changed it"
|
||||
);
|
||||
}
|
||||
|
|
@ -2226,6 +2250,68 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// **H1-A lands before the DARVO arm check** (review M5).
|
||||
///
|
||||
/// The delta orders it "+1 Stress, then clamp, then DARVO arm
|
||||
/// check as today". CB-WP-0038 claimed this was got right and
|
||||
/// nothing tested it: moving the pressure after the arm check
|
||||
/// left every test green.
|
||||
///
|
||||
/// A seat at 4 with a Problem unclaimed must arm **in the same
|
||||
/// round end** — pressure takes it to 5, and the check sees it.
|
||||
#[test]
|
||||
fn h1a_pressure_arms_darvo_in_the_same_round_end() {
|
||||
let mut s = setup(3, Variant::H1ProblemStress, 7);
|
||||
for p in s.players.values_mut() {
|
||||
p.stress = 4;
|
||||
p.darvo = DarvoStage::Off;
|
||||
}
|
||||
assert!(s.problems.values().any(|p| p.claimed_by.is_none()));
|
||||
let events = s.end_round_events();
|
||||
assert!(
|
||||
events
|
||||
.iter()
|
||||
.any(|e| matches!(e, GroundEvent::DarvoTriggered { .. })),
|
||||
"pressure took the seat to 5 but the arm check did not see it, \
|
||||
so H1-A is ordered after it"
|
||||
);
|
||||
// And the order is visible in the event stream: the Stress
|
||||
// must be set before the trigger, not after.
|
||||
let first_stress = events
|
||||
.iter()
|
||||
.position(|e| matches!(e, GroundEvent::StressSet { .. }));
|
||||
let first_trigger = events
|
||||
.iter()
|
||||
.position(|e| matches!(e, GroundEvent::DarvoTriggered { .. }));
|
||||
assert!(
|
||||
first_stress < first_trigger,
|
||||
"the arm check precedes the pressure in the event stream"
|
||||
);
|
||||
}
|
||||
|
||||
/// **H1-B must not fire on a GROUND—OU cancellation** (review M11).
|
||||
///
|
||||
/// `resolve_attack` has two cancel paths and the existing test
|
||||
/// exercised only Protection — it passed `&Default::default()`
|
||||
/// for `ou_cancels`, so the other path was never reached.
|
||||
#[test]
|
||||
fn h1b_does_not_soothe_an_ou_cancelled_attack() {
|
||||
let mut s = setup(3, Variant::H1ProblemStress, 3);
|
||||
let seats: Vec<PlayerId> = s.players.keys().copied().collect();
|
||||
let (a, t) = (seats[0], seats[1]);
|
||||
s.players.get_mut(&a).expect("a").stress = 4;
|
||||
let mut ou = std::collections::BTreeSet::new();
|
||||
ou.insert((a, t));
|
||||
let mut events = Vec::new();
|
||||
s.resolve_attack(a, t, &ou, &mut events);
|
||||
assert!(
|
||||
!events
|
||||
.iter()
|
||||
.any(|e| matches!(e, GroundEvent::StressSet { player, .. } if *player == a)),
|
||||
"an Attack cancelled by GROUND—OU still soothed the attacker"
|
||||
);
|
||||
}
|
||||
|
||||
/// **`rules_delta.yaml`'s `unchanged:` list is ground-game's claim
|
||||
/// about their own experiment, and it is checkable.**
|
||||
///
|
||||
|
|
@ -2260,6 +2346,61 @@ mod tests {
|
|||
base.solution_deck, h1.solution_deck,
|
||||
"{players}p: deck moved"
|
||||
);
|
||||
|
||||
// solve_legality / support / ground_modes: the SAME menu
|
||||
// in the SAME position. Comparing setup fields could not
|
||||
// see a delta that forbids an action -- the review made
|
||||
// SOLVE illegal under H1 and this test stayed green.
|
||||
let seats: Vec<PlayerId> = h1.players.keys().copied().collect();
|
||||
for seat in &seats {
|
||||
let mut lb: Vec<String> = crate::bot::legal_commands(&base, *seat)
|
||||
.iter()
|
||||
.map(|c| format!("{c:?}"))
|
||||
.collect();
|
||||
let mut lh: Vec<String> = crate::bot::legal_commands(&h1, *seat)
|
||||
.iter()
|
||||
.map(|c| format!("{c:?}"))
|
||||
.collect();
|
||||
lb.sort();
|
||||
lh.sort();
|
||||
assert_eq!(
|
||||
lb, lh,
|
||||
"{players}p {seat}: H1 changed which commands are legal"
|
||||
);
|
||||
}
|
||||
|
||||
// relation_slots: 2, asserted at the BOUNDARY. The old
|
||||
// check asked `has_free_slot` of a seat with no relations,
|
||||
// which holds for any capacity >= 1.
|
||||
if seats.len() >= 3 {
|
||||
let mut s = h1.clone();
|
||||
s.relations
|
||||
.insert(Pair::new(seats[0], seats[1]), Relation::Bond);
|
||||
assert!(s.has_free_slot(seats[0]), "one relation leaves a slot");
|
||||
s.relations
|
||||
.insert(Pair::new(seats[0], seats[2]), Relation::Bond);
|
||||
assert!(
|
||||
!s.has_free_slot(seats[0]),
|
||||
"{players}p: two relations must fill both slots -- capacity moved"
|
||||
);
|
||||
}
|
||||
|
||||
// darvo_stage_table: the arm sits at Stress 5. Every
|
||||
// Problem claimed, so H1-A cannot add pressure and the
|
||||
// only question is where the threshold is.
|
||||
let mut s = h1.clone();
|
||||
for p in s.players.values_mut() {
|
||||
p.stress = 4;
|
||||
}
|
||||
for p in s.problems.values_mut() {
|
||||
p.claimed_by = Some(seats[0]);
|
||||
}
|
||||
assert!(
|
||||
!s.end_round_events()
|
||||
.iter()
|
||||
.any(|e| matches!(e, GroundEvent::DarvoTriggered { .. })),
|
||||
"{players}p: DARVO armed below Stress 5 -- the stage table moved"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
129
reviews/CB-REV-0001-h1.md
Normal file
129
reviews/CB-REV-0001-h1.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# CB-REV-0001 — adversarial review of the H1 measurement
|
||||
|
||||
The tier-L review CB-WP-0038 owed (InnerLoop Step 2). One round: challenge,
|
||||
then response. Run 2026-08-08 by a separate agent against
|
||||
CB-WP-0038/CB-EV-0030 and CB-WP-0039/CB-EV-0031.
|
||||
|
||||
> **Verdict: not approvable as submitted.** Thirteen challenges, five
|
||||
> rated FATAL. **Every FATAL is conceded.** Nothing from either evidence
|
||||
> file had reached `ground-game`, which is the only reason this is a
|
||||
> correction rather than a retraction.
|
||||
|
||||
The reviewer reproduced every number, re-derived on **three** samples
|
||||
(seeds 0..200, 1000..1200, 5000..5500) and ran **14 kernel mutations**.
|
||||
|
||||
---
|
||||
|
||||
## The five that were fatal
|
||||
|
||||
### 1. `Reactive` was not "greedy with one preference changed" — it differed in five
|
||||
|
||||
**Conceded, and it is the worst thing in the pass.** The policy re-typed
|
||||
an abridged copy of greedy's ranking and changed one line *of the copy*,
|
||||
under a comment reading `// THE ONE LINE`. It also differed in
|
||||
`SpendFreedom` (95 unconditional against greedy's `95 if gated else 0`),
|
||||
`Solve` on a claimed Problem, `ChooseGroundMode` and `RespondToSupport`.
|
||||
|
||||
**The `SpendFreedom` difference is a second change to the exact mechanism
|
||||
under study**: the seat burned its Freedom token in round one of every
|
||||
game, ungated. Measured by the reviewer: 1200 spends against greedy's 0.
|
||||
|
||||
**This pass claimed ADR-0018's one-varying-parameter discipline in its own
|
||||
workplan while violating it.** That is worse than not claiming it.
|
||||
|
||||
**Fixed structurally rather than by testing:** `GreedyPolicy::rank` is now
|
||||
`pub`, and `Reactive` delegates to it and overrides a single match arm. A
|
||||
caller that delegates cannot drift. The reviewer's `StrictReactive`
|
||||
numbers reproduce exactly.
|
||||
|
||||
### 2. The H1-B suppression mechanism does not survive the corrected policy
|
||||
|
||||
**Conceded and withdrawn.** The reviewer disabled H1-B and re-ran:
|
||||
`darvo` is **identical in every cell**. The effect CB-EV-0031 §3 attributed
|
||||
to "H1-B's arithmetic" was an interaction with challenge 1's bugs.
|
||||
|
||||
**The hedge was on the wrong variable.** §3 disclaimed *"the number 2"*
|
||||
and defended *"the direction"*. The direction is what failed.
|
||||
|
||||
### 3. `[5,5,4,4,4,4]` does not show that two seats armed
|
||||
|
||||
**Conceded.** `DarvoEnded` resets the stage and REVERSE gives its owner
|
||||
−2, so a seat can arm and end below 5. The reviewer exhibited **six**
|
||||
seats arming behind the same signature. A terminal snapshot was used to
|
||||
prove a path property.
|
||||
|
||||
### 4. Criterion 1 was failed on the greedy column alone
|
||||
|
||||
**Conceded.** CB-EV-0030 rendered *"DARVO arm rate still 0"* as a flat
|
||||
failure while **its own printed table** showed 31–1000 arms per cell in
|
||||
the rank-75 and rank-95 columns. **The selective-column move, in the file
|
||||
that names selective reporting as the thing to avoid.** Restated per
|
||||
column.
|
||||
|
||||
### 5. "Peak Stress was 1" was a wrong-subject error, with two more defects
|
||||
|
||||
**Conceded, all three.** `peak` maximised over `StressSet` event
|
||||
*payloads*; starting Stress is written by `setup` and never by an event,
|
||||
so a table sitting at 2 reported 1. True peak held: **2**. The baseline
|
||||
game count was **1,600**, not 3,200. And the generalisation — *"a reckless
|
||||
policy plays identically to a careful one"* — is refuted by this repo's
|
||||
own rank-95 policy, which under **baseline** drives Stress to 5.
|
||||
|
||||
**What survives is narrower and is now stated that way**: under the two
|
||||
policies measured, neither of which selects ATTACK under baseline, no
|
||||
Stress is ever added. ATTACK is the sole inbound pressure.
|
||||
|
||||
---
|
||||
|
||||
## The serious ones
|
||||
|
||||
| # | challenge | outcome |
|
||||
|---|---|---|
|
||||
| 6 | `baseline_is_bit_for_bit_what_it_was` compared two identically-constructed states — inert against its own threat; `#[serde(skip)]` on `variant` left 57/57 green | **conceded.** Replaced by `the_variant_reaches_the_hash_and_baseline_is_not_a_change`, which asserts baseline and H1 hash **differently**. The reviewer's M6 now goes red |
|
||||
| 7 | the `unchanged:` test checked **3 of 7** entries; making SOLVE illegal under H1 left it green | **conceded.** Now compares `legal_commands` per seat (catches M9), checks relation slots at the **boundary** rather than on an empty seat, and asserts the DARVO arm is at 5 (catches M7). Both verified red |
|
||||
| 8 | "criteria met" rests on a forced move — `reactive` ranks ATTACK at 10 and picks it only when the gate leaves nothing else | **conceded as a limitation, recorded, not fixed.** It is true that the instrument cannot show a null result once H1-A reaches Stress 4. That is a real weakness of the measurement and is now stated in CB-EV-0031 §4 |
|
||||
| 9 | three claimed properties had no failing test: H1-A's ordering, H1-B on the DARVO extra Attack, H1-B on an OU-cancelled Attack | **two fixed** — `h1a_pressure_arms_darvo_in_the_same_round_end` and `h1b_does_not_soothe_an_ou_cancelled_attack`, both mutation-verified. **The DARVO extra-Attack path remains untested** and is carried as open |
|
||||
|
||||
## The rest
|
||||
|
||||
- **10 — H1-B's `after_target_and_relation_effects` ordering is vacuous
|
||||
here.** Conceded: nothing between the two positions touches the
|
||||
attacker's Stress, so the clause cannot be checked in this kernel. The
|
||||
honest statement replaces the claim that it was got right.
|
||||
- **11 — `regulation.rs` still skips setup failures silently.** Conceded;
|
||||
the credit CB-EV-0030 §3 took was half-earned. The `play` path was
|
||||
instrumented, the `setup` path was not.
|
||||
- **13 — round-5 DARVO arms are counted but can never act**, and
|
||||
`end_round_events` scores from `self` rather than `work`, so round-5 H1-A
|
||||
Stress is invisible to the CommonProblem tiebreak. **Accepted as a real
|
||||
defect in the other two modes**, which CB-EV-0030 reports on. Carried
|
||||
open.
|
||||
- **12 — reproduction and sample robustness: no problem found.** Every
|
||||
number reproduced; no conclusion was seed-specific. **The failures were
|
||||
of construction and interpretation, not sampling.**
|
||||
|
||||
## What the reviewer could not check, and it is recorded rather than glossed
|
||||
|
||||
The design note itself (criteria quoted from CB-EV-0030, not read from
|
||||
source); the catalog digest, which the reviewer could not find and
|
||||
reported as **unverified rather than absent**; `--variant` on the CLI;
|
||||
`make cost`; and the two non-SHARED modes under `reactive`.
|
||||
|
||||
**The catalog digest is a real gap.** CB-WP-0038 T01's control said *"the
|
||||
catalog is vendored with a digest, like every other borrowed file"*, and
|
||||
`PROVENANCE.md` records digests for the ten CSVs and **not** for
|
||||
`catalog.yaml` or the H1 package. The control was claimed and not met.
|
||||
|
||||
---
|
||||
|
||||
## What this cost, and what it bought
|
||||
|
||||
The review found **five fatal defects in numbers that were one step from
|
||||
another repository's design decision**, and four of the five were errors
|
||||
of the exact class this project has been cataloguing since ADR-0018 —
|
||||
correct computation, wrong subject.
|
||||
|
||||
**The instrument caught the instrument's author.** CB-WP-0039 was itself
|
||||
written to correct CB-EV-0030, and it introduced worse errors than the
|
||||
ones it fixed. **The lesson is not "review works" — it is that a pass
|
||||
written to correct a previous pass inherits none of its caution.**
|
||||
|
|
@ -50,13 +50,29 @@ def vendored_files():
|
|||
return sorted(f for f in os.listdir(d) if f.endswith(".csv"))
|
||||
|
||||
|
||||
# CB-WP-0038 vendored two things that are NOT inside the edition
|
||||
# directory: `editions/catalog.yaml`, which selects between packages and
|
||||
# so belongs above them, and the `h1-problem-stress` experiment package.
|
||||
#
|
||||
# Their digests were CLAIMED by that pass and never recorded. The
|
||||
# adversarial review (CB-REV-0001) could not find them and reported the
|
||||
# control unverified — correctly. Recorded relative to `editions/`.
|
||||
SIBLINGS = "../"
|
||||
|
||||
|
||||
def sibling_files():
|
||||
"""Recorded paths that live beside the edition rather than in it."""
|
||||
return sorted(f for f in recorded() if f.startswith(SIBLINGS))
|
||||
|
||||
|
||||
def check():
|
||||
want = recorded()
|
||||
print("edition-check — vendored data against its provenance")
|
||||
rc = 0
|
||||
|
||||
present = vendored_files()
|
||||
undocumented = [f for f in present if f not in want]
|
||||
siblings = sibling_files()
|
||||
present = vendored_files() + siblings
|
||||
undocumented = [f for f in vendored_files() if f not in want]
|
||||
if undocumented:
|
||||
print(f" [FAIL] vendored with no recorded digest: {', '.join(undocumented)}")
|
||||
rc = 1
|
||||
|
|
@ -69,6 +85,7 @@ def check():
|
|||
if name not in want:
|
||||
continue
|
||||
have = digest(os.path.join(ROOT, EDITION, name))
|
||||
# `../x` resolves out of the edition dir, which is the point.
|
||||
if have != want[name]:
|
||||
print(f" [FAIL] {name} does not match its recorded digest")
|
||||
print(f" recorded {want[name]}\n actual {have}")
|
||||
|
|
@ -125,11 +142,18 @@ def self_test():
|
|||
chk("every vendored file has a recorded digest",
|
||||
all(f in want for f in present),
|
||||
"a file added without a digest must fail, not be skipped")
|
||||
# `present` is the edition's own CSVs plus the sibling paths recorded
|
||||
# for the catalog and the experiment package (CB-WP-0038).
|
||||
everything = present + sibling_files()
|
||||
chk("every digest names a file that is here",
|
||||
all(f in present for f in want),
|
||||
all(f in everything for f in want),
|
||||
"a stale digest is a lie with a filename")
|
||||
chk("digests match the real files",
|
||||
all(digest(os.path.join(ROOT, EDITION, f)) == want[f] for f in present))
|
||||
all(digest(os.path.join(ROOT, EDITION, f)) == want[f] for f in everything))
|
||||
chk("the sibling packages are covered",
|
||||
len(sibling_files()) >= 3,
|
||||
"catalog.yaml and rules_delta.yaml decide WHAT WE MEASURED; "
|
||||
"CB-WP-0038 claimed digests for them and recorded none")
|
||||
|
||||
# The control that matters: a changed byte must be detected.
|
||||
import tempfile
|
||||
|
|
|
|||
|
|
@ -64,27 +64,47 @@ state_hub_task_id: "2c509cdb-aedb-4dce-a21f-f16bcdf17d80"
|
|||
- **the answer may be that H1 still fails**, and that is reported as
|
||||
plainly as the other outcome.
|
||||
|
||||
**Done 2026-08-08.**
|
||||
**Done 2026-08-08, then CORRECTED after adversarial review**
|
||||
([CB-REV-0001](../reviews/CB-REV-0001-h1.md)).
|
||||
[CB-EV-0031](../evidence/CB-EV-0031-a-seat-that-does-not-regulate.md).
|
||||
|
||||
**It changed CB-EV-0030's verdict.** H1's criteria 1 and 2 are **met** for
|
||||
the seat H1 was aimed at: DARVO arms 400 times per cell and ATTACK is
|
||||
selected 3 times per seat per game. The earlier "H1 does nothing" was a
|
||||
statement about greedy, which grounds the moment the gate bites.
|
||||
> **This task's own control — "exactly one preference differs from
|
||||
> greedy" — was false when it was marked done.** The policy differed in
|
||||
> five places, including `SpendFreedom`, which made the seat burn its
|
||||
> Freedom token in round one of every game: a second change to the exact
|
||||
> mechanism under study. **Every number below was measuring that.**
|
||||
>
|
||||
> `GreedyPolicy::rank` is now public and the policy delegates to it,
|
||||
> overriding one match arm — so the control is structurally true rather
|
||||
> than asserted in a comment.
|
||||
|
||||
**Criterion 3 fails harder**: reactive wins nothing, at any seat count.
|
||||
**It changed CB-EV-0030's verdict**, and that part survives: H1 does reach
|
||||
the unregulated seat. DARVO arms **2 per seat per game**, scaling with the
|
||||
table — the earlier flat "400 in every cell" was the broken policy.
|
||||
|
||||
**The larger finding is about the baseline, not H1.** Greedy and reactive
|
||||
play *identically* under baseline — same wins, zero attacks, zero DARVO —
|
||||
and **peak Stress across 3,200 baseline games was 1**, against a starting
|
||||
value of 2. The gate at 4, the arm at 5 and the Freedom token are all
|
||||
unreachable. That is a deeper account of F17 than F17 has, and it is
|
||||
**not raised as a finding here** because it wants the plural panel first.
|
||||
**Criterion 3's verdict is corrected and was wrong**: reactive wins
|
||||
**66 at 2p against greedy's 68**, not "nothing at any seat count". It wins
|
||||
nothing at 3p and above, and so does greedy.
|
||||
|
||||
**A constant was investigated rather than reported.** `darvo` was exactly
|
||||
400 in every cell while `atk` scaled properly with seats. Six-player final
|
||||
Stress is `[5, 5, 4, 4, 4, 4]` every seed: **H1-B holds the attacker at 4,
|
||||
below the arm, and pushes its targets to 5** — the self-soothe suppresses
|
||||
DARVO in the aggressor and concentrates it in the attacked. The direction
|
||||
follows from H1-B's arithmetic; **the number 2 is partly an artifact of
|
||||
`reactive`'s first-legal targeting**, and is labelled as such.
|
||||
**The baseline observation survives only in a narrower form.** Peak Stress
|
||||
*held* across the **1,600** baseline games was **2** — the starting value,
|
||||
never exceeded. The original said **1** because the metric maximised over
|
||||
`StressSet` event payloads and the starting value is written by `setup`,
|
||||
never by an event: **wrong subject**, in the pass that was written to
|
||||
correct a wrong-subject error.
|
||||
|
||||
And the generalisation was refuted by this repo's own harness: the rank-95
|
||||
policy *is* a reckless policy and it drives Stress to 5 under **baseline**.
|
||||
What survives: under the two policies here, neither of which selects ATTACK
|
||||
under baseline, **no Stress is ever added** — ATTACK is the sole inbound
|
||||
pressure.
|
||||
|
||||
**The H1-B suppression claim is WITHDRAWN.** It was inferred from a
|
||||
constant that the broken policy produced, and disabling H1-B under the
|
||||
corrected policy changes the arm count by **exactly zero** in every cell.
|
||||
The inference was also invalid on its own terms: final Stress does not
|
||||
record who armed, because `DarvoEnded` resets the stage and REVERSE gives
|
||||
its owner −2.
|
||||
|
||||
**The pass hedged the wrong variable** — it disclaimed "the number 2" and
|
||||
defended "the direction", and the direction is what failed.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue