diff --git a/evidence/CB-EV-0031-a-seat-that-does-not-regulate.md b/evidence/CB-EV-0031-a-seat-that-does-not-regulate.md new file mode 100644 index 0000000..e7fa5be --- /dev/null +++ b/evidence/CB-EV-0031-a-seat-that-does-not-regulate.md @@ -0,0 +1,107 @@ +# CB-EV-0031 — a seat that does not regulate + +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.** + +**Delivered:** the `reactive` policy, and the measurement CB-EV-0030 §4 +named as the one it had not run. + +--- + +## 1. It changes CB-EV-0030's verdict + +CB-EV-0030 concluded H1's DARVO arm rate was still 0. **That was true of +the panel, and the panel was greedy-family throughout.** + +`GreedyPolicy` ranks `Action::Ground if gated => 100`: the instant the +stress gate bites it grounds. Stress plateaus at 3, and the arm at 5 is +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: + +| | | 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** | + +**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. + +**Criterion 3 still fails, and now worse.** Reactive wins **nothing**, at +any seat count, under H1. + +## 2. The baseline finding, which is larger than H1 + +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. + +> **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. + +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. + +## 3. What H1-B actually does, which is not what it was for + +`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. + +Six-player final Stress, every seed: **`[5, 5, 4, 4, 4, 4]`**. Exactly two +seats arm, whatever the table size. + +**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.** + +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. + +**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.** + +## 4. What this does not say + +- **`reactive` is not a good player and is not meant to be.** It wins + nothing under H1 and exactly what greedy wins under baseline. It is an + instrument for reaching the Stress economy, not a claim about how people + play. +- **Two policies is still not a panel.** RetrospectiveAnalysis §4.1 asks + for greedy, random and first-legal at minimum; this pass added a fourth + axis to two of them, in one mode. +- **SHARED GROUND only.** CB-EV-0030 covered three modes; this covers one. +- **No felt-play**, and this is the kind of claim — about pressure, and + when a player feels cornered — that a human table answers and 3,200 + simulated games do not. + +## 5. Open + +- **The baseline's Stress economy being unreachable (§2) deserves its own + finding**, and is not raised here because it wants the plural panel of + §4 first. It is the more consequential of the two results in this file. +- **Whether H1-B's suppression is intended** is ground-game's question, + and it is the sharpest thing this pass can hand them. diff --git a/games/ground/examples/regulation.rs b/games/ground/examples/regulation.rs new file mode 100644 index 0000000..10def52 --- /dev/null +++ b/games/ground/examples/regulation.rs @@ -0,0 +1,186 @@ +//! Does H1 do nothing, or does it do nothing **to a seat that already +//! manages its Stress**? (CB-WP-0039) +//! +//! [`CB-EV-0030`] measured ground-game's H1 with a panel that was +//! greedy-family throughout, and found DARVO never arms. But +//! `GreedyPolicy` ranks `Action::Ground if gated => 100`: the moment the +//! stress gate bites it grounds, Stress plateaus at 3, and the arm at 5 +//! is unreachable **by construction**. +//! +//! H1 was written for the seat that does *not* do that. So this varies +//! **exactly one preference** — GROUND demoted below ATTACK — and holds +//! every other ranking identical to greedy's. +//! +//! [`CB-EV-0030`]: ../../../evidence/CB-EV-0030-h1-measured.md + +use cb_game_runtime::{ScenarioGame, Setup}; +use cb_kernel::PlayerId; +use games_ground::bot::{play, Choice, GreedyPolicy, Policy}; +use games_ground::{Action, GroundCommand, GroundState, ScoringMode, Variant}; + +/// Greedy, with GROUND demoted below ATTACK. Nothing else differs. +/// +/// **The copy is deliberate.** `GreedyPolicy::rank` is private, and the +/// point is to change one line of it while holding the rest identical — +/// calling into it would make that impossible to demonstrate. +struct Reactive; + +impl Policy for Reactive { + fn name(&self) -> &'static str { + "reactive" + } + fn choose( + &mut self, + state: &GroundState, + seat: PlayerId, + legal: &[GroundCommand], + _may_pass: bool, + ) -> Choice { + let gated = state + .players + .get(&seat) + .is_some_and(|p| p.stress >= 4 && !p.freedom_gate_lifted); + 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, + } + }; + let mut best = 0; + for (i, c) in legal.iter().enumerate() { + if rank(c) > rank(&legal[best]) { + best = i; + } + } + Choice::Command(best) + } +} + +struct Cell { + games: u32, + won: u32, + atk: u32, + darvo: u32, + peak_stress: u8, +} + +fn sweep(mode: ScoringMode, variant: Variant, players: u8, reactive: bool) -> Cell { + let mut c = Cell { + games: 0, + won: 0, + atk: 0, + darvo: 0, + peak_stress: 0, + }; + for seed in 0..200u64 { + let Ok(mut st) = GroundState::setup( + &Setup { + players, + preset: format!("standard-{players}p"), + patch: Default::default(), + }, + seed, + ) else { + continue; + }; + st.mode = mode; + st.variant = variant; + let mut ps: Vec> = (0..players) + .map(|_| { + if reactive { + Box::new(Reactive) as Box + } else { + Box::new(GreedyPolicy) as Box + } + }) + .collect(); + // A refused game is not a lost game (CB-EV-0030 §3). + let g = match play(st, &mut ps) { + Ok(g) => g, + Err(e) => { + eprintln!(" !! {players}p seed {seed}: {e:?}"); + continue; + } + }; + c.games += 1; + if g.state.outcome.as_ref().is_some_and(|o| o.group_success) { + c.won += 1; + } + for (_, cmd) in &g.steps { + if let GroundCommand::SelectAction { + action: Action::Attack, + .. + } = cmd + { + c.atk += 1; + } + } + 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 { + c.peak_stress = c.peak_stress.max(*stress); + } + } + } + c +} + +fn main() { + println!("CB-WP-0039 — does H1 reach a seat that does not regulate?\n"); + println!("`reactive` is greedy with ONE preference changed: GROUND is"); + println!("demoted below ATTACK, so the seat never grounds to shed Stress."); + println!("SHARED GROUND; `won` is group success; 200 games per cell.\n"); + + for (vlabel, variant) in [ + ("BASELINE ground-darvo-r0", Variant::Baseline), + ("H1 h1-problem-stress", Variant::H1ProblemStress), + ] { + println!("{vlabel}"); + println!(" greedy (regulates) reactive (does not)"); + println!("seats games won atk darvo peak games won atk darvo peak"); + for players in [2u8, 3, 4, 6] { + let g = sweep(ScoringMode::SharedGround, variant, players, false); + let r = sweep(ScoringMode::SharedGround, variant, players, true); + println!( + " {players}p {:>4} {:>4} {:>4} {:>5} {:>4} {:>4} {:>4} {:>4} {:>5} {:>4}", + g.games, + g.won, + g.atk, + g.darvo, + g.peak_stress, + r.games, + r.won, + r.atk, + r.darvo, + r.peak_stress + ); + } + println!(); + } +} diff --git a/specs/ChaosRollHistory.md b/specs/ChaosRollHistory.md index 3a8aa4b..23517ea 100644 --- a/specs/ChaosRollHistory.md +++ b/specs/ChaosRollHistory.md @@ -64,6 +64,7 @@ it recurs; not evidence of anything on its own. | 8 | CB-WP-0036 (re-declared L→M) | d8 = 7 | — | | 9 | CB-WP-0037 | d8 = 1 | — | | 10 | CB-WP-0038 | **d8 = 8** | **yes — redraw L, structural was L, so it changed nothing** | +| 11 | CB-WP-0039 | d8 = 2 | — | **The window's first 8, at declaration 10.** Expectation over ten rolls at d8 is 1.25; one is exactly on rate. @@ -71,7 +72,7 @@ d8 is 1.25; one is exactly on rate. **The override changed nothing**, which is the observation ADR-0017 D2's retirement condition is built from — it needs *a full window whose overrides all change nothing*, in two consecutive windows. This window now -has one qualifying override and two declarations left to run. +has one qualifying override and **one declaration left to run**. **Declaration 8 is a re-declaration of the same workplan**, counted separately because it was a materially different pass: CB-WP-0036 was diff --git a/trials/2026-08-08-0104-2.yaml b/trials/2026-08-08-0104-2.yaml new file mode 100644 index 0000000..bb3f149 --- /dev/null +++ b/trials/2026-08-08-0104-2.yaml @@ -0,0 +1,149 @@ +scenario: ground/cb-play-session +description: recorded by cb-play (CB-WP-0008 T02) +covers: [] +provisional: false +provisional_owner: '' +provisional_raised: '' +ruled: '' +ruled_by: '' +ruled_note: '' +encodes_u_item: '' +seed: 2 +setup: + players: 3 + preset: standard-3p + patch: {} +commands: +- actor: P1 + cmd: select_action + args: + action: SUPPORT + target: P3 +- actor: P2 + cmd: select_action + args: + action: SOLVE + problem: 1 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 1 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: P3 + cmd: respond_to_support + args: + response: accept_bond +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SUPPORT + target: P2 +- actor: P2 + cmd: select_action + args: + action: INVESTIGATE + problem: 2 +- actor: P3 + cmd: select_action + args: + action: INVESTIGATE + problem: 2 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: P2 + cmd: respond_to_support + args: + response: accept_bond +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: INVESTIGATE + problem: 3 +- actor: P2 + cmd: select_action + args: + action: SOLVE + problem: 2 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 2 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: INVESTIGATE + problem: 4 +- actor: P2 + cmd: select_action + args: + action: INVESTIGATE + problem: 4 +- actor: P3 + cmd: select_action + args: + action: INVESTIGATE + problem: 4 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SOLVE + problem: 4 +- actor: P2 + cmd: select_action + args: + action: SOLVE + problem: 4 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 4 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +expect: + events: [] + state: {} + rejects: [] + state_hash: 18faddebb7568ff504b1f758a5906c54ed271c4705a192f458a20d8bb1a30650 diff --git a/trials/2026-08-08-0104.yaml b/trials/2026-08-08-0104.yaml new file mode 100644 index 0000000..cb8c617 --- /dev/null +++ b/trials/2026-08-08-0104.yaml @@ -0,0 +1,149 @@ +scenario: ground/cb-play-session +description: recorded by cb-play (CB-WP-0008 T02) +covers: [] +provisional: false +provisional_owner: '' +provisional_raised: '' +ruled: '' +ruled_by: '' +ruled_note: '' +encodes_u_item: '' +seed: 1 +setup: + players: 3 + preset: standard-3p + patch: {} +commands: +- actor: P1 + cmd: select_action + args: + action: INVESTIGATE + problem: 2 +- actor: P2 + cmd: select_action + args: + action: SOLVE + problem: 1 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 1 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: INVESTIGATE + problem: 3 +- actor: P2 + cmd: select_action + args: + action: INVESTIGATE + problem: 3 +- actor: P3 + cmd: select_action + args: + action: INVESTIGATE + problem: 3 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SOLVE + problem: 2 +- actor: P2 + cmd: select_action + args: + action: INVESTIGATE + problem: 4 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 2 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SOLVE + problem: 3 +- actor: P2 + cmd: select_action + args: + action: SOLVE + problem: 3 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 4 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SUPPORT + target: P3 +- actor: P2 + cmd: select_action + args: + action: SUPPORT + target: P1 +- actor: P3 + cmd: select_action + args: + action: SUPPORT + target: P1 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: P1 + cmd: respond_to_support + args: + response: accept_bond +- actor: P3 + cmd: respond_to_support + args: + response: accept_bond +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +expect: + events: [] + state: {} + rejects: [] + state_hash: 0e9629eec89de6c479a617d2b8929819c502fc6d1020b30a871fe23ad8ea1a1b diff --git a/workplans/CB-WP-0039-a-seat-that-does-not-regulate.md b/workplans/CB-WP-0039-a-seat-that-does-not-regulate.md new file mode 100644 index 0000000..27c5992 --- /dev/null +++ b/workplans/CB-WP-0039-a-seat-that-does-not-regulate.md @@ -0,0 +1,88 @@ +--- +id: CB-WP-0039 +kind: product +title: "A seat that does not regulate" +status: done +--- + +# Purpose + +``` +structural tier M (adds a policy to the panel every future difficulty + and design measurement is reported against) +chaos d8 = 2 → no override +declared tier M +``` + +**Declaration 11 of chaos window 3.** + +## Why + +[CB-EV-0030](../evidence/CB-EV-0030-h1-measured.md) §4 named this as the +measurement it had not run, and it is the one that decides whether H1's +verdict is about **H1** or about **greedy**. + +H1 assumes rising Stress pushes a table toward the DARVO arm. It does not, +**because greedy regulates**: `Action::Ground if gated => 100` makes +GROUND the top choice the moment the stress gate bites, so Stress +plateaus at 3 and neither the gate (4) nor the arm (5) is ever reached. + +> **Every policy in that panel was greedy-family.** So "H1 does nothing" +> may be "H1 does nothing *to a seat that already manages its Stress*" — +> and H1 was written for the seat that does not. + +## The one preference that varies + +`Reactive` is `GreedyPolicy` with **GROUND demoted below ATTACK**, and +nothing else changed. It is the player who chases Problems and ignores +their own state — which is not a straw man: it is the commonest way a +person plays a co-operative game badly, and the behaviour GROUND's whole +Stress economy exists to punish. + +**One preference, per ADR-0018 and the same discipline as +`attack-value.rs`'s `Attacker`.** A policy that differed in several places +would tell us a variant is different without saying which difference did +the work. + +## Task: the policy, and the measurement + +```task +id: CB-WP-0039-T01 +status: done +priority: high +``` + +**Controls:** +- **exactly one preference differs from greedy**, and the artifact says + which; +- **both variants, same instrument, same run** — otherwise the comparison + is between harnesses; +- **the game count is reported**, because CB-EV-0030 §3's harness defect + was silently discarding games and printing zeros that read as losses; +- **the answer may be that H1 still fails**, and that is reported as + plainly as the other outcome. + +**Done 2026-08-08.** +[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. + +**Criterion 3 fails harder**: reactive wins nothing, at any seat count. + +**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. + +**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.