CB-WP-0038: variant selection, H1 implemented, and H1 measured
Some checks failed
ci / check (push) Failing after 3s

ground-game packages hypotheses as selectable rules variants — a catalog,
a rules_delta.yaml, and prose — and their note is explicit that CSV text
alone is not executable here. So the kernel gains a Variant in game state:
in the state, therefore in the hash, therefore in the recording, because a
scenario replayed under a different variant would diverge silently.

Baseline is bit-for-bit what it was, asserted across seat counts and
seeds. A variant system that perturbs the baseline invalidates every
measurement this repo has.

H1-A and H1-B implemented from rules_delta.yaml and mutation-proven on
their own defects: "unclaimed" misread as face-up-and-unsolved, and the
attacker's Stress read after the attack's effects. Their `unchanged:` list
is asserted rather than trusted — that list is their claim about their own
experiment.

Measured, and three of their four criteria fail. DARVO arm rate is still
0 under greedy; ATTACK selection does not rise and falls for the rank-75
policy; group success collapses from 165/190/200 to 0 at 3/4/6 seats.
The mechanism is not the assumed one: greedy answers the pressure by
regulating, Stress plateaus at 3, so it never reaches the gate at 4 or the
arm at 5 — H1-A acts as a solve-rate tax and H1-B is unreachable under
competent play.

A harness defect was caught before the claim: sweep discarded refused
games silently and never reported its count, so "nobody won" and "nothing
played" printed identically. Reporting H1 as unwinnable on that basis
would have been the ADR-0018 family aimed at another repo's design. All
200 games ran in every cell; the zeros are real.

Chaos d8 = 8 — the window's first override, redrew L against a structural
L, so it changed nothing. Window 3 recorded in ChaosRollHistory.

NOT REVIEWED: tier L owes a separate-agent adversarial review, and no H1
result may reach ground-game until it has run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-08 00:50:08 +02:00
parent 627992bc85
commit a928b5925c
15 changed files with 928 additions and 28 deletions

View file

@ -1216,6 +1216,7 @@ mod tests {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace: crate::table::Pace::Speed,
variant: games_ground::Variant::Baseline,
},
std::io::Cursor::new(Vec::new()),
out,
@ -1406,6 +1407,7 @@ mod tests {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace: crate::table::Pace::Speed,
variant: games_ground::Variant::Baseline,
},
std::io::Cursor::new(Vec::new()),
out,

View file

@ -765,6 +765,7 @@ mod tests {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace: crate::table::Pace::Speed,
variant: games_ground::Variant::Baseline,
};
let mut sink: Vec<u8> = Vec::new();
let summary =

View file

@ -29,6 +29,8 @@ play:
--replay DIR write a .cbreplay bundle of the finished game to DIR
--record FILE write the finished game as a scenario YAML
--trial FILE write a trial log: what the player said, bound to where
--variant V rules package: ground-darvo-r0 (default) or
h1-problem-stress (experimental; ground-game H1)
--pace P speed (default) or interactive: how much ornamentation
is performed. Never changes the game -- the recording is
byte-identical either way (specs/Ornamentation.md)
@ -137,6 +139,13 @@ fn parse_args(argv: &[String]) -> Result<Mode, String> {
// **The flag exists before anything reads it, deliberately.**
// It is the seam `clay-animate` attaches to, and a seam is
// cheap now where a retrofit would not be.
// CB-WP-0038: ground-game names these in
// `editions/catalog.yaml`; `--variant` takes that id.
"--variant" => {
play_flags.push(flag.into());
config.variant = value(i, argv, flag)?.parse()?;
i += 2;
}
"--pace" => {
play_flags.push(flag.into());
config.pace = value(i, argv, flag)?.parse()?;
@ -306,6 +315,7 @@ mod tests {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace: table::Pace::Speed,
variant: games_ground::Variant::Baseline,
};
let script = "0\n".repeat(400);
let mut out: Vec<u8> = Vec::new();
@ -352,6 +362,7 @@ mod tests {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace,
variant: games_ground::Variant::Baseline,
};
let script = "0\n".repeat(400);
let mut out: Vec<u8> = Vec::new();
@ -420,6 +431,7 @@ mod tests {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace: table::Pace::Speed,
variant: games_ground::Variant::Baseline,
};
let mut out: Vec<u8> = Vec::new();
table::play(&config, "0\n".repeat(200).as_bytes(), &mut out).expect("game");
@ -485,6 +497,7 @@ mod tests {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace: table::Pace::Speed,
variant: games_ground::Variant::Baseline,
};
let mut out: Vec<u8> = Vec::new();
let summary = table::play(&config, "".as_bytes(), &mut out).expect("bot game");
@ -548,6 +561,7 @@ mod tests {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace: table::Pace::Speed,
variant: games_ground::Variant::Baseline,
};
let mut out: Vec<u8> = Vec::new();
let summary = table::play(&config, "".as_bytes(), &mut out).expect("game");

View file

@ -55,6 +55,11 @@ pub struct Config {
/// rules cannot see gets shown. Calling both of them "mode" on one
/// driver is a collision waiting to be mis-read.
pub pace: Pace,
/// Which selectable rules package to play (CB-WP-0038).
///
/// **Unlike `pace`, this changes the game** — outcome, state hash and
/// recording all move, which is correct: a variant is mechanism.
pub variant: games_ground::Variant,
}
/// Speed or Interactive (Ornamentation §4).
@ -99,6 +104,7 @@ impl Default for Config {
trial: None,
mode: games_ground::ScoringMode::SharedGround,
pace: Pace::Speed,
variant: games_ground::Variant::Baseline,
}
}
}
@ -400,6 +406,9 @@ fn run_game<'a, R: BufRead + 'a, W: Write + 'a>(
// GR-E02..E04. Set before the hash is taken, so a recorded session
// replays in the mode it was played in.
initial.mode = config.mode;
// CB-WP-0038: set beside `mode` and before the hash, so a recorded
// session replays under the variant it was played under.
initial.variant = config.variant;
let initial_json = serde_json::to_value(&initial).map_err(|e| e.to_string())?;
let initial_hash = cb_events::state_hash_hex(&initial);