clay-borg/games/ground/examples/regulation.rs
tegwick 0f64961d06
Some checks failed
ci / check (push) Failing after 3s
CB-WP-0039: a seat that does not regulate — and it changes H1'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
`Ground if gated => 100`, so it grounds the instant the stress gate bites,
Stress plateaus at 3, and the arm at 5 is unreachable by construction. "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 exactly one preference changed: GROUND demoted
below ATTACK. Under it, H1's criteria 1 and 2 are MET — DARVO arms 400
times per cell, ATTACK is chosen 3 times per seat per game. Criterion 3
fails harder: reactive wins nothing at any seat count.

The larger finding is about the baseline. Greedy and reactive play
IDENTICALLY under baseline, and peak Stress across 3,200 baseline games
was 1 — against a starting value of 2. The gate at 4, the DARVO arm at 5
and the Freedom token are all unreachable, and a policy built to be
reckless with Stress is indistinguishable from one built to husband it.
That is a deeper account of F17 than F17 has. Not raised as a finding yet:
it wants the plural panel first.

A constant was investigated rather than reported: darvo was exactly 400 in
every cell while atk scaled 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.

Still unreviewed: tier L review outstanding on CB-WP-0038, and nothing
here reaches ground-game until it runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 01:08:40 +02:00

186 lines
6.3 KiB
Rust

//! 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<Box<dyn Policy>> = (0..players)
.map(|_| {
if reactive {
Box::new(Reactive) as Box<dyn Policy>
} else {
Box::new(GreedyPolicy) as Box<dyn Policy>
}
})
.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!();
}
}