Phase 2 (sweep): lever-tuning sweep, negative result

Sweep the two knobs surfaced by Phases 2a/2b — retreatThreatBump
and clampSurpriseNick's wounded-entrant divisor — across a full
3×4 grid (bump ∈ {2, 5, 10} × divisor ∈ {3, 5, 8, 12}) at 200
trials/cell across every matrix zone.

Wiring is harness-only: clampSurpriseNick keeps its live shape and
delegates to a new clampSurpriseNickD(divisor) variant; the harness
profile gains RetreatThreatBumpOverride/SurpriseNickDivisorOverride
fields threaded onto expeditionHarness; resolvedRetreatBump and
resolvedNickDivisor pick override-or-live. Zero on either field
falls back to the shipped value so live runHarvestInterrupt is
untouched.

Sweep test: TestExpeditionBalance_Phase2_LeverSweep, -short skipped,
mirrors Phase2_CadenceCalibration's per-tier digest shape.

Outcome: across 24,000 trial-cells (12 lever combos × 10 zones
× 200 trials), every cell reports 0.0% completion / ~100% death.
The knobs are inert on the headline metric — even (b=2, d=12)
can't lift any tier off the floor. Confirms the post-2b
tier-lethality trace: remaining deaths are fresh-entry elite
one-shots (Warchief, Hag, Roper, Young Red Dragon), not chained-
interrupt cascades. Justifies Phase 2c (roster dilution) rather
than further tuning of these two levers.

Plan doc updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-15 10:39:45 -07:00
parent 159daf8fc8
commit 8cdd64b383
4 changed files with 198 additions and 8 deletions

View File

@@ -232,14 +232,30 @@ func surpriseRoundNick(m DnDMonsterTemplate, tier int) int {
//
// Tuning surface: the /5 divisor is the wounded-fighter lethality knob.
// Tighter (e.g. /10) is gentler; looser (/3) re-opens the cascade. See
// gogobee_expedition_difficulty.md Phase 2b.
// gogobee_expedition_difficulty.md Phase 2b. liveSurpriseNickDivisor
// names the shipped value; the harness Phase 2 lever sweep
// (TestExpeditionBalance_Phase2_LeverSweep) calls clampSurpriseNickD
// directly with alternate divisors. Live callers always go through
// clampSurpriseNick.
const liveSurpriseNickDivisor = 5
func clampSurpriseNick(rawNick, hpCurrent, hpMax int) int {
return clampSurpriseNickD(rawNick, hpCurrent, hpMax, liveSurpriseNickDivisor)
}
// clampSurpriseNickD is the divisor-parameterized form used by the
// sim harness lever sweep. divisor <= 0 falls back to the shipped
// value so a zero-valued harness override behaves as "use live."
func clampSurpriseNickD(rawNick, hpCurrent, hpMax, divisor int) int {
if rawNick <= 0 || hpCurrent <= 0 {
return 0
}
if divisor <= 0 {
divisor = liveSurpriseNickDivisor
}
nick := rawNick
if hpCurrent < hpMax {
cap := hpCurrent / 5
cap := hpCurrent / divisor
if cap < 1 {
cap = 1
}