H2's ATTACK dynamics, and a construction hazard found while measuring them
Some checks failed
ci / check (push) Failing after 3s
Some checks failed
ci / check (push) Failing after 3s
The H2 panel measured wins, variance and DARVO but not ATTACK selection — which is F17's actual question. attack-value now runs H2 too. The shape is the finding. Under H1 a seat that sometimes attacks loses everything at 3p and above. Under H2 rank-75 wins 112/173/199 against greedy's 120/175/199, while attacking and arming DARVO. So H2 makes occasional ATTACK affordable — it does not make it pay. rank-75 never beats greedy in any cell, and rank-95 (always attack) still wins 0 everywhere in all three variants, so "not always-attack-optimal" holds. F17 therefore stands: ATTACK earns its place in no mode. What changed is that choosing it is no longer catastrophic. Whether affordable is what the design wants is ground-game's judgement. And a hazard: with_variant() exists because H2 assigns Problem owners at setup and `state.variant = v` leaves them unassigned, so scoped pressure ticks nobody and H2 measures as INERT. Three call sites had the bare write, including cb-play's driver. No published figure is affected, and that was checked rather than assumed: h2-panel used the builder, and the two harnesses with the bare write had only ever run baseline and H1, neither of which has a setup step; the driver has never played H2. All three fixed, and a_bare_variant_write_leaves_h2_inert now states the difference so a regression is caught by a named test rather than by a reader wondering why H2 did nothing. The builder was not enough — the field is public, so the old form still compiles. Worth knowing before the next variant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
d3c2dc1375
commit
47e7941057
7 changed files with 409 additions and 4 deletions
|
|
@ -2288,6 +2288,60 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// **The hazard `with_variant` exists for, made visible.**
|
||||
///
|
||||
/// Setting `state.variant = H2` directly compiles and produces a
|
||||
/// game where **no Problem has a scope**, so scoped pressure ticks
|
||||
/// nobody and H2 measures as inert — a silently wrong result, not
|
||||
/// a failure.
|
||||
///
|
||||
/// Three call sites had the bare write when this was found, one of
|
||||
/// them the driver. This test states the difference so a
|
||||
/// regression is caught by a name rather than by a puzzled reader
|
||||
/// wondering why H2 did nothing.
|
||||
#[test]
|
||||
fn a_bare_variant_write_leaves_h2_inert() {
|
||||
let base = GroundState::setup(
|
||||
&Setup {
|
||||
players: 4,
|
||||
preset: "standard-4p".into(),
|
||||
patch: Default::default(),
|
||||
},
|
||||
5,
|
||||
)
|
||||
.expect("setup");
|
||||
|
||||
// The wrong way.
|
||||
let mut bare = base.clone();
|
||||
bare.variant = Variant::H2ScopedProblemStress;
|
||||
assert!(
|
||||
bare.problems.values().all(|p| p.scope.is_none()),
|
||||
"a bare write should leave scopes unassigned — if it does not, \
|
||||
`with_variant` is no longer load-bearing and this test is stale"
|
||||
);
|
||||
assert!(
|
||||
!bare
|
||||
.end_round_events()
|
||||
.iter()
|
||||
.any(|e| matches!(e, GroundEvent::StressSet { .. })),
|
||||
"a bare-write H2 produced pressure, so the hazard has changed shape"
|
||||
);
|
||||
|
||||
// The right way.
|
||||
let built = base.with_variant(Variant::H2ScopedProblemStress);
|
||||
assert!(
|
||||
built.problems.values().all(|p| p.scope.is_some()),
|
||||
"`with_variant` did not assign scopes"
|
||||
);
|
||||
assert!(
|
||||
built
|
||||
.end_round_events()
|
||||
.iter()
|
||||
.any(|e| matches!(e, GroundEvent::StressSet { .. })),
|
||||
"`with_variant` H2 produced no pressure with Problems unclaimed"
|
||||
);
|
||||
}
|
||||
|
||||
/// **The baseline is untouched by H2's new state** (T02).
|
||||
///
|
||||
/// `owner` and `scope` are new fields on `ProblemState`. Both are
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue