H1-B on the DARVO extra Attack: the delta says the extra Attack shares the Attack resolution "so it can self-soothe too if Stress >= 4". CB-WP-0038 asserted it because the code shares resolve_attack; nothing tested it, and CB-EV-0031's withdrawn mechanism story ran through that exact path. Now tested and mutation-verified. Round-5 pressure did not reach the score, and this was a real defect rather than a reporting one. end_round_events scored from `self` while H1-A's pressure went into `work`, and score() reads Stress for the GR-E03 and GR-E04 tiebreaks — so the final round's pressure was invisible to the two modes CB-EV-0030 reports on. Fixed. The test uses the case that bites: uniform pressure preserves an ordering, so it takes the clamp at 5 to collapse a gap and change who wins. Inert arms reported separately: a DARVO arm at the End of Round 5 can never advance a stage, and criterion 1 is about DARVO mattering. 29 of 363 at 2p, none above — matching the reviewer's independent figure, so criterion 1 stands as met. That fix produced one more wrong-subject error, caught before reporting: the first inert-arm metric tested `g.rounds >= 5`, a property of the GAME rather than the EVENT, so it marked every arm in every completed game inert and briefly read as "criterion 1 fails after all". An arm is inert when no RoundEnded follows it. regulation.rs no longer skips setup failures silently: they are counted, and a short cell fails an assertion rather than printing a number a reader has to notice — which is the credit CB-EV-0030 §3 took and half earned. All thirteen challenges closed. Re-review is owed before any of this travels: the corrections were made by the author of the errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
041c0e7c3e
commit
84aa09264c
4 changed files with 218 additions and 16 deletions
|
|
@ -1572,9 +1572,15 @@ impl GroundState {
|
|||
.map_or(self.lead, |i| seats[(i + 1) % seats.len()]);
|
||||
|
||||
// GR-R09: after Round 5's End the game ends and scoring applies.
|
||||
// **Scored from `work`, not `self`** (CB-REV-0001 #13). H1-A's
|
||||
// pressure goes into `work` above, and `score` reads Stress for
|
||||
// the GR-E03 and GR-E04 tiebreaks — so scoring from `self` made
|
||||
// the final round's pressure invisible to exactly the two modes
|
||||
// CB-EV-0030 reports on. Under the baseline `work` is a clone and
|
||||
// this is a no-op.
|
||||
if self.round >= 5 {
|
||||
events.push(GroundEvent::GameEnded {
|
||||
outcome: self.score(),
|
||||
outcome: work.score(),
|
||||
});
|
||||
return events;
|
||||
}
|
||||
|
|
@ -2312,6 +2318,106 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// **H1-B applies to the DARVO extra Attack** (review M12).
|
||||
///
|
||||
/// The delta says so outright — *"DARVO-stage extra Attack uses
|
||||
/// the same Attack resolution (so it can self-soothe too if
|
||||
/// Stress >= 4)"* — and CB-WP-0038 asserted it on the strength of
|
||||
/// the code sharing `resolve_attack`. **Nothing tested it**, and
|
||||
/// CB-EV-0031's withdrawn mechanism story ran through this path.
|
||||
#[test]
|
||||
fn h1b_soothes_the_darvo_stage_attack_too() {
|
||||
let soothe_count = |variant: Variant| {
|
||||
let mut s = setup(3, variant, 5);
|
||||
let seats: Vec<PlayerId> = s.players.keys().copied().collect();
|
||||
let (owner, target) = (seats[0], seats[1]);
|
||||
s.step = RoundStep::Resolve;
|
||||
s.players.get_mut(&owner).expect("o").stress = 4;
|
||||
s.players.get_mut(&owner).expect("o").darvo = DarvoStage::Attack;
|
||||
s.darvo_targets.insert(
|
||||
owner,
|
||||
DarvoTarget {
|
||||
problem: None,
|
||||
player: Some(target),
|
||||
},
|
||||
);
|
||||
s.resolution_events()
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
matches!(e, GroundEvent::StressSet { player, stress }
|
||||
if *player == owner && *stress == 3)
|
||||
})
|
||||
.count()
|
||||
};
|
||||
assert_eq!(
|
||||
soothe_count(Variant::H1ProblemStress),
|
||||
1,
|
||||
"the DARVO extra Attack did not self-soothe, though the delta says it \
|
||||
shares the same Attack resolution"
|
||||
);
|
||||
assert_eq!(
|
||||
soothe_count(Variant::Baseline),
|
||||
0,
|
||||
"the baseline soothed on a DARVO stage Attack"
|
||||
);
|
||||
}
|
||||
|
||||
/// **Round-5 pressure must reach the score** (review #13).
|
||||
///
|
||||
/// `end_round_events` scored from `self` while H1-A's pressure
|
||||
/// went into `work`, so the last round's Stress was invisible to
|
||||
/// the GR-E03 and GR-E04 tiebreaks — the two modes CB-EV-0030
|
||||
/// reports on.
|
||||
///
|
||||
/// Uniform pressure preserves an ordering, so the case that bites
|
||||
/// is the **clamp**: two seats with equal claims, one at 5 and one
|
||||
/// at 4, become equal once both are pushed to 5.
|
||||
#[test]
|
||||
fn the_last_rounds_pressure_reaches_the_tiebreak() {
|
||||
let mut s = setup(3, Variant::H1ProblemStress, 5);
|
||||
s.mode = ScoringMode::CommonProblem;
|
||||
s.round = 5;
|
||||
let seats: Vec<PlayerId> = s.players.keys().copied().collect();
|
||||
|
||||
// A CONSTRUCTED board, not the deal: the tiebreak only
|
||||
// decides among seats tied at the top, and no natural 3p deal
|
||||
// splits evenly while leaving a Problem unclaimed.
|
||||
//
|
||||
// Two Problems worth 4 (one each to the two seats, so they
|
||||
// tie on personal) and one worth 1 left unclaimed, so H1-A
|
||||
// fires. Claimed = 8 against a threshold of 7.
|
||||
let ids: Vec<u32> = s.problems.keys().copied().collect();
|
||||
for id in &ids {
|
||||
s.problems.remove(id);
|
||||
}
|
||||
let mk = |value: u8, claimed_by: Option<PlayerId>| ProblemState {
|
||||
suit: Suit::Repair,
|
||||
value,
|
||||
face_up: true,
|
||||
denied: false,
|
||||
claimed_by,
|
||||
protected_this_round: false,
|
||||
};
|
||||
s.problems.insert(1, mk(4, Some(seats[0])));
|
||||
s.problems.insert(2, mk(4, Some(seats[1])));
|
||||
s.problems.insert(3, mk(1, None));
|
||||
s.players.get_mut(&seats[0]).expect("a").stress = 5;
|
||||
s.players.get_mut(&seats[1]).expect("b").stress = 4;
|
||||
|
||||
let events = s.end_round_events();
|
||||
let Some(GroundEvent::GameEnded { outcome }) = events.last() else {
|
||||
panic!("round 5 did not end the game");
|
||||
};
|
||||
assert!(outcome.group_success, "the fixture must qualify");
|
||||
let pre = s.score();
|
||||
assert_ne!(
|
||||
outcome.winners, pre.winners,
|
||||
"the outcome ignores the final round's pressure — scored from `self`. \
|
||||
pre={:?} post={:?}",
|
||||
pre.winners, outcome.winners
|
||||
);
|
||||
}
|
||||
|
||||
/// **`rules_delta.yaml`'s `unchanged:` list is ground-game's claim
|
||||
/// about their own experiment, and it is checkable.**
|
||||
///
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue