mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
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:
@@ -97,6 +97,12 @@ type expeditionBalanceProfile struct {
|
||||
// calibration sweep sets this per cell; everyday callers leave it
|
||||
// zero.
|
||||
HarvestRollsPerDay int
|
||||
// Phase 2 lever overrides (sweep-only). Zero means "use the live
|
||||
// shipped value." Wired into the harness day loop / runHarnessFight
|
||||
// path; the live runHarvestInterrupt is untouched. See
|
||||
// TestExpeditionBalance_Phase2_LeverSweep.
|
||||
RetreatThreatBumpOverride int
|
||||
SurpriseNickDivisorOverride int
|
||||
}
|
||||
|
||||
// expeditionTrialResult is the outcome of one simulated expedition.
|
||||
@@ -274,7 +280,7 @@ func (h *expeditionHarness) advanceExpeditionOneDay() expeditionTrialResult {
|
||||
// dnd_expedition_combat.go (Phase 2). Run continues
|
||||
// with carryover HP and a threat bump; the harvest
|
||||
// slot's loot is just forfeit.
|
||||
h.exp.ThreatLevel += retreatThreatBump
|
||||
h.exp.ThreatLevel += h.resolvedRetreatBump()
|
||||
if h.exp.ThreatLevel > 100 {
|
||||
h.exp.ThreatLevel = 100
|
||||
}
|
||||
@@ -333,6 +339,25 @@ func (h *expeditionHarness) advanceExpeditionOneDay() expeditionTrialResult {
|
||||
return expeditionTrialResult{}
|
||||
}
|
||||
|
||||
// resolvedRetreatBump returns the per-harness lever override or the
|
||||
// shipped retreatThreatBump if no override is set. Zero is the "use
|
||||
// live" sentinel so the field's zero value remains safe.
|
||||
func (h *expeditionHarness) resolvedRetreatBump() int {
|
||||
if h.retreatThreatBumpOverride > 0 {
|
||||
return h.retreatThreatBumpOverride
|
||||
}
|
||||
return retreatThreatBump
|
||||
}
|
||||
|
||||
// resolvedNickDivisor returns the per-harness override or the shipped
|
||||
// liveSurpriseNickDivisor. Same zero-sentinel contract as above.
|
||||
func (h *expeditionHarness) resolvedNickDivisor() int {
|
||||
if h.surpriseNickDivisorOverride > 0 {
|
||||
return h.surpriseNickDivisorOverride
|
||||
}
|
||||
return liveSurpriseNickDivisor
|
||||
}
|
||||
|
||||
// terminate stamps the final trial result with shared bookkeeping
|
||||
// (days elapsed, threat at end, encounter count, HP%).
|
||||
func (h *expeditionHarness) terminate(reason string, completed, died, starved bool) expeditionTrialResult {
|
||||
@@ -376,7 +401,11 @@ func (h *expeditionHarness) runHarnessFight(zone ZoneDefinition, elite bool) Com
|
||||
// (clampSurpriseNick) that breaks the chained-interrupt death
|
||||
// spiral. Mirror live exactly so the harness measures the same
|
||||
// lever the live caller applies.
|
||||
nick := clampSurpriseNick(surpriseRoundNick(monster, int(zone.Tier)), h.char.HPCurrent, h.char.HPMax)
|
||||
nick := clampSurpriseNickD(
|
||||
surpriseRoundNick(monster, int(zone.Tier)),
|
||||
h.char.HPCurrent, h.char.HPMax,
|
||||
h.resolvedNickDivisor(),
|
||||
)
|
||||
h.char.HPCurrent -= nick
|
||||
|
||||
player := buildHarnessPlayer(h.char)
|
||||
@@ -487,6 +516,10 @@ type expeditionHarness struct {
|
||||
rng *harnessRNG
|
||||
encounters int
|
||||
rollsPerDay int // resolved from profile + default; never zero
|
||||
// Lever overrides for the Phase 2 sweep. Zero on both fields ⇒
|
||||
// live behavior (retreatThreatBump=5, /5 surprise-nick divisor).
|
||||
retreatThreatBumpOverride int
|
||||
surpriseNickDivisorOverride int
|
||||
// traceFight, if non-nil, is invoked once per fight inside
|
||||
// runHarnessFight with a human-readable summary. Used by the
|
||||
// Phase 2 lethality probe to spot whether the nick, the picked
|
||||
@@ -531,10 +564,12 @@ func runExpeditionBalanceTrial(p expeditionBalanceProfile, seed uint64) expediti
|
||||
rolls = harnessHarvestRollsPerDay
|
||||
}
|
||||
h := &expeditionHarness{
|
||||
exp: exp,
|
||||
char: char,
|
||||
rng: newHarnessRNG(seed),
|
||||
rollsPerDay: rolls,
|
||||
exp: exp,
|
||||
char: char,
|
||||
rng: newHarnessRNG(seed),
|
||||
rollsPerDay: rolls,
|
||||
retreatThreatBumpOverride: p.RetreatThreatBumpOverride,
|
||||
surpriseNickDivisorOverride: p.SurpriseNickDivisorOverride,
|
||||
}
|
||||
for {
|
||||
res := h.advanceExpeditionOneDay()
|
||||
|
||||
Reference in New Issue
Block a user