CB-WP-0050 T02: F31 falsified — the module is unreachable, then real
Some checks failed
ci / check (push) Failing after 4s

GateAttackPolicy ranks ATTACK above GROUND at the stress gate and
delegates everything else. A PROBE, not a better bot: it wins 12/100
where greedy wins 60/100 under the same module, and the panel's banner
says so, because a column that looks like a policy comparison will be
read as one.

(a) THE MODULE IS UNREACHABLE IN THE PRINTED GAME. Selected alone it
still never fires: peak Stress never exceeds 2, so the gate never bites
and no ATTACK is ever gated. A module can be unreachable because of an
aspect it does not name -- attack_relief needs a problem_stress module
before it can act at all, and nothing in its own declaration says so.

(b) ONCE REACHABLE IT IS REAL. Holding the probe fixed and varying only
the module: group wins 12->21, 19->28, 50->57 at 3/4/6p, and DARVO arms
299->221, 294->213, 334->164. Under flat pressure the same shape appears
in DARVO alone: h1 arms 200 against flat_any_open's 300/400/600.

Stated as the conditional it is: GIVEN seats that attack at the gate.
Not advice to attack, and not evidence against F17.

AND IT CHANGES WHAT WE TOLD GROUND-GAME. CB-EV-0030 rejected H1 with
policies that never attacked, so H1-B was inert for every game behind
that verdict. The rejection stands on flat pressure alone -- wins are 0
either way -- but "H1-B does nothing" was never established and is now
known to be false. We owe them that correction.

Sensitivity: vary only the ATTACK rank. At 10 (greedy) and +55
(module-aware) the module never fires; at 110 it fires in every game.
Nothing about the module changed -- only whether any seat gave it a turn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-09 02:30:08 +02:00
parent 1b6a68fb39
commit 4a72420e01
4 changed files with 224 additions and 11 deletions

View file

@ -19,7 +19,7 @@
use cb_game_runtime::{ScenarioGame, Setup};
use cb_kernel::PlayerId;
use games_ground::bot::{play, GreedyPolicy, ModuleAwarePolicy, Policy};
use games_ground::bot::{play, GateAttackPolicy, GreedyPolicy, ModuleAwarePolicy, Policy};
use games_ground::config::Configuration;
use games_ground::{GroundState, ScoringMode};
@ -94,6 +94,10 @@ fn fired(m: &str, g: &games_ground::bot::BotGame) -> bool {
enum Seat {
Greedy,
ModuleAware,
/// F31's falsifier: a seat that attacks at the stress gate, so
/// `attack_relief.self_soothe_ge4` gets an opportunity at all. **A
/// probe, not a recommendation** — its win rate is not advice.
GateAttack,
}
fn sweep(cfg: &Configuration, players: u8, who: Seat) -> Cell {
@ -120,6 +124,7 @@ fn sweep(cfg: &Configuration, players: u8, who: Seat) -> Cell {
.map(|_| match who {
Seat::Greedy => Box::new(GreedyPolicy) as Box<dyn Policy>,
Seat::ModuleAware => Box::new(ModuleAwarePolicy) as Box<dyn Policy>,
Seat::GateAttack => Box::new(GateAttackPolicy) as Box<dyn Policy>,
})
.collect();
let g = match play(st, &mut ps) {
@ -227,6 +232,11 @@ fn main() {
println!("ModuleAwarePolicy. A row flat under `grdy` and moving under");
println!("`mod` is a module that needs a seat able to see it — not a");
println!("module that does nothing (F27's distinction).\n");
println!("`atk!` is GateAttackPolicy, which attacks at the stress gate so");
println!("attack_relief gets an opportunity at all (F31). It is a PROBE:");
println!("its win rate is not a recommendation. The peak/darvo/atk columns");
println!("are its games, because they are the only ones where ATTACK");
println!("happens.\n");
println!("`never fired: UNMEASURED` means no seat created that module's");
println!("precondition in any game, so its contribution to the row is not");
println!("a null result — it is an absence of evidence. The other numbers");
@ -235,8 +245,8 @@ fn main() {
for players in [3u8, 4, 6] {
println!("{players} players");
println!(
" {:<18} {:<38} {:>5} {:>5} {:>6} {:>6} {:>6}",
"aspect", "module / profile", "grdy", "mod", "peak", "darvo", "atk"
" {:<18} {:<38} {:>5} {:>5} {:>5} {:>6} {:>6} {:>6}",
"aspect", "module / profile", "grdy", "mod", "atk!", "peak", "darvo", "atk"
);
for (aspect, name, cfg) in points() {
let cfg = match cfg {
@ -257,6 +267,7 @@ fn main() {
}
let g = sweep(&cfg, players, Seat::Greedy);
let m = sweep(&cfg, players, Seat::ModuleAware);
let a = sweep(&cfg, players, Seat::GateAttack);
// **A module that never acted is UNMEASURED, not null.**
// Printing win counts for it invites the reading that it does
// nothing, when what happened is that no seat ever created
@ -269,7 +280,10 @@ fn main() {
// every game and drives group success to 0, which is a real
// result about that module even though its ATTACK half never
// gets an opportunity.
let never: Vec<&String> = m
// **The probe's row decides "never fired", not the others.**
// A module is unreachable only if the seat built to reach it
// could not either.
let never: Vec<&String> = a
.unfired
.iter()
.filter(|(_, n)| **n == GAMES)
@ -288,8 +302,8 @@ fn main() {
)
};
println!(
" {aspect:<18} {name:<38} {:>5} {:>5} {:>6} {:>6} {:>6}{note}",
g.won, m.won, m.peak_stress, m.darvo, m.attacks
" {aspect:<18} {name:<38} {:>5} {:>5} {:>5} {:>6} {:>6} {:>6}{note}",
g.won, m.won, a.won, a.peak_stress, a.darvo, a.attacks
);
}
println!();