Files
gogobee/gogobee_expedition_difficulty.md
prosolis d0a8505c76 Phase 5a (diag): tier-wide sensitivity sweep on T2/T3/T5 under-band zones
Phase 4-B closed the per-zone outliers but T2/T3/T5 sibling pairs sit
below band as a group (T2 7-13% vs 62-82%; T3 3-14% vs 54-74%; T5
25-57% vs 36-56%). The plan doc's three tier-wide candidates (gear-
tier centerline remap, per-tier elite threshold, ship burn=75) needed
disambiguation before pulling any of them.

TestExpeditionBalance_Phase5A_TierWideSensitivity runs the 6 problem
zones through three one-axis sweeps holding the other two levers at
Phase 3-B best (e=23, d=1, burn=50):

  Axis L  player level    {centerline-2, centerline, centerline+2}
  Axis E  elite threshold {18, 23, 28}
  Axis B  supply burn pct {40, 50, 60}

200 trials/cell × 6 zones × 9 cells = 10.8k trials; runs in 0.31s.
Reuses the harness's traceFightStruct plumbing for elite-vs-standard
fight share alongside comp/death/starve.

Reading (full numbers in the plan doc):

  - Player level is the dominant lever at T2/T3. +2 levels lifts
    completion meaningfully; the elite gate and burn are inert.
  - Elite threshold is already at its sweet spot at e=23 — e=18
    floods elites and collapses every tier; e=28 is flat. Per-tier
    threshold candidate is killed.
  - burn=75 globally is killed. burn=60 alone produces 36%/61%
    starve at T5; the Phase 3-B negative result holds tier-wide.
  - T5 dragons_lair is already in-band at the baseline (60.5%);
    the T5 gap is really an abyss_portal-only gap.
  - The level-bump path has a catch: L7 at T2 (max of design range,
    still gearTier=2) only reaches ~27%, well below the 62-82% band.
    Closing T2 properly likely needs a cross-gearTier-boundary
    centerline (T2 → L9 = gearTier 3) — a design call for Phase 5-B,
    not a knob twist.

