Review follow-up N: Misty's round-end procs fire in turn-based combat

DerivePlayerStats builds MistyHealProc / CrowdRevengeProc onto every turn-based
combatant, but stepRoundEnd had no counterpart to endOfRoundForSeat, so neither
was ever read. The buff (Misty's heal) was silently lost. The debuff (her
crowd's revenge) was an exploit: a player who declined Misty escaped it entirely
by fighting with !attack instead of letting the room auto-resolve -- no
discovery required, just press the button.

Hoisted both procs out of endOfRoundForSeat into shared helpers
(mistyCrowdRevenge, mistyHeal) and a seatEndOfRound hook that runs the pair in
the auto-resolve order. endOfRoundForSeat now calls the helpers; stepRoundEnd
calls seatEndOfRound per seat, after the poison tick so a heal can answer the
round's damage. A one-sided debuff-only hook would have been a second parallel
sibling of the kind deferred item D warns about, so the heal ships with it -- a
player-favourable discovery mechanic, consistent with the lift-trailers stance.

Both helpers short-circuit before st.randFloat() when their proc is unarmed, so
a character with no Misty history draws no dice: the sim corpus and
combat_characterization.golden do not move.

seatCombatResult now reads MistyHealed back off the misty_heal event (as
combat_pet_save always has), so combat_misty_clutch is reachable turn-based.
combat_sniper_kill stays unreachable -- Arina's proc is a pre-combat one-shot
with no round-end seam.

renderAllySeatEvent renders crowd_revenge as unattributed damage: an ally sees
the hit but not Misty's name, keeping the grudge the owner's own discovery.

Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
prosolis
2026-07-10 08:44:25 -07:00
parent c34e740008
commit 91eeee0826
5 changed files with 296 additions and 24 deletions

View File

@@ -979,6 +979,12 @@ func renderAllySeatEvent(e CombatEvent, name, enemyName string) string {
return down(fmt.Sprintf("☠️ %s takes %d from poison.", who, e.Damage))
case "environmental":
return down(fmt.Sprintf("%s takes %d from the room.", who, e.Damage))
case "crowd_revenge":
// Deliberately unattributed. An ally sees the damage land — silence
// would read as HP vanishing — but Misty's grudge is the owner's own
// discovery, and naming her here would spoil it for the whole party.
// Same reason misty_heal below reads as a plain recovery.
return down(fmt.Sprintf("%s takes %d.", who, e.Damage))
case "flat_damage":
return fmt.Sprintf("%s deals %d.", who, e.Damage)
case "heal_item", "misty_heal":