//! Every board, in every mode, at every seat band (CB-WP-0047). //! //! The engine dealt `SCN_01` and only `SCN_01` for the whole life of this //! repo: `deal` took a scenario id, `setup` passed a literal, and **15 of //! the 20 Problem cards had never been dealt by anything**. The three //! scoring modes were reachable from the driver but no measurement had //! ever compared them. //! //! This plays all of it. It is a **characterisation panel**, not a //! hypothesis test: nothing here predicts an outcome, and the numbers //! exist so that a later change to a deck or a mode is visible as a //! change rather than discovered by a player. //! //! **SCN_01 and SCN_02 are the same board.** Identical suits and values //! at every priority; they differ only in prose. They are both played //! anyway — a panel that dropped one would hide the day they diverge — //! and the column is expected to match, which is itself a control. use cb_game_runtime::{ScenarioGame, Setup}; use games_ground::bot::{play, GreedyPolicy, ObjectivePolicy, Policy}; use games_ground::{GroundState, ScoringMode}; /// Games per cell. Named once so the banner and the assertion cannot /// disagree — a previous panel printed "200 games per cell" over /// 196-game columns (CB-REV-0002 #1). const GAMES: u32 = 100; struct Cell { games: u32, /// Games that reached an outcome over the full five rounds. A game /// that RAN is not a game that was PLAYED (CB-REV-0003 #2). played: u32, group_success: u32, /// Seats named a winner. Under SHARED GROUND that is everyone or /// nobody; under the other two it is the point of the mode. winners: u32, /// Claimed value summed over games, so the threshold has something /// to be compared against. total: u32, threshold: u32, setup_fails: u32, } /// Which bot fills every seat. CB-WP-0049 T03: F27 says the three modes /// produce identical play because greedy never reads `state.mode`; the /// only way to know whether that is a fact about GROUND or a fact about /// our bot is to run the same panel with a seat that does read it. #[derive(Clone, Copy, PartialEq)] enum Seat { Greedy, Objective, } fn sweep(scenario: &str, mode: ScoringMode, players: u8, who: Seat) -> Cell { let mut c = Cell { games: 0, played: 0, group_success: 0, winners: 0, total: 0, threshold: 0, setup_fails: 0, }; let preset = if scenario == "SCN_01" { format!("standard-{players}p") } else { format!("scn-{}-{players}p", scenario.rsplit('_').next().unwrap()) }; for seed in 0..GAMES as u64 { let Ok(mut st) = GroundState::setup( &Setup { players, preset: preset.clone(), patch: Default::default(), }, seed, ) else { // Counted, not skipped (CB-REV-0001 #11). c.setup_fails += 1; continue; }; st.mode = mode; // The board actually dealt, so the panel cannot report a // scenario it did not play. assert_eq!(st.scenario, scenario, "{preset} dealt {}", st.scenario); let mut ps: Vec> = (0..players) .map(|_| match who { Seat::Greedy => Box::new(GreedyPolicy) as Box, Seat::Objective => Box::new(ObjectivePolicy) as Box, }) .collect(); let g = match play(st, &mut ps) { Ok(g) => g, Err(e) => { eprintln!(" !! {scenario} {mode:?} {players}p seed {seed}: {e:?}"); continue; } }; c.games += 1; if g.state.outcome.is_some() && g.rounds == 5 { c.played += 1; } if let Some(o) = &g.state.outcome { if o.group_success { c.group_success += 1; } c.winners += o.winners.len() as u32; c.total += o.total; c.threshold = o.threshold; } } assert_eq!( c.games, GAMES, "{scenario} {mode:?} {players}p: only {} of {GAMES} games ran \ ({} setups refused) — every number in the cell is over a sample \ nobody chose", c.games, c.setup_fails ); assert_eq!( c.played, GAMES, "{scenario} {mode:?} {players}p: {} of {} games reached an outcome \ over five rounds", c.played, c.games ); c } fn main() { println!("CB-WP-0047 — the four Scenarios, the three Modes\n"); println!("Greedy throughout; {GAMES} games per cell; seeds 0..{GAMES}."); println!("`won` is group success; `win/g` is winning SEATS per game,"); println!("which is what separates the modes — SHARED GROUND names all"); println!("or none, the other two name a subset.\n"); let scenarios = games_ground::edition::scenarios().expect("Scenarios.csv"); // Every mode the kernel has. Adding a fourth without extending the // panel is the omission this list exists to make loud. let modes = [ ("SHARED GROUND ", ScoringMode::SharedGround), ("COMMON PROBLEM ", ScoringMode::CommonProblem), ("BONDED COALITIONS ", ScoringMode::BondedCoalitions), ]; for players in [2u8, 4, 6] { println!("{players} players"); println!( " {:<32} {:<18} {:>5} {:>5} {:>7} {:>7} {:>6} {:>9}", "scenario", "mode", "grdy", "obj", "win/g g", "win/g o", "pts", "threshold" ); for s in &scenarios { for (label, mode) in &modes { let g = sweep(&s.id, *mode, players, Seat::Greedy); let c = sweep(&s.id, *mode, players, Seat::Objective); println!( " {:<32} {label} {:>5} {:>5} {:>7.2} {:>7.2} {:>6.1} {:>9}", format!("{} {}", s.id, s.title), g.group_success, c.group_success, // **Both, side by side.** Reading one policy's number // against a figure remembered from an earlier pass is // how a comparison gets made against a board nobody // re-ran. f64::from(g.winners) / f64::from(g.games), f64::from(c.winners) / f64::from(c.games), f64::from(c.total) / f64::from(c.games), c.threshold, ); } } println!(); } println!("`grdy` is GreedyPolicy, which never reads state.mode; `obj`"); println!("is ObjectivePolicy, which plays its seat's own objective."); println!("If the obj column varies ACROSS modes where grdy does not,"); println!("F27 was a fact about our bot. If it does not, it is a fact"); println!("about GROUND.\n"); println!("SCN_01 and SCN_02 are the same board — identical suits and"); println!("values at every priority. Their rows are expected to MATCH,"); println!("and a divergence means one of the two decks was edited."); }