Files
gogobee/sim_results/j2b_findings.md
prosolis f2c2d774d4 J2: sim picker casts+consumes; T5 raid-content warning
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/.
2026-05-17 15:43:41 -07:00

8.2 KiB
Raw Blame History

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

  1. No martial regression. Fighter/Ranger/Paladin/Rogue all moved within +0.4 to +6.2pp, indistinguishable from sweep variance. The simMartialFirstClass gate added after the v1 sweep saved Ranger (which had regressed -35.8pp when the picker burned L3 slots on lightning_arrow instead of letting the weapon-attack + Hunter's Mark + Extra Attack chassis swing).
  2. Caster cliff is dissolved. The pre-fix 1922% cluster spread out to 3962%. The picker alone — no balance changes — converted the trailers to mid-band classes.
  3. 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".
  4. 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 (autoResolveCombat never 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 (~5084%) 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), shatter L2. Everything else in the bard kit is control / heal / buff, which the picker skips.
  • Cleric damage options: sacred_flame (cantrip), guiding_bolt L1, spiritual_weapon L2, spirit_guardians L3, flame_strike L5. 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:

  1. 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.
  2. 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.

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.
  • EffectSpellHeal should compete with the consume-on-low-HP branch — if the cleric has mass_cure_wounds ready, it should fire at the low-HP threshold instead of a Spirit 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 3040% 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).
    • BuildCharacter now seeds ensureSpellsForCharacter.
    • autoResolveCombat dispatches via the picker.
  • cmd/expedition-sim/main.go: -trace flag.
  • 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.