Phase 2 diagnostics named the InterruptElite bracket as the likely
first lever; tier-walking the lethality probe at the matrix cadence
told a different story. Phase 1's uniform-0% baseline isn't an
elite-bracket calibration issue at all — every tier reads 0% because
the engine's TimedOut contract was being ignored by every expedition
caller.
combat_engine.go:451 says: "Timeout = retreat, not lethal blow.
Caller treats a timeout loss as 'fight ended, no character death'".
But runHarvestInterrupt / tryPatrolEncounter / resolveCombatRoom all
called abandonZoneRun + retireAllRegionRuns on any !PlayerWon —
ending the expedition outright on a retreat. The retreat flavor line
("X outlasts you. You retreat from the expedition, wounded but alive")
was already in the code, just stapled to an actual run-abort.
Splits the policy by caller:
• runHarvestInterrupt — autopilot daytime interrupt. TimedOut →
retreat: threat +5, HP carries over, run continues, harvest slot
forfeit (no kill / loot). HP<=0 still ends the run + marks dead.
• tryPatrolEncounter — !advance pre-room patrol roll. Same
retreat policy: patrols don't gate progress, so retreating from
one and walking into the next room is the right shape.
• resolveCombatRoom — !advance room/elite combat. Unchanged —
this path gates room progression; a retreat has nowhere to go, so
any loss still ends the run. (Manual zone runs were always
intended to end here.)
Harness mirrored: daytime interrupt timeout → carry HP + threat bump
+ continue day; night-encounter loss → terminate (mirrors
resolveCombatRoom, since live night encounters defer to !advance).
retreatThreatBump = 5 is the per-retreat threat penalty. Low enough
not to compound brutally with chained retreats, high enough that 3–4
retreats noticeably walks the threat clock toward Stirring. Easy to
dial in Phase 3 if zones go off-band.
Phase 1 matrix after the change still reads 0% completion at every
cell — but the encounter counts and survival shape are dramatically
different (T4 underdark 3.6→7.5 encs; T3 underforge trial saw 18
encounters across 10 days where the pre-change run died on day 2/3).
Adds TestExpeditionBalance_Phase2_TierLethality, a tier-walking
companion to the T1/rolls=1 probe, that traces every fight at the
matrix cadence across one zone per tier — the actual Phase 2b lever
work picks from this data, not the old T1-only probe.
The remaining 0% is now legibly driven by tier-disproportionate elite
rosters (Hobgoblin Warchief at T1, Green Hag at T2, Roper/Helmed
Horror higher up) that one-shot or two-shot tier-appropriate
fighters. Phase 2b's lever shortlist:
1. Roster gate / SpawnWeight tuning to dilute over-tier elites.
2. Surprise-nick floor reduction on chained interrupts (carryover
HP + nick is the death-spiral fingerprint at T1 specifically).
3. Per-day cadence reduction if 1+2 don't carry T1 to band.
Pre-existing test failures (TestAdv2Scenario_ZoneRunGoblinWarrens,
TestMageSpellbookLineInRender) verified to fail identically on HEAD;
no new test regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three Phase 2 diagnostic artifacts. No tuning knob has moved on
production code yet — these tests calibrate the harness and surface
the real first lever for next session.
1) Cadence calibration sweep
TestExpeditionBalance_Phase2_CadenceCalibration sweeps
HarvestRollsPerDay ∈ {1,2,3,4} across the full Phase 1 matrix and
logs per-cell + per-tier completion. Required a new
HarvestRollsPerDay field on expeditionBalanceProfile so cells can
override the package-default constant. Finding: cadence is NOT the
dominant lever — at rolls=1 the T1 cell only reaches 2%, with
bimodal hp_left (100% survivors / 0% deaths). Killed the cadence
hypothesis from Phase 1's commit message.
2) Gear-tier centerline fix
phase1TierCenterline bumped for T3/T4/T5 (8→9, 11→13, 15→17). The
shared gearTier ladder (5/9/13/17 boundaries) was placing T3/T4/T5
centerlines one gear bracket *below* the zone's tier, so those
cells fought with under-spec'd weapons/armor. New centerlines are
the lowest level in each tier's design-doc range where gearTier ==
tier. All centerlines still inside their design-doc ranges. Effect
in the sweep at rolls=1: underforge T3 1.0% → 10.5% comp, underdark
T4 flipped from pure combat-death to 14% starve (i.e. fighter now
survives combat, runs out of food). Real bug, but small — the
structural lethality problem remains.
3) Lethality probe + traceFight hook
TestExpeditionBalance_Phase2_LethalityProbe runs 5 trials at the
cleanest cell (T1 goblin_warrens L3 fighter, rolls=1) with a new
optional traceFight hook on expeditionHarness that logs
monster/AC/atk/HP-pre/HP-post/outcome per fight. Hook is nil in
production runs, zero cost when unused. Finding: at T1, the
InterruptElite branch keeps drawing Hobgoblin Warchief (AC 18,
atk +5) from goblin_warrens' elite roster, and an L3 fighter has
~coin-flip odds against a CR-6-ish elite. One bad draw = dead;
that's the bimodal hp_left fingerprint from the sweep. Non-elite
draws (Worg, AC 13) play out as normal multi-round combats and
are winnable.
Next-session lever choices, in order of suspected impact:
- Roster gate: Hobgoblin Warchief out of (or weighted down in)
the T1 elite pool — it's tier-disproportionate for goblin_warrens.
- InterruptElite threshold: rarer elite-bracket draws at low threat
so a single d20 swing doesn't equal expedition end.
- Tier-floor cap on already-over-tier bestiary entries.
Plan doc: gogobee_expedition_difficulty.md §Phase 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New permanent test TestExpeditionBalance_Phase1_FullMatrix — every
registered zone × its tier-centerline level, 200 trials/cell, Fighter
fixed (class parity is the class-balance pass's job).
Centerlines per plan doc §Method (median of design-doc level range):
T1=L3, T2=L5, T3=L8, T4=L11, T5=L15.
Gates split: wiring pathologies (zero-day loop, days > cap) are fatal;
"0% at T1 / 100% at T5" land as WARN log lines, not t.Errorf. Phase 1's
deliverable is a logged baseline, not a tuning gate — Phase 2 promotes
those to band assertions once the global levers move.
Baseline numbers (seed 0xE0FFEE1, 200 trials, current Phase 0 harness):
all 10 cells: 0.0% completion, 100% death, median day 2,
avg encounters 2.0-3.6, threat at end 5-7.
The uniform 0% says the placeholder harnessHarvestRollsPerDay=4 plus
the tier-floored AC/atk is overwhelming the fighter on day 1 across
all tiers, well before threat drift matters. Per-tier spread is 0.0pp
because every zone is floored — Phase 2 has plenty of headroom and a
clear first lever (encounter cadence).
Plan doc: gogobee_expedition_difficulty.md §Phase 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New file internal/plugin/expedition_balance.go — sibling to
dnd_class_balance.go, no dnd_ prefix per feedback_avoid_dnd_naming.
Plan doc: gogobee_expedition_difficulty.md.
Critical de-risk settled: clock-injection seam. The harness does not
run the wall-clock tickers (expeditionBriefingTicker @ 06:00,
expeditionRecapTicker @ 21:00, expedition_ambient @ 3h) and does not
inject a virtual time.Now. Instead, advanceExpeditionOneDay
reimplements the morning→day→night pipeline by calling the math-pure
helpers directly: applyDailyBurn, dailyThreatDrift,
resolveCombatInterrupt, resolveWanderingCheck, simulateCombatWithRNG.
No DB, no goroutines, no clock.
Phase 0 trade-offs (documented in the file header):
- Boss completion deferred; survive-N-days is the proxy.
- Per-region zones, loot/XP, pardon/Sovereign, babysit, temporal
stack effects all skipped — Phase 1+ scope.
- Encounter cadence (harnessHarvestRollsPerDay=4) is a placeholder
to be calibrated against live traces in Phase 1.
Tests:
- TestExpeditionBalance_Phase0_Spike — T2 Crypt Valdris × L5 Fighter,
100 trials, asserts only degenerate sentinels (no 0%/100%, days>0,
days<=cap). Runs in <3s.
- TestExpeditionBalance_Phase0_SeedSpread — confirms the RNG seam is
actually wired by showing two distinct seeds produce different
trials. Full byte-for-byte reproducibility under same seed is not
asserted at Phase 0; surpriseRoundNick + pickWanderingMonster
draw from package-global rand (same caveat as class balance).
Spike numbers (calibration baseline, NOT a target): 1% completion,
99% deaths, median 4 days, ~6 encounters. The headline death rate is
loud but expected — Phase 1's full matrix will diagnose whether it's
HP-carryover punishment, bestiary-vs-abstract-monster delta, or kit
ladder mismatch. Phase 2 is where the centerline gets tuned.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>