Applying /code-review high findings on the review-follow-up stack:
- expeditionCmdStart: the resumable-extraction guard swallowed a partySize
error and fell open, starting a new expedition on top of a still-seated
party — the exact orphaning it exists to prevent. Now checks
extractionLapsed first (no DB call on the reap path) and treats a
roster-read error as "assume occupied → refuse".
- Lapsed reap on !expedition start silently unseated members. Extracted a
shared reapLapsedExtraction helper (reap + notify the roster) and routed
both the hourly sweeper and the start-path reap through it.
- stepRoundEnd: moved Misty's crowd/heal seat-loop after the concentration
tick so a caster whose lingering aura would kill the enemy that round wins
before the end-of-round crowd swing, honoring the concentration block's
"a lethal pulse settles the fight" intent.
- Promoted the misty_heal event-log scan to a shared hasAction helper.
- Renamed the new sweep test off the dnd_ prefix.
Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
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