The post-J1 sweep had the casters clustered at 19–22% L12 clear, vs martials at 70–80%. A per-round trace across 240 boss-room fights showed why: autoResolveCombat dispatched !attack only — zero spell_casts, zero mid-fight consumable uses across every caster class. The entire "caster cliff" was the sim measuring a strawman where casters couldn't cast. J2a teaches the sim's autoResolveCombat to mirror a competent prod player: heal at low HP if an inventory consumable is available, otherwise cast the highest-EV damage spell (slot or cantrip), otherwise swing. BuildCharacter now seeds the known-spell list via ensureSpellsForCharacter so the synthetic spellbook is populated. A -trace flag on the cmd attaches the raw CombatEvent stream to the last combat of each run for post-hoc diagnostics. A first re-baseline (n=100, all 10 classes) showed Ranger regressed -35.8pp — the picker was burning L3 slots on lightning_arrow when Ranger's weapon chassis (Hunter's Mark + Extra Attack) was the better play. Added simMartialFirstClass to gate the picker off for Ranger and Paladin (whose default kit is also weapon-first / no damage spells). J2c experimented with widening the picker to control + heal spells; heal-spell preempt cost druid 10pp (slot heals are 10HP vs 40HP consumables) and control-spell scoring at 22 cost warlock 6.6pp. Both reverted. Corpora retained under baseline_j2c*.jsonl for the post-mortem in sim_results/j2b_findings.md. Post-J2 L12 leaderboard (baseline_j2a_v2_all10.jsonl, n=100): fighter 80.0, ranger 80.0, paladin 78.4, rogue 76.8, druid 61.6, mage 53.4, sorcerer 50.6, warlock 48.2, bard 40.4, cleric 39.0. The caster cluster is dissolved; martials are within ±5pp of J1 (sweep noise). Bard/cleric still trail, but it's no longer a sim artifact — their defaultKnownSpells damage rosters cap at L2 and the picker can't pick spells they don't have. That's a prod-level fix, deferred. J3 trace (sim_results/j3_findings.md): T5 dragons_lair walls every solo class at 0% (Infernax 546 HP vs solo player HP 110–175; ~25% boss HP eaten before TPK across all classes). Per the J3 plan menu, this is party-shaped content the engine doesn't yet have parties for. Surface a TwinBee-voiced heads-up in handleDnDExpeditionCmd's start path and a matching tag in !expedition list — players see "raid-shaped — solo runs not yet survivable" before they spend outfitting coin. No combat or class balance changes. Files: cmd/expedition-sim/main.go +trace flag; expedition_sim.go picker + SimCombatSummary.Events + spellbook seed; dnd_expedition_cmd.go raidContentWarning + list tag. All baselines + traces + findings checked in under sim_results/.
8.2 KiB
J2b — Post-J2a all-classes baseline (n=100)
Date: 2026-05-17
Corpus: sim_results/baseline_j2a_v2_all10.jsonl (15,000 rows = 10 classes × 3 levels × 5 zones × n=100). Per-cell tabular summary in sim_results/j2b_all10_summary.txt.
Replaces: sim_results/baseline_j1_all10.jsonl as the post-sim-fix canonical corpus. The J1 numbers measured a strawman where casters never cast (see j2_findings.md).
L12 leaderboard (mean %clr across all 5 zones)
| Class | J1 baseline | J2b | Δ | J1 boss% | J2b boss% | Per-zone J2b %clr (T1 / T2 / T3 / T4 / T5) |
|---|---|---|---|---|---|---|
| fighter | 79.6 | 80.0 | +0.4 | 100.0 | 100.0 | 100 / 100 / 100 / 100 / 0 |
| ranger | 78.6 | 80.0 | +1.4 | 100.0 | 100.0 | 100 / 100 / 100 / 100 / 0 |
| paladin | 72.2 | 78.4 | +6.2 | 99.6 | 99.2 | 100 / 100 / 96 / 96 / 0 |
| rogue | 70.6 | 76.8 | +6.2 | 99.0 | 99.0 | 100 / 100 / 90 / 94 / 0 |
| druid | 38.6 | 61.6 | +23.0 | 88.4 | 88.0 | 100 / 100 / 74 / 34 / 0 |
| mage | 20.0 | 53.4 | +33.4 | 77.2 | 77.6 | 100 / 99 / 49 / 19 / 0 |
| sorcerer | 20.0 | 50.6 | +30.6 | 70.4 | 70.0 | 100 / 98 / 47 / 8 / 0 |
| warlock | 21.4 | 48.2 | +26.8 | 82.2 | 81.6 | 100 / 100 / 34 / 7 / 0 |
| bard | 21.6 | 40.4 | +18.8 | 82.0 | 84.4 | 100 / 100 / 2 / 0 / 0 |
| cleric | 18.6 | 39.0 | +20.4 | 61.6 | 62.4 | 96 / 90 / 9 / 0 / 0 |
Headline
- No martial regression. Fighter/Ranger/Paladin/Rogue all moved within +0.4 to +6.2pp, indistinguishable from sweep variance. The
simMartialFirstClassgate added after the v1 sweep saved Ranger (which had regressed -35.8pp when the picker burned L3 slots onlightning_arrowinstead of letting the weapon-attack + Hunter's Mark + Extra Attack chassis swing). - Caster cliff is dissolved. The pre-fix 19–22% cluster spread out to 39–62%. The picker alone — no balance changes — converted the trailers to mid-band classes.
- Druid is now top-of-band-2 at 61.6%. Wild Resilience's ThornLash on weapon swings plus the new cast path stacks cleanly. Druid is still defensive-first by design; the cast layer turns boss rooms from "swing-and-bleed" into "burn-and-finish".
- T5 dragons_lair still walls everyone (0% clear across all 10 classes). That's the J3 problem; J2 wasn't on the hook for it, and the sim-artifact half of J3's hypothesis 1 (
autoResolveCombatnever casting/consuming) is now closed by J2a.
What's still trailing — bard and cleric
Bard L12 manor 2%, cleric L12 manor 9% — both reach the boss (~50–84%) and die there. Not a sim artifact; this is the spell pool. The v1 picker only scores damage-effect spells, and these two classes have thin damage rosters in defaultKnownSpells:
- Bard damage options:
vicious_mockery(cantrip),shatterL2. Everything else in the bard kit is control / heal / buff, which the picker skips. - Cleric damage options:
sacred_flame(cantrip),guiding_boltL1,spiritual_weaponL2,spirit_guardiansL3,flame_strikeL5. The non-damage options (bless,healing_word,mass_cure_wounds, etc.) are skipped too.
Both classes' identity spells are control (hold_person, hypnotic_pattern, command) and heal (cure_wounds, healing_word, mass_cure_wounds). The picker treats those as zero-value.
Note on cleric underdark: 87% of L12 underdark runs end as tpk with stop ended — that's expedition-side starvation, not boss-fight TPK (only 13% reach boss/elite). Cleric's exploration loop is brittle for unrelated reasons (low STR/CON build, supply burn). Out of J2 scope.
J2 DoD check
Reframed DoD (gogobee_harvest_charges_plan.md §6.J2, updated 2026-05-17):
- J2a — ≥80% of boss fights produce a cast or consumable. MET (100% cast rate in the verification sweep, j2a_findings.md).
- J2b — caster L12 manor mean clear ≥ druid's pre-J2a 39%. Mage 49 ✓, sorcerer 47 ✓, warlock 34 ✗ (close), bard 2 ✗, cleric 9 ✗. 3 of 5 pass.
- No martial leader regresses >10pp. MET (all +0.4 to +6.2pp).
Two trailers still under the floor (bard, cleric). They're now bottlenecked on spell-pool richness for the v1 damage-only picker, not on the sim itself. The cleanest next move:
J2c — tried, reverted (2026-05-17)
Implemented two widening levers, ran two n=100 sweeps, and reverted both:
- Heal-spell preempt (cure_wounds / healing_word / mass_cure_wounds preferred over heal consumable at low HP). Regressed druid -10pp: slot heals at L12 heal ~10HP vs 40HP from a tier-4 Spirit Tonic, and the picker burned slots druid needed for damage spells.
- Control-spell scoring at 22 (denied-enemy-turn proxy). Regressed warlock -6.6pp — vampiric_touch L3 (10.5 expected over multiple rounds + heal) was outscored by hypnotic_pattern L3 (single denied turn). Dropping the score to 5 brought net deltas back to ±2pp but didn't lift bard or cleric, because the level-first sort prevents low-score control spells from ever winning over a higher-level damage option (which the trailer classes don't have at higher slots).
Corpora retained for reference: sim_results/baseline_j2c_all10.jsonl (heal-preempt + control@22) and sim_results/baseline_j2c_v2_all10.jsonl (control@5 only). The picker reverted to the J2b shape (damage-effect only, level-first sort, no heal-spell preempt).
Real lever for bard/cleric trailing. Their defaultKnownSpells damage rosters cap at L2 (bard: shatter; cleric: spiritual_weapon's 1d8). The picker can't pick higher-damage spells they don't have. Lifting these classes is therefore a prod-level change to defaultKnownSpells (add a damage option at L3+ for bard, a damage option at L4+ for cleric) — not a sim-picker change. Out of scope for J2; revisit alongside class identity / Open5e integration work.
(original) Recommended J2c (now superseded by the section above)
Widen the picker to score control + heal spells.
EffectControl(e.g.hold_person,hypnotic_pattern,hideous_laughter,command) should score as the enemy damage it prevents over its expected effect duration — i.e. each prevented enemy turn ≈ one round's worth of incoming damage. For a stunner that lands and lasts 1 round on a 30-dmg/round boss, that's a 30-equivalent. Better than most damage spells.EffectSpellHealshould compete with the consume-on-low-HP branch — if the cleric hasmass_cure_woundsready, it should fire at the low-HP threshold instead of aSpirit Tonic.EffectBuffSelf(e.g.bless,shield_of_faith) lifts to-hit chance / AC. Score in opening round of a fight only; subsequent rounds it's a waste.
This is one focused change in simPickSpell + simPickCombatAction. Expected impact: bard pulls into the 30–40% manor band (control spells are the bard identity); cleric pulls similarly. Martial leaders unchanged. Whether it's needed depends on operator: J2's reframed DoD allows shipping J2 with bard/cleric trailing if the prod target is "five classes mid-band, two below". Per feedback_difficulty_target, the explicit ask is to lift trailers — that argues for landing J2c.
Files touched this phase
internal/plugin/expedition_sim.go:SimCombatSummary.Events,simIncludeTrace+SetSimIncludeTrace(J2 diagnostic).simPickCombatAction,simPickSpell,simMartialFirstClass,simHealHPThresholdPct(J2a picker + post-baseline fix).BuildCharacternow seedsensureSpellsForCharacter.autoResolveCombatdispatches via the picker.
cmd/expedition-sim/main.go:-traceflag.- Docs:
sim_results/j2_findings.md,sim_results/j2a_findings.md,sim_results/j2b_findings.md,sim_results/j2b_all10_summary.txt, plan §6.J2.
No prod combat / class / spell paths changed. Everything is in the sim harness.