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.
Generalizes the Phase 0 spike harness to the full build matrix the
class-balance doc plans for. No tuning yet — just measurement.
- classBalanceProfile gains Subclass; buildHarnessCharacter sets it on
the synthetic DnDCharacter; buildHarnessPlayer now calls
applySubclassPassives after class+race passives, matching live order
(combat_bridge.go, combat_session_build.go). Subclass="" is a no-op,
so L1–L4 pre-unlock rows are unaffected.
- buildPhase1Profiles yields 190 rows: 10 classes × 4 pre-subclass
levels (L1–L4) + 10 classes × 3 subclasses × 5 post-unlock checkpoints
(L5/7/10/15/20). Order is registry order so output reads like the
design doc / !class help.
- TestClassBalance_Phase1_FullMatrix runs the matrix at 200 trials/cell
(~5.5s) and logs every cell plus a per-class tier-mean summary with
min/max range. Only harness-broken pathologies fail the test (0% at
T1 anywhere, or 100% at T5 for an L1 build); per-tier parity bands
land in Phase 2 once we have data to calibrate the tolerance.
Phase-2 baseline from this run: at T4 the cross-class spread of mean
win rate runs Bard 0.62 → Fighter 0.80 (~18pp); at T5 0.48 → 0.64
(~16pp); casters trail martials at the post-unlock tier (T3) by ~20pp.
Phase 0 test (TestClassBalance_Phase0_FighterVsMage) still green with
identical numbers — the additional applySubclassPassives call is a
no-op for Subclass=="".
Phased plan for the class-balance pass. Unlike the race pass, classes
are *measured* not modeled: combat is one-shot auto-resolved through a
seedable simulateCombatWithRNG, so the harness runs Monte Carlo over the
real engine and reads win rates directly.
Scopes the full matrix (10 classes × 30 subclasses × level checkpoints ×
monster tiers), the two hard policies to de-risk in Phase 0 (equipment
loadout, spell selection), the per-tier win-rate parity rule, and the
tuning levers. Phase 0 spike in progress.