Lift caster trailers: concentration re-tick + Josie caster-aid bootstraps

Diagnosed a cleric "death loop" (L14 dying at T2/T3 bosses while
over-levelled): the boss isn't overtuned — caster sustained DPS is
under-delivered, compounded by a fragile healer build.

Engine fix — concentration AOE re-tick:
- Concentration damage spells (spirit_guardians, heat_metal, spike_growth,
  call_lightning, flaming_sphere) now tick the enemy every round at
  round_end instead of resolving as a one-shot, via a new
  CombatStatuses.ConcentrationDmg armed on cast and round-tripped through
  the turn engine. Closes the long-tracked turn-engine concentration gap;
  the burst still lands the casting round, then the aura lingers.
- Sim picker skips re-casting an already-active aura (models competent play
  and prevents a burst+aura double-dip). Re-baseline (n=30 sweep + n=100
  confirm): bard +47pp T3 (heat_metal), druid +3-7, cleric/mage flat,
  fighter unchanged — no regressions.

Player-data bootstraps (idempotent, run once on Init):
- bootstrapCasterSpellBackfill: ensureSpellsForCharacter only seeds an empty
  book, so defaults added after a character's roll never reach it. Backfill
  missing defaults into known+prepared for existing casters (gives the
  affected cleric inflict_wounds + a working healing_word, since her
  healing_word_spell is a dead alias).
- bootstrapGrantStarterPet: one-off L10 pet for an endgame player who never
  got the morning arrival roll; adds per-round proc damage + deflect.
- TestScenario_JosieCasterAid verifies both against a copy of the live DB,
  incl. idempotency.

Also fix a pre-existing wall-clock flake in
TestFireBriefings_EventAnchoredActivePlayerDelivers (start_date defaulted to
real now, filtering the row out when the suite runs after 06:00 UTC).
This commit is contained in:
prosolis
2026-06-18 06:34:16 -07:00
parent f4a39b46e9
commit cbfca525f5
11 changed files with 733 additions and 6 deletions

View File

@@ -239,6 +239,9 @@ func renderEvent(e CombatEvent, playerName, enemyName string, result CombatResul
case "spirit_weapon_strike":
return fmt.Sprintf(pickRand(narrativeSpiritWeapon), e.Damage)
case "concentration_tick":
return fmt.Sprintf(pickRand(narrativeConcentrationTick), e.Damage)
case "pet_deflect":
return pickRand(narrativePetDeflect)
@@ -536,6 +539,13 @@ var narrativeSpiritWeapon = []string{
"✨ The spectral blade flickers, then bites. %d damage. It does not tire. It does not blink.",
}
var narrativeConcentrationTick = []string{
"🌀 The lingering aura grinds the enemy down — %d damage. Still humming. Still hungry.",
"🌀 Your spell hasn't let go: the spirits sweep through again for %d damage.",
"🌀 The radiant field pulses once more — %d damage. Concentration holds.",
"🌀 The enemy steps wrong and the standing magic answers, %d damage. It does not move on.",
}
var narrativePetDeflect = []string{
"🐾 Your pet intercepts the blow. Damage halved. Your pet is now your best piece of equipment.",
"🐾 Your pet pushes you aside at the last second. Impact reduced. You did not ask to be pushed. Results speak for themselves.",