fix(stats): non-attack damage now lands in damage_taken - bleed ticks, recurring spell damage (Acid Arrow) and cure-spells-damage-undead (SRD Cure Light Wounds negative-energy hit, attributed to the caster's damage_dealt) were invisible to attrition while attacks were counted; transcripts and RNG untouched

This commit is contained in:
2026-09-19 17:02:21 +02:00
parent 7fefce4653
commit 0ad627c97c
4 changed files with 72 additions and 1 deletions
+18
View File
@@ -89,6 +89,24 @@ def test_bleed_to_death_ends_battle() -> None:
assert any("a: bleed 5 (hp -8->-13)" in line for line in eng._transcript)
def test_bleed_tick_records_damage_taken() -> None:
"""Attrition (README "dégâts subis"): bleed is damage - the tick lands in
damage_taken with no attributed dealer (no attack source)."""
eng = _engine(
[_combatant("a"), _combatant("b")],
[(0, 0), (0, 1)],
["players", "monsters"],
[],
)
a, _b = eng._states
a.bleed_damage = 2
eng._apply_bleed_damage(a)
assert a.hp == 98
assert eng._stats["a"].damage_taken == 2
assert eng._stats["a"].damage_dealt == 0
assert eng._stats["b"].damage_taken == 0
def test_heal_spell_clears_bleed() -> None:
eng = _engine(
[_combatant("a"), _combatant("b")],
+24
View File
@@ -78,3 +78,27 @@ class TestCureDamagesUndead:
engine._cast_spell(c, t, "Cure Light Wounds")
assert t.hp == 18
assert any("+13 HP" in line for line in engine._transcript)
class TestCureUndeadAttrition:
"""Given: a cure spell delivered to an undead
When: the negative-energy damage lands
Then: it counts as damage in attrition (dealer + taker), while healing a
living target records no damage either way."""
def test_undead_damage_recorded_for_both_sides(self) -> None:
engine, c, t = _make_engine(
_caster(), _undead(), [1, 8], target_pos=(1, 0), target_side="players"
)
engine._cast_spell(c, t, "Cure Light Wounds")
assert engine._stats["skeleton"].damage_taken == 13
assert engine._stats["caster"].damage_dealt == 13
def test_living_heal_records_no_damage(self) -> None:
engine, c, t = _make_engine(
_caster(), _make_target(), [1, 8], target_pos=(1, 0), target_side="players"
)
t.hp = 5
engine._cast_spell(c, t, "Cure Light Wounds")
assert engine._stats["caster"].damage_dealt == 0
assert engine._stats["target"].damage_taken == 0
+20
View File
@@ -244,3 +244,23 @@ class TestRecurringSnapshot:
assert entry["recurring_damage"] == [
{"formula": "2d4", "types": ("acid",), "rounds_left": 2}
]
class TestRecurringTicksRecordDamageTaken:
"""Given: a target burning from Acid Arrow
When: each recurring tick lands
Then: attrition's damage_taken grows by the tick (README "dégâts subis");
the initial spell hit was already counted at cast time."""
def test_each_tick_counts_as_damage_taken(self) -> None:
caster = _caster(level=3)
victim = _victim(hp_max=200)
# attack (15) + initial 2d4 (3+3) + tick1 2d4 (2+2) + tick2 2d4 (4+4)
engine, c, v = _engine(caster, victim, [15, 3, 3, 2, 2, 4, 4])
engine._cast_spell(c, v, "Acid Arrow")
assert engine._stats["victim"].damage_taken == 6
assert engine._stats["caster"].damage_dealt == 6
engine._apply_recurring_damage(v)
assert engine._stats["victim"].damage_taken == 10
engine._apply_recurring_damage(v)
assert engine._stats["victim"].damage_taken == 18