fix(rules): tick bleed and auto-fail stabilization for a dying creature
PF1e (CRB, Stable Characters and Recovery): characters taking continuous damage such as bleed automatically fail all Constitution checks made to stabilize, losing 1 hp per round in addition to the continuous damage. _process_dying never applied bleed (only _turn_gate, reached by active creatures did), so a bleeding dying creature lost 0 hp per round. It now ticks bleed_damage at the start of the dying turn and auto-fails the check (no d20 consumed), then evaluates death. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -170,6 +170,48 @@ def test_dying_bleeds_to_death_threshold() -> None:
|
||||
assert s.hp == -10
|
||||
|
||||
|
||||
def test_dying_with_bleed_ticks_damage_and_auto_fails_stabilization() -> None:
|
||||
# PF1e (CRB, Stable Characters and Recovery): a creature taking continuous
|
||||
# damage (bleed) automatically fails the stabilization check and loses 1 hp
|
||||
# per round IN ADDITION to the bleed damage. No d20 is consumed (an empty
|
||||
# queue would raise), and it dies faster than the 1-hp-per-round baseline.
|
||||
eng, s = _solo(con_score=8) # death threshold -8
|
||||
s.hp = -3
|
||||
s.bleed_damage = 2
|
||||
eng._current_round = 2
|
||||
eng._rng = ScriptedRng([]) # no roll: the check is auto-failed
|
||||
eng._process_dying(s)
|
||||
# bleed 2 (hp -3 -> -5), then auto-fail loses 1 more (hp -5 -> -6).
|
||||
assert s.hp == -6
|
||||
assert s.stabilized_hp is None
|
||||
lines = "\n".join(eng._transcript)
|
||||
assert "bleed 2 (hp -3->-5)" in lines
|
||||
assert "continuous damage auto-fails the Constitution check" in lines
|
||||
assert "loses 1 hp (hp -5 -> -6)" in lines
|
||||
|
||||
|
||||
def test_dying_with_bleed_reaches_death_faster_than_without() -> None:
|
||||
# Same start; the bleeding creature hits -CON in fewer rounds than the
|
||||
# non-bleeding one (which only loses 1 hp per failed check).
|
||||
eng_b, bleed = _solo(con_score=8) # death threshold -8
|
||||
bleed.hp = -3
|
||||
bleed.bleed_damage = 2
|
||||
eng_b._rng = ScriptedRng([]) # auto-fail, no roll
|
||||
eng_b._process_dying(bleed) # -3 -2 bleed -1 = -6
|
||||
assert bleed.hp == -6
|
||||
assert not bleed.dead
|
||||
eng_b._process_dying(bleed) # -6 -2 bleed -1 = -9 <= -8 -> dead
|
||||
assert bleed.dead
|
||||
|
||||
eng_n, plain = _solo(con_score=8)
|
||||
plain.hp = -3
|
||||
eng_n._rng = ScriptedRng([1, 1]) # two failed CON checks: -3 -> -4 -> -5
|
||||
eng_n._process_dying(plain)
|
||||
eng_n._process_dying(plain)
|
||||
assert plain.hp == -5 # still alive after two rounds
|
||||
assert not plain.dead
|
||||
|
||||
|
||||
def test_dying_tick_runs_on_initiative_slot_during_round() -> None:
|
||||
# m1 is downed before the battle starts; both sides still have active
|
||||
# members so the battle continues and m1's slot processes the dying check.
|
||||
|
||||
Reference in New Issue
Block a user