mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
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:
@@ -263,6 +263,74 @@ func TestTurnEngine_PoisonTickCanBeLethal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTurnEngine_ConcentrationTickAtRoundEnd(t *testing.T) {
|
||||
sess := turnSession(CombatPhaseRoundEnd, 50, 80)
|
||||
sess.Statuses = CombatStatuses{ConcentrationDmg: 12}
|
||||
player, enemy := basePlayer(), baseEnemy()
|
||||
|
||||
events, err := stepEngine(sess, &player, &enemy, PlayerAction{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sess.EnemyHP != 68 {
|
||||
t.Errorf("enemy_hp = %d, want 68 (80 - 12 aura)", sess.EnemyHP)
|
||||
}
|
||||
if sess.Statuses.ConcentrationDmg != 12 {
|
||||
t.Errorf("concentration_dmg = %d, want 12 (aura persists)", sess.Statuses.ConcentrationDmg)
|
||||
}
|
||||
if len(events) != 1 || events[0].Action != "concentration_tick" || events[0].Damage != 12 {
|
||||
t.Errorf("expected one concentration_tick event, got %+v", events)
|
||||
}
|
||||
if sess.Round != 2 || sess.Phase != CombatPhasePlayerTurn {
|
||||
t.Errorf("round=%d phase=%q, want 2/player_turn", sess.Round, sess.Phase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTurnEngine_ConcentrationTickCanBeLethal(t *testing.T) {
|
||||
sess := turnSession(CombatPhaseRoundEnd, 50, 9)
|
||||
sess.Statuses = CombatStatuses{ConcentrationDmg: 12}
|
||||
player, enemy := basePlayer(), baseEnemy()
|
||||
|
||||
if _, err := stepEngine(sess, &player, &enemy, PlayerAction{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sess.Status != CombatStatusWon || sess.Phase != CombatPhaseOver {
|
||||
t.Errorf("status=%q phase=%q, want won/over (aura dropped enemy)", sess.Status, sess.Phase)
|
||||
}
|
||||
if sess.EnemyHP != 0 {
|
||||
t.Errorf("enemy_hp = %d, want 0", sess.EnemyHP)
|
||||
}
|
||||
}
|
||||
|
||||
// A concentration damage cast lands its burst this round AND arms the aura so
|
||||
// it re-ticks at every subsequent round_end.
|
||||
func TestTurnEngine_ConcentrationCastArmsAura(t *testing.T) {
|
||||
sess := turnSession(CombatPhasePlayerTurn, 50, 200)
|
||||
player, enemy := basePlayer(), baseEnemy()
|
||||
|
||||
eff := &turnActionEffect{
|
||||
Label: "Spirit Guardians", Action: "spell_cast",
|
||||
EnemyDamage: 15, ConcentrationDmg: 15,
|
||||
}
|
||||
if _, err := stepEngine(sess, &player, &enemy, PlayerAction{Kind: ActionCast, Effect: eff}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sess.EnemyHP != 185 {
|
||||
t.Errorf("enemy_hp = %d, want 185 (200 - 15 burst)", sess.EnemyHP)
|
||||
}
|
||||
if sess.Statuses.ConcentrationDmg != 15 {
|
||||
t.Fatalf("concentration_dmg = %d, want 15 (aura armed)", sess.Statuses.ConcentrationDmg)
|
||||
}
|
||||
// Drive to round_end and confirm the aura bites again.
|
||||
sess.Phase = CombatPhaseRoundEnd
|
||||
if _, err := stepEngine(sess, &player, &enemy, PlayerAction{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sess.EnemyHP != 170 {
|
||||
t.Errorf("enemy_hp = %d, want 170 (185 - 15 aura tick)", sess.EnemyHP)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTurnEngine_Flee(t *testing.T) {
|
||||
sess := turnSession(CombatPhasePlayerTurn, 50, 50)
|
||||
player, enemy := basePlayer(), baseEnemy()
|
||||
|
||||
Reference in New Issue
Block a user