Design steer from user — "relatively easy but not too easy" — narrowed the
target: lift the embarrassing caster trailers on off-tier cells (a casual
player walking into a slightly too-hard dungeon underleveled) without
pushing the already-saturated in-tier ceiling.
Levers:
- Druid passive: was the only chassis with a purely defensive passive
(5% DR, no offense), and it read it — L1/T1 mean 0.77 (lowest at the
entry tier), L1/T2 0.04. Added a level + WIS-scaled FlatDmgStart burst,
same shape as the Phase-2 Bard/Mage/Warlock pass. Kept the DR; no
DamageBonus rider so high-tier ceilings stay flat.
- Sorcerer passive: burst base 3→5. Sorcerer was second-worst caster
off-tier (L1/T2 0.10 vs Mage 0.27 pre-tune) despite a comparable stat
line; the bump pulls it toward arcane-chassis parity.
Observed lifts:
- Druid L1/T1: 0.77 → 0.86 (+9pp) — chassis now functional at its
intended tier
- L2/T2 cross-class spread: 77pp → 63pp; druid trailer 0.23 → 0.35
- L1/T1 spread: 23pp → 14pp
Off-tier diagnostic: added a focused log to TestClassBalance_Phase1_FullMatrix
that names the trailing class at each off-tier (lvl, tier) cell. Not
asserted — L1 in T2 is *supposed* to be hard, so the diagnostic is for
watching the gap, not the absolute number.
In-tier parity assertion (35pp band on the diagonal) still passes;
TestApplyClassPassives updated for the new druid/sorcerer FlatDmgStart
values; full plugin -short suite clean.
The Phase 1 per-class-mean summary was hiding the truth — most cells are
floor/ceiling-saturated (L10+ pinned at 1.0, L1-4 caster cells at high
tier pinned at 0.0), so means barely budge when you tune passives. Added
a per-(level, tier) cross-class spread diagnostic to the matrix log,
then tuned with the levers from doc §6 in priority order:
1. Class passives (dnd_passives.go) — caster trailers (Bard, Mage,
Warlock, Sorcerer) gained level + casting-stat-scaled FlatDmgStart
bursts so the L1-4 chassis isn't a quarterstaff + one weak spell
against a T2-T3 monster; small DamageBonus riders (Mage/Bard/
Sorcerer/Rogue +5%, Warlock 10→12%) and +1 attack for Bard/Warlock
close the steady-DPS gap. Added clampNonNeg so ability-mod-scaled
additions never go negative on sub-10-stat sheets.
2. Subclass L5 tiers (dnd_subclass_combat.go) — the three Sorcerer L5
picks (Wild/Storm/Draconic) and Warlock Great Old One were defense-
only or near-inert pre-tune; each gained a small bite (DamageBonus
+0.10, or a FlatDmgStart burst for Storm) so the L5 chassis can press
through a T4 monster.
Parity band locked in TestClassBalance_Phase1_FullMatrix: cross-class
spread ≤ 35pp on the in-tier diagonal — (level, tier) cells where the
level is appropriate for the tier (T1: L1-4, T2: L3-7, T3: L5-10, T4:
L7-15, T5: L10-20). Off-tier cells (L1 mage at T3 dungeon etc.) are
still logged but not asserted: those are level-vs-tier mismatches and
casters at L1-4 can't muscle through a T3 monster on a single L1-slot
spell the way martials muscle through with weapon dice. Worst in-tier
cell after tuning: ~26pp at L3/T2. The 35pp band gives ~9pp Monte-Carlo
headroom over the worst signal at 200 trials/cell.
TestApplyClassPassives expectations updated to match the new passives.
Phase 0 spike still green, full plugin suite (-short) clean.
Adds signature class passives for Druid/Bard/Sorcerer/Warlock/Paladin in
dnd_passives.go, each riding an existing CombatModifiers channel so a
playable caster is no longer mechanically empty: Druid Wild Resilience
(damage reduction), Bard Bardic Inspiration (initiative), Sorcerer Innate
Sorcery (CHA-scaled pre-combat burst), Warlock Agonizing Blast (+10%
damage), Paladin Divine Smite (level-scaled burst).
Adds 15 subclasses (3 per caster, registry now 30) using canonical 5e
archetype names, with passive-only L5/7/10/15 effects in
applySubclassPassives — consistent with the majority of the original
fifteen, no new resource pools or active abilities. subclassesForClass is
now non-empty for casters, so the L5 !subclass prompt and sheet display
light up through the existing generic plumbing.
Tests: TestApplyClassPassives extended to all ten classes and the new
channels; TestSubclassRegistry_* updated to expect 30 entries across all
ten classes.