Diagnostic-only — no gates. -short skips. Plan doc updated with
numbers and three Phase 5-B candidate moves (cross-bracket centerline
bump recommended; player combat-math retune is the bigger option;
lowering the band target is the fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:36:17 -07:00

515 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Expedition Difficulty Tuning Pass
Started 2026-05-15, after the Phase D audit close-out. Sibling to the
class-balance pass (`gogobee_class_balance.md`) — same Monte-Carlo
philosophy, different target metric.
**Class balance answers** "is my class viable?" (parity between classes
vs. the same monster tier).
**This pass answers** "is the dungeon fair?" (does a tier-appropriate
character complete a tier-N expedition at the expected rate?).
Same combat engine (`simulateCombatWithRNG`), orthogonal knobs.
---
## Method
**Sim harness, not telemetry-first.** A new
`internal/plugin/dnd_expedition_balance.go` will run thousands of
seeded expeditions through the real autopilot / threat / encounter /
combat code and report:
- completion %
- death %
- median days-to-extract
- median threat at extract
- supply curve (% expeditions that hit Rationing / Severe / Starvation)
- combat-encounter count per run
- coin/XP earned
The harness produces these per (zone, player-level) cell; the
regression test asserts the per-tier completion band.
**Tier target band** (steered by [[feedback_difficulty_target]]
"relatively easy but not too easy", and reflecting that expeditions are
multi-day commitments players plan around — losing one should sting but
not feel arbitrary):
| Tier | Target completion % | Acceptable band |
|------|--------------------:|-----------------|
| T1 | 80% | 7090% |
| T2 | 72% | 6282% |
| T3 | 65% | 5575% |
| T4 | 55% | 4565% |
| T5 | 45% | 3555% |
Bands are ±10pp around the centerline. Sim is asserted against the band,
not the centerline (no overfit). Player-level for each cell = "tier
appropriate" per the class-balance kit ladder: T1 → L1-4, T2 → L3-7,
T3 → L5-10, T4 → L7-15, T5 → L10-20 (median level per tier).
**Out of scope:**
- Per-encounter combat parity (covered by `gogobee_class_balance.md`).
- Arena difficulty (separate system, separate balance).
- Live telemetry tables — deferred; sim is the primary truth source for
this pass. We can add counters later if the sim diverges from
observed live numbers.
---
## Phases
Each phase ≈ 1 session, ships green + committed.
### Phase 0 — sim harness spike
Build the minimum harness:
- New file `internal/plugin/dnd_expedition_balance.go` exposing
`simulateExpeditionWithRNG(profile, seed)` → result struct.
- **Critical de-risk: replay the autopilot deterministically.** The
ticker is wall-clock driven (`dnd_expedition_cycle.go:38,56` at
06:00 / 21:00 UTC; `expedition_ambient.go:22` every 3h). Harness
must inject a virtual clock or call the per-phase advance functions
directly (`deliverBriefing`, `deliverRecap`, ambient nudge, harvest
autopilot) in tight loop, bypassing the goroutine ticker. **Settle
the clock-injection seam in Phase 0** — if this is messy, the whole
pass collapses.
- DB-touching layers stubbed/in-memory (same trick as class-balance
Phase 0).
- Sanity test: one cell — e.g. T2 zone at L5 Fighter — runs to
completion or death deterministically, returns sensible numbers,
100 trials in <10s.
**Exit:** `TestExpeditionBalance_Phase0_Spike` green; commit.
### Phase 1 — full matrix measurement
- Build the cell list: every zone × tier-appropriate-level. Per the
exploration map, expeditions span zones tagged T1T5; we measure
every zone at its centerline level.
- 200 trials/cell (same density as class-balance Phase 1).
- Matrix log: per-cell completion%, death%, median days, median
extract-threat, supply-curve histogram.
- Per-tier mean + spread diagnostic across zones (analogue of the
class-balance per-(level,tier) spread diagnostic).
- `TestClassBalance_Phase1_FullMatrix`-style permanent test, but only
gating on harness-broken pathologies for now (0% completion anywhere
at T1, 100% completion at T5).
**Exit:** matrix numbers logged; commit with "Phase 1 baseline" note.
### Phase 2b — wounded-entrant nick clamp (shipped)
`clampSurpriseNick(rawNick, hpCurrent, hpMax)` in
`dnd_expedition_combat.go`: at full HP the raw nick stands; when
wounded the nick is capped at `max(1, hpCurrent/5)` so a single nick
on a low-HP fighter can't shave more than ~20% of remaining HP. The
existing "nick can't KO" guard is preserved.
Motivation came from the post-2a tier-lethality trace
(`TestExpeditionBalance_Phase2_TierLethality`): every tier had a
fingerprint where an over-tier elite left the fighter at 25-50% HP +
retreat, then the next standard's surprise nick (3-7 HP, often
exceeding the fighter's headroom) pre-empted the combat engine
entirely. Capping the wounded-entrant nick separates that cascade
from genuine elite-one-shot deaths so Phase 2c can tune the elites
with a clean read.
Matrix delta vs Phase 2a baseline is small (median encs +0.1-0.2
per cell) — completion% still 0% across every tier — but the
tier-lethality trace stretched substantively: T1 trial 0 went 5 → 8
encs / 5 → 7 days; T3 trial 1 ran 18 → 10 encs but visibly survived
multiple chained interrupts at low HP that pre-2b would have ended
on the nick alone. The remaining deaths are now legible as elite
one-shot fights on fresh entries (Hobgoblin Warchief, Green Hag,
Roper, Young Red Dragon) — that's the Phase 2c roster signal.
**Tuning surface:** the `/5` divisor in `clampSurpriseNick` is the
wounded-fighter lethality knob.
### Phase 2 lever sweep (shipped) — negative result
Before adding Phase 2c, the two surfaced knobs (`retreatThreatBump`,
`clampSurpriseNick` divisor) got swept across a full 3×4 grid
(bump ∈ {2, 5, 10} × divisor ∈ {3, 5, 8, 12}) at 200 trials/cell
across every zone in the matrix. Wired via
`expeditionBalanceProfile.{RetreatThreatBumpOverride,
SurpriseNickDivisorOverride}` threaded into `expeditionHarness`;
the live caller (`runHarvestInterrupt`) is untouched.
Sweep file: `TestExpeditionBalance_Phase2_LeverSweep` in
`expedition_balance_test.go`. `-short` skips.
**Outcome:** every one of the 24,000 trial-cells reports 0.0%
completion / ~100% death. The two knobs are inert on the headline
metric — even the gentlest combo (b=2, d=12) cannot lift any tier
off the floor. Median days-to-end stays at 23 across the grid
with median 2.67.3 encounters before death.
**Read:** confirms the post-2b tier-lethality trace — deaths are
fresh-entry elite one-shots (Warchief @ HP19/20, Hag @ HP41, Roper,
Young Red Dragon), not chained-interrupt cascades. The two levers
target the cascade path that's now mostly resolved; they have no
remaining death-fraction to convert. Phase 2c (roster dilution) is
justified rather than premature.
### Phase 2c — roster gate (shipped)
Promoted one mid-tier alt to `IsElite: true` in every zone whose elite
pool was a single SpawnWeight=1 boss (`internal/plugin/dnd_zone.go`).
The boss stays in the pool; weighted dilution means the InterruptElite
bracket no longer auto-picks the over-tier boss. `pickZoneEnemy` also
excludes the promoted entry from the standard pool, lowering standard
encounter difficulty by removing one mid-difficulty option from
non-elite brackets (acceptable: each zone still has 3-4 standard
entries left). Crypt-Valdris was already dual-elite (wight + flameskull)
and was left alone.
| zone | promoted alt | boss (untouched) |
|--------------------|-----------------------|--------------------|
| goblin_warrens | worg (SW=3) | hobgoblin_warchief |
| forest_shadows | owlbear (SW=4) | green_hag |
| sunken_temple | aboleth_thrall (SW=3) | water_elemental |
| manor_blackspire | vampire_spawn (SW=3) | revenant |
| underforge | salamander (SW=3) + fire_elemental (SW=3) | helmed_horror |
| underdark | drow_elite_warrior (SW=4) | roper |
| feywild_crossing | night_hag (SW=3) | fomorian |
| dragons_lair | dragonborn_cultist (SW=3) | young_red_dragon |
| abyss_portal | hezrou (SW=4) | marilith |
**Phase 1 matrix delta (200 trials/cell, Fighter, tier centerline):**
goblin_warrens 0% → **3.0%** completion (first non-zero T1 reading
since Phase 0); sunken_temple 0% → 0.5%; dragons_lair death% **100% →
60%** with 40% now starve (fighter survives the elite-spawn path long
enough to run out of supplies — different failure mode); underdark
death% 100% → 90% (10% starve); feywild_crossing 100% → 92% death
(8% starve). T3 manor_blackspire / underforge still 100% death — the
remaining death pressure there is the post-2b chained-standard path,
not the elite roll.
Tier-lethality trace cross-check (`Phase2_TierLethality`) shows the
new alt-elites actually spawning and being winnable: underdark trial
1 now opens with `Drow Elite Warrior` (won), reaches the Roper at
turn 6, retreats, then dies on a Hook Horror nick — a substantively
longer arc than the pre-2c fresh-entry Roper one-shot. Dragons_lair
trials similarly show `Dragonborn Cultist` as a winnable elite stop
before the dragon roll lands.
Stat-block tuning of the bosses themselves is off-limits per the
Phase 3 constraint (bestiary is shared across systems).
### Phase 3 — global lever tuning
Tune the knobs that affect every zone uniformly:
- **Daily threat drift** (`dnd_expedition_threat.go:87` — base +3/day).
- **Threat band thresholds** (Quiet/Stirring/Alert/Hostile/Siege —
`dnd_expedition_threat.go:70-82`).
- **Supply burn multipliers** (`dnd_expedition_supplies.go:43-56`
T1×1 → T5×3).
- **Surprise-round damage floor** (`dnd_expedition_combat.go:184`).
- **Encounter bracket thresholds** (`dnd_expedition_combat.go:39-44`
None/Noise/Standard/Elite/Patrol).
- **Camp mods** (`dnd_expedition_night.go:85-92`).
Goal: every tier centerline lands within ±5pp of target. Bands stay
±10pp.
#### Phase 3-A — elite-bracket + drift sweep (shipped)
Wired two harness overrides
(`EliteInterruptThresholdOverride`, `ThreatDriftBaseOverride`) and
swept a 3×3 grid (elite ∈ {17, 19, 23} × drift ∈ {1, 3, 5}) over
the Phase 1 matrix. Test: `TestExpeditionBalance_Phase3_GlobalLeverSweep`.
**Strong partial positive.** The elite-bracket threshold is the
dominant lever for T1T3; threat drift is a small modulator on top.
| (elite, drift) | T1 mean | T2 mean | T3 mean | T4 mean | T5 mean |
|----------------|--------:|--------:|--------:|--------:|--------:|
| 17, 1 (more elites) | 0.5% | 0.0% | 0.0% | 0.0% | 0.0% |
| 19, 3 (live) | 3.0% | 0.2% | 0.0% | 0.0% | 0.0% |
| 23, 1 (rare elites) | **24.0%** | **7.2%** | **1.8%** | 0.0% | 0.0% |
| 23, 3 | 16.5% | 4.0% | 2.0% | 0.0% | 0.0% |
| 23, 5 | 11.8% | 3.2% | 0.2% | 0.0% | 0.0% |
Best cell at e=23/d=1: goblin_warrens 40.5%, sunken_temple 14.5%.
Still well below the T1 70-90% / T2 62-82% target bands — the lever
moves the needle in the right direction but cannot land any tier
on-band alone.
**T4/T5 fingerprint changed but didn't lift.** At e=23 the dragons_lair
death-fraction drops from ~60% → 24% but starvation climbs to 75% —
the fighter now survives elites long enough to run out of supplies.
T4 (underdark/feywild) likewise sees death drop and starvation rise.
Indicates a second lever is needed for the higher tiers: either
standard-fight survivability (surprise-nick floor / standard roster
density) or supply margin.
**Next:** Phase 3-B sweeps surprise-round nick and supply-burn knobs
on top of e=23/d=1 to push T1-T3 toward target band and lift T4-T5
off the floor.
#### Phase 3-B — nick-floor + supply-burn sweep (shipped)
Wired two more harness overrides (`SurpriseNickFloorOverride`,
`SupplyBurnRatePctOverride`) and swept a 3×3 grid (floor ∈ {0, 1, tier
(=live)} × burn% ∈ {50, 75, 100 (=live)}) on top of the Phase 3-A best
cell (e=23, d=1). Test: `TestExpeditionBalance_Phase3B_NickSupplySweep`.
**Strong partial positive for T5 supply margin; nick-floor lever inert.**
Tier-mean completion (Fighter, centerline level, 10 zones × 200 trials):
| (floor, burn%) | T1 mean | T2 | T3 | T4 | T5 |
|----------------|--------:|----:|----:|-----:|-----:|
| 0, 50 | 20.8% | 5.0%| 3.8%| 7.5% | 29.5%|
| 0, 75 | 26.5% | 6.0%| 2.8%| 9.0% | 0.0% |
| 0, 100 | 25.8% | 5.0%| 3.0%| 0.0% | 0.0% |
| 1, 50 | 25.5% | 6.5%| 1.8%| 8.5% | 27.8%|
| 1, 75 | 28.5% | 6.2%| 4.0%|11.5% | 0.0% |
| 1, 100 | 26.8% | 7.5%| 2.5%| 0.0% | 0.0% |
| tier, 50 | 26.8% | 6.2%| 2.5%| 7.8% | 27.5%|
| tier, 75 (live floor) | **29.5%** | **7.8%** | 3.0% | **12.8%** | 0.0% |
| tier,100 (live)| 28.5% | 6.8%| 3.0%| 0.0% | 0.0% |
Three readings:
1. **Supply burn is the T5 unlock.** dragons_lair completes ~55% at
burn=50 (versus 0% at burn=75/100) — the fighter survives elites
but starves en route. burn=75 isn't enough margin; burn=50
converts the starvation-out into a completion.
2. **Supply burn is the T4 modulator.** underdark/feywild step from
0% (burn=100) → 12% (burn=75) → 8% (burn=50). The burn=75 peak is
the sweet spot; halving further leaves the fighter alive *into*
more elite fights, where it dies in combat instead.
3. **Nick-floor lever is inert.** Across all burn levels, dropping
the surprise-round floor from `tier``1``0` moves T1-T3
means by at most ~3pp. The wounded-clamp (Phase 2b,
`clampSurpriseNick`) already eats the chip-damage budget on
repeat entries; the raw floor only matters on the first fresh-HP
swing, which the engine recovers from. **Recommendation: discard
this lever from the live-tuning candidate list.**
**T2-T3 wall persists.** Even at the best (floor=tier, burn=75) cell:
T2 reads 7.8% (target 62-82%) and T3 reads 3.0% (target 54-74%).
forest_shadows T2, manor_blackspire T3, and abyss_portal T5 sit at
~0% across every combo — these are zone-specific outliers, not
addressable by any global lever. **Phase 4 (per-zone outlier pass)
is the next move.**
**Next:** Phase 4 walks the matrix zone-by-zone. The shipped Phase 3
sweep already names the worst offenders (forest_shadows T2,
manor_blackspire T3, abyss_portal T5, crypt_valdris T1); each needs a
diagnostic trace (boss stat-block, roster mix, supply DC) to pick the
right per-zone tool from `Phase 4`'s list. Optional: revisit shipping
the burn=75 global if Phase 4 outlier fixes leave T4 still stuck.
### Phase 4 — per-zone outlier pass
After globals settle, the matrix names outlier zones — tiers where
one zone reads dramatically off-band while sibling zones are fine.
Per-zone tools:
- `SpawnWeight` / `IsElite` flags in zone rosters (`dnd_zone.go:100`).
- Boss stat tuning in zone definitions.
- Resource DCs (affects harvest interrupt roll).
- Per-zone loot table density (downstream economy impact — flag if
significant).
Constraint: do not touch monster stat blocks (those belong to the
bestiary, shared across systems). Tune the roster + zone-level knobs,
not the monsters themselves.
#### Phase 4-A — outlier diagnostic (shipped)
Added a structured per-fight trace hook (`traceFightStruct` +
`harnessFightTrace`) alongside the existing string `traceFight`;
both fire from the same `runHarnessFight` site so the human log and
the structured aggregate stay in sync. Test:
`TestExpeditionBalance_Phase4A_OutlierDiagnostic`. For each of the
four outlier zones it pairs against the healthy sibling at the same
tier (goblin_warrens, sunken_temple, underforge, dragons_lair),
running 200 trials each on the Phase 3-B best cell (e=23, d=1,
burn=50) and reporting per-monster appearances / win-rate / avg HP
loss / kill attribution + day-of-end histogram.
Findings — each outlier had one or two miscategorised entries that
Phase 2c's single-boss-elite dilution sweep had skipped:
- **crypt_valdris**: dual-killer elite pool (Wight 99k, Flameskull
68k); Phase 2c had skipped this zone as "already dual-elite," but
both elites are over-tier for T1.
- **forest_shadows**: standard pool too lethal — Displacer Beast
(38% win, 53k) + Bandit Captain (57% win, 48k).
- **manor_blackspire**: Wraith on standard slot (45hp loss per win,
85k).
- **abyss_portal**: Nalfeshnee mis-classified standard (CR-13, 2.8%
win, 86k) + Vrock standard at 43hp loss per win.
#### Phase 4-B — roster re-classification (shipped)
Per-zone roster tweaks based on Phase 4-A's attribution. No monster
stat-block touches.
- crypt_valdris: dropped flameskull (lives in underforge T3);
promoted specter to IsElite SW=3 as the soft dilutor.
- forest_shadows: promoted Displacer Beast + Bandit Captain to
IsElite; Owlbear SW 4→2 so the 4-elite pool dilutes evenly.
- manor_blackspire: promoted Wraith to IsElite (joins
Spawn+Revenant).
- abyss_portal: promoted Nalfeshnee + Vrock to IsElite (collapses
standard pool to Quasit alone, ≥99% win).
Outlier-vs-sibling completion (Phase 3-B best cell, 200 trials):
| Zone | Before | After | Sibling |
|------|-------:|------:|--------:|
| crypt_valdris T1 | 8.5% | **46.0%** | 45.0% |
| forest_shadows T2 | 0.0% | **7.5%** | 13.0% |
| manor_blackspire T3| 0.5% | **14.5%** | 3.0% |
| abyss_portal T5 | 0.0% | **25.0%** | 57.5% |
Phase 3-B tier mean (b=50, f=tier) before → after:
T1 26.8% → 44.0% (+17pp), T2 6.2% → 10.0% (+4),
T3 2.5% → 8.5% (+6), T4 unchanged, T5 27.5% → 41.0% (+14).
**Exit (partial):** the four off-band outliers are no longer
outliers — every per-zone cell is within the spread of its tier
siblings. The Phase 4 plan's stricter exit ("all per-zone cells
within target band") is **not** met: T2/T3/T5 sibling pairs still
sit below band (T2 7-13% vs 62-82%; T3 3-14% vs 54-74%; T5 25-57%
vs 36-56%). That gap is tier-wide, not zone-specific — both zones at
each problem tier underperform together — so no further per-zone
tool will close it. Phase 5 (or a follow-up tier-wide pass) needs to
take over.
**Next:** Phase 5. Candidate moves are tier-wide: revisit player
gear-tier mapping at T2/T3 (the centerline level vs gear ladder, see
`phase1TierCenterline`), reconsider the elite-bracket threshold
per-tier instead of global, or ship `burn=75` as the new live global
since Phase 4-B already converted T4 to playable at b=50.
### Phase 5 — tier-wide pass (T2/T3/T5 under-band)
Phase 4-B closed the per-zone outliers but left T2/T3/T5 sibling pairs
below the target band as a *tier-wide* gap. The plan doc's three
candidate moves (gear-tier centerline remap, per-tier elite threshold,
ship `burn=75`) needed disambiguation before any of them got pulled —
hence a measure-first sub-phase.
#### Phase 5-A — tier-wide sensitivity sweep (shipped)
`TestExpeditionBalance_Phase5A_TierWideSensitivity` runs the 6 under-
band zones (T2: forest_shadows + sunken_temple; T3:
manor_blackspire + underforge; T5: abyss_portal + dragons_lair) through
three one-axis sweeps with the other two levers held at Phase 3-B best
(e=23, d=1, burn=50): player level ±2 around tier centerline, elite
threshold ∈ {18, 23, 28}, supply burn pct ∈ {40, 50, 60}. 200
trials/cell × 6 zones × 9 cells = 10.8k trials; runs in <1s.
Numbers (200 trials/cell, comp%):
```
T2 (band 62-82%)
L forest=0→6.5→19 sunken=0→13.5→34.5 STRONG slope
E forest=0.5→9→8 sunken=0→14.5→13 saturated @23; 18 collapses
B forest=3.5→7→6.5 sunken=14→15.5→11 inert
T3 (band 54-74%)
L manor=3→10.5→13.5 under=0→4→8.5 clear slope
E manor=0→9.5→9.5 under=0→3→4 saturated @23; 18 collapses
B manor=13→9.5→9.5 under=2.5→4→2.5 inert
T5 (band 36-56%)
L abyss=21.5→28.5→32 dragons=60→60.5→53 gentle; dragons in-band
E abyss=1→28.5→31 dragons=10→60.5→57.5 saturated @23; 18 collapses
B abyss=27.5→28.5→0 dragons=59→60.5→0 burn=60 STARVE CLIFF
```
**Reading:**
- **Player level is the dominant lever at T2/T3.** Every +2 levels
lifts completion meaningfully; the elite gate and supply burn are
inert or actively harmful around the Phase 3-B baseline.
- **Elite threshold is already at its sweet spot at e=23.** Going to
e=18 floods elites and collapses every tier; e=28 is flat. The per-
tier-threshold candidate from Phase 4 close-out is killed: there's
no per-tier value to be found in the explored range.
- **`burn=75` globally is killed.** burn=60 alone produces 36% / 61%
starve at T5; burn=75 would be catastrophic. The Phase 3-B negative
result holds tier-wide. burn must stay ≤50.
- **T5 dragons_lair is already in-band** at the baseline (60.5%).
The T5 tier-wide gap is really an abyss_portal gap (28.5%); the
Phase 4-B re-class lifted it from outlier to "low-but-not-isolated,"
but it still sits below band by itself.
- **The level-bump path has a catch.** L7 at T2 (still inside the
L3-7 design range and gearTier=2) only reaches ~27% mean — still
well below the 62-82% band. Closing T2 to band likely requires a
*cross-gearTier-boundary* centerline (T2 → L9 = gearTier 3) and/or
a player-side combat-math adjustment, not just a within-bracket
centerline shift. That's a design call, not a knob twist.
**Next:** Phase 5-B is a design choice between three concrete moves
that the measurement now backs:
1. **Cross-bracket centerline bump** for T2 (→ L9) and T3 (→ L13),
plus T5 abyss-only level tweak. Lifts player gear bracket into the
next tier; closes the band gap directly but is a player-side power
move that affects the live game (chars at the centerline level
keep their existing gear; centerline only governs the harness's
measurement cell). The change is *in the harness*, but it admits
the live difficulty target was wrong for these tiers — Phase 5-C
would then carry it back to the live gear-acquisition curve.
2. **Player combat-math retune** at T2/T3 (HP scaling, proficiency
curve, attack-bonus floor) — bigger lift, touches the class-balance
harness too, deserves its own pass.
3. **Lower the band target** for T2/T3/T5 to ~30-50% mean and accept
that completion is meant to be hard. This is the "do nothing"
option dressed up as a design decision; surfaces the question of
whether the original 70% T2 target was ever the right number.
Recommend option (1) for Phase 5-B since the harness already measures
it cleanly and the data points at it; option (3) is the fallback if
the cross-bracket numbers still don't close the band.
### Phase 6 — optional MAD / second-order
If post-Phase-3 the bands hold but feel wrong subjectively
(e.g. "median days too short", "no expedition ever hits Siege"), add a
secondary assertion on median-days-to-extract or extract-threat. Phase
3 may also surface a request to expose a difficulty knob to operators
(env var or admin command); scope that here if it comes up.
---
## Open questions
- Should the babysit perk be on or off in the sim? Pick one and
document; both as separate cells doubles matrix size for diminishing
insight. Default proposed: **off**, since it's a discovery-mechanic
buff ([[feedback_npc_buffs_are_secret]]) — players who find it earn
extra margin, not the baseline.
- Pardon proc (33% @ chat 20+): include in sim, but at a fixed
chat-level assumption (chat 15 = no pardon, since most fresh runs
haven't hit the threshold). Decide in Phase 0.
- Sovereign reprieve: skip in sim — it's gated on full set equipped
and is a one-shot per cooldown. End-game-only; not part of the
baseline difficulty.
- Multi-region zones (E4): does the sim need to walk all regions in
one expedition, or pick one region per run? Probably full walk for
realism. Settle in Phase 0.
- DM mood: starts at 50; drifts via combat outcomes. Let it drift
naturally in the sim, do not pin.
---
## Deliverables checklist
- [ ] `internal/plugin/dnd_expedition_balance.go` (sim harness)
- [ ] `TestExpeditionBalance_Phase0_Spike`
- [ ] `TestExpeditionBalance_Phase1_FullMatrix` (becomes the permanent
regression gate after Phase 2 tightens it)
- [ ] Tuning commits per phase
- [ ] One-line update to `MEMORY.md` + project memory pointer