mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-18 01:42:42 +00:00
adventure: route round-end concentration kill through enemyDown (P8)
The T6 Valdris phylactery rebirth (and the pre-existing survive_at_1 one-shot) live in enemyDown, on the premise that it is the single death chokepoint. It isn't: the round-end concentration pulse ended the fight on a raw enemyHP<=0 read, so a cleric's Spirit Guardians landing the lethal blow robbed a revive-armed boss of its rebirth — exactly the cleric-party arm P8 is tuned around. Route that win-check through enemyDown so the boss gets its chance to stand back up. Regression test covers both the armed (revives) and charge-less (still wins) paths. Claude-Session: https://claude.ai/code/session_0156WqjgsbmSY2U8eQ3Kkb1s
This commit is contained in:
@@ -842,7 +842,12 @@ func (te *turnEngine) stepRoundEnd() {
|
|||||||
Round: st.round, Phase: CombatPhaseRoundEnd, Actor: "player", Action: "concentration_tick",
|
Round: st.round, Phase: CombatPhaseRoundEnd, Actor: "player", Action: "concentration_tick",
|
||||||
Damage: st.concentrationDmg, PlayerHP: st.playerHP, EnemyHP: st.enemyHP, Seat: i,
|
Damage: st.concentrationDmg, PlayerHP: st.playerHP, EnemyHP: st.enemyHP, Seat: i,
|
||||||
})
|
})
|
||||||
if st.enemyHP <= 0 {
|
// Route the kill through enemyDown, not a raw HP read: a boss that cheats
|
||||||
|
// death (survive_at_1) or holds a phylactery rebirth (T6 Valdris) must get
|
||||||
|
// that chance even when the lethal blow is a lingering concentration pulse.
|
||||||
|
// enemyDown restores its HP and returns false, so the next seat's pulse (or
|
||||||
|
// the following round) resolves against the revived pool.
|
||||||
|
if enemyDown(st, CombatPhaseRoundEnd) {
|
||||||
te.finish(CombatStatusWon)
|
te.finish(CombatStatusWon)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,3 +275,57 @@ func TestTurnEngine_CommitPersistsSeatZeroNotTheCursor(t *testing.T) {
|
|||||||
t.Error("seat 1's consumed Lucky reroll leaked onto the session row")
|
t.Error("seat 1's consumed Lucky reroll leaked onto the session row")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A lingering concentration pulse that lands the killing blow must still give a
|
||||||
|
// revive-armed boss (survive_at_1 / T6 Valdris's phylactery rebirth) its chance
|
||||||
|
// to stand back up — the round-end tick routes the kill through enemyDown, not a
|
||||||
|
// raw enemyHP<=0 read. Regression for the concentration-bypass gap found in the
|
||||||
|
// P8 Layer-2 review.
|
||||||
|
func TestTurnEngine_ConcentrationKillHonorsRebirth(t *testing.T) {
|
||||||
|
// A charged rebirth: the pulse drops the enemy, a charge spends, it revives.
|
||||||
|
sess := turnSession(CombatPhaseRoundEnd, 500, 30)
|
||||||
|
p := basePlayer()
|
||||||
|
e := baseEnemy()
|
||||||
|
te := resumeTurnEngine(sess, []*Combatant{&p}, &e, combatSessionStepRNG(sess, enemySeat))
|
||||||
|
te.st.concentrationDmg = 100 // lethal against 30 HP
|
||||||
|
te.st.enemyReviveCharges = 1
|
||||||
|
te.st.enemyReviveHP = 40
|
||||||
|
if _, err := te.step(PlayerAction{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
te.commit()
|
||||||
|
|
||||||
|
if !sess.IsActive() {
|
||||||
|
t.Fatalf("a concentration kill ended the fight (%q) while a rebirth charge was armed", sess.Status)
|
||||||
|
}
|
||||||
|
if sess.EnemyHP != 40 {
|
||||||
|
t.Errorf("revived EnemyHP = %d, want the 40-HP revive pool", sess.EnemyHP)
|
||||||
|
}
|
||||||
|
if sess.Statuses.EnemyReviveCharges != 0 {
|
||||||
|
t.Errorf("post-revive charges = %d, want 0 (one spent)", sess.Statuses.EnemyReviveCharges)
|
||||||
|
}
|
||||||
|
rebirths := 0
|
||||||
|
for _, ev := range sess.TurnLog {
|
||||||
|
if ev.Action == "phylactery_rebirth" {
|
||||||
|
rebirths++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rebirths != 1 {
|
||||||
|
t.Errorf("phylactery_rebirth events = %d, want 1", rebirths)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With no charge left, the same pulse ends the fight cleanly (the win path
|
||||||
|
// is not broken by the enemyDown routing).
|
||||||
|
mortal := turnSession(CombatPhaseRoundEnd, 500, 30)
|
||||||
|
p2 := basePlayer()
|
||||||
|
e2 := baseEnemy()
|
||||||
|
te2 := resumeTurnEngine(mortal, []*Combatant{&p2}, &e2, combatSessionStepRNG(mortal, enemySeat))
|
||||||
|
te2.st.concentrationDmg = 100
|
||||||
|
if _, err := te2.step(PlayerAction{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
te2.commit()
|
||||||
|
if mortal.Status != CombatStatusWon {
|
||||||
|
t.Errorf("charge-less concentration kill status = %q, want %q", mortal.Status, CombatStatusWon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user