CB-REV-0003: round 3, and three of four FATAL came from round 2's fixes
Some checks failed
ci / check (push) Failing after 3s

The pattern is now measured over three rounds: 5 fatal, then 3 (2 from the
previous round's corrections), then 4 (3 from them). The corrections are
not getting safer.

FATAL 1: round 2's short-cell assertion went into regulation.rs only.
attack-value.rs — which produced every number in CB-EV-0030's DARVO table
— still just warned, and the gate registered to close the finding claimed
the property for both.

FATAL 2, the sharpest of the three rounds: counting games proves they
STARTED. Stopping the engine after one round gives 200 games, all-zero
columns and exit 0 — byte for byte the signature CB-EV-0030 says the
instrumentation distinguishes from a real result. Both harnesses now
require every counted game to have reached an outcome over five rounds.

FATAL 3: round 2's `.csv` filter was applied to all three loops, so
catalog.yaml and rules_delta.yaml — whose missing digests were round 1's
finding — were recorded and then never compared, and never checked against
upstream at all. Only the parser loop filters now.

FATAL 4: five of six tiebreak comparators had no coverage. GR-E04's
tiebreak never executes in any scenario. All four are now covered and
mutation-verified; the Blame key needed compensating claims to be
reachable at all, since Blame also lowers the coalition score.

SERIOUS: "peak held" computed the same number as "peak assigned" for every
possible input — the real gap was that START_STRESS was an unchecked
constant, now read off the dealt state; cadence="none" was a pure
loophole, removed; sibling discovery swapped a hand-written list for
hand-written globs and missed metadata.json and VARIANT.md, both named in
the package's own changed_files — now walked, and it found them
immediately; and "~72,000 games" was unsourced, make panels runs 17,600.

Also separated two kinds of number that were presented alike: seats×games
is invariant, 363 and 29 vary 7.1%-11.5% across samples.

Round 4 owed. The conclusion is not that the work is nearly right — it is
that author-made corrections to measurement work should be assumed
defective until a fresh reader has attacked them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-08 10:26:25 +02:00
parent feb68027f9
commit c4a8a227c0
9 changed files with 386 additions and 57 deletions

View file

@ -2427,6 +2427,108 @@ mod tests {
);
}
/// **Every tiebreak comparator, both modes** (CB-REV-0003 #4).
///
/// The strengthened oracle covered **1 of 6**: GR-E03's Stress
/// key. Reversing GR-E03's *Bond* key, or **either** of GR-E04's
/// two keys, left all 179 tests and all 26 scenarios green —
/// GR-E04's tiebreak had no coverage at all, because the only
/// GR-E04 scenario sets `group_success: false`, so the winners
/// branch returns empty and the comparator never runs.
///
/// Each case is built so exactly one key decides, and the
/// expected winner is named — asserting "the set changed" is what
/// let a reversed comparator pass in the first place.
#[test]
fn every_tiebreak_key_decides_the_way_the_rules_say() {
let board = |mode: ScoringMode| {
let mut s = setup(4, Variant::Baseline, 5);
s.mode = mode;
let seats: Vec<PlayerId> = s.players.keys().copied().collect();
let ids: Vec<u32> = s.problems.keys().copied().collect();
for id in &ids {
s.problems.remove(id);
}
(s, seats)
};
let mk = |value: u8, claimed_by: Option<PlayerId>| ProblemState {
suit: Suit::Repair,
value,
face_up: true,
denied: false,
claimed_by,
protected_this_round: false,
};
// GR-E03 key 2, Stress: equal claims, lower Stress wins.
let (mut s, seats) = board(ScoringMode::CommonProblem);
s.problems.insert(1, mk(4, Some(seats[0])));
s.problems.insert(2, mk(4, Some(seats[1])));
s.players.get_mut(&seats[0]).expect("a").stress = 5;
s.players.get_mut(&seats[1]).expect("b").stress = 1;
assert_eq!(
s.score().winners,
vec![seats[1]],
"GR-E03: lower Stress must win the tiebreak"
);
// GR-E03 key 3, Bonds: equal claims AND equal Stress, more
// Bonds wins.
s.players.get_mut(&seats[0]).expect("a").stress = 1;
s.relations
.insert(Pair::new(seats[0], seats[2]), Relation::Bond);
assert_eq!(
s.score().winners,
vec![seats[0]],
"GR-E03: with claims and Stress level, more Bonds must win"
);
// GR-E04 key 2, combined Stress: two coalitions of equal
// score, the calmer one wins.
let (mut s, seats) = board(ScoringMode::BondedCoalitions);
s.problems.insert(1, mk(4, Some(seats[0])));
s.problems.insert(2, mk(4, Some(seats[2])));
s.relations
.insert(Pair::new(seats[0], seats[1]), Relation::Bond);
s.relations
.insert(Pair::new(seats[2], seats[3]), Relation::Bond);
for seat in &seats {
s.players.get_mut(seat).expect("p").stress = 1;
}
s.players.get_mut(&seats[0]).expect("p").stress = 5;
let won = s.score().winners;
assert!(
won.contains(&seats[2]) && !won.contains(&seats[0]),
"GR-E04: the coalition with lower combined Stress must win, got {won:?}"
);
// GR-E04 key 3, Blame — and reaching it takes care, which is
// the point. A coalition's score is `sum(claimed - blame)`, so
// a Blame token lowers the score too and key 1 decides first.
// The key is only reachable when the claims COMPENSATE: 5
// claimed with one Blame ties 4 claimed with none.
s.players.get_mut(&seats[0]).expect("p").stress = 1;
s.problems.insert(1, mk(5, Some(seats[0])));
s.problems.insert(2, mk(4, Some(seats[2])));
s.players.get_mut(&seats[0]).expect("p").blame_from = vec![seats[3]];
let scored = s.score();
assert_eq!(
scored.coalitions.len(),
2,
"the fixture needs two coalitions to compare"
);
assert_eq!(
scored.coalitions[0].score, scored.coalitions[1].score,
"the Blame key is unreachable unless the scores tie: {:?}",
scored.coalitions
);
let won = scored.winners;
assert!(
won.contains(&seats[2]) && !won.contains(&seats[0]),
"GR-E04: with score and Stress level, fewer Blame must win, got {won:?}"
);
}
/// **`rules_delta.yaml`'s `unchanged:` list is ground-game's claim
/// about their own experiment, and it is checkable.**
///