diff --git a/src/pf1e_simulator/combat.py b/src/pf1e_simulator/combat.py index bc19c69..4f8a513 100644 --- a/src/pf1e_simulator/combat.py +++ b/src/pf1e_simulator/combat.py @@ -5652,7 +5652,9 @@ class CombatEngine: if affected_state.hp < 0: affected_state.stabilized_hp = affected_state.hp actual = affected_state.hp - old_hp - self._stats[state.combatant.id].damage_dealt += actual + # Healing is NOT damage dealt: the attrition counters track damage only + # (README "degats infligés/subis"), consistent with spell healing + # (HealEffect), which is counted nowhere. self._stop_bleeding(affected_state) self._log( f"round {self._current_round} {state.combatant.id}: " diff --git a/tests/test_class_abilities.py b/tests/test_class_abilities.py index c92966e..2174984 100644 --- a/tests/test_class_abilities.py +++ b/tests/test_class_abilities.py @@ -202,6 +202,43 @@ def test_oni_channel_heal_heals_living_enemies_in_range() -> None: assert enemy[0].hp == min(2 + (1 + 5), 6) # healed, capped at hp_max +def test_oni_channel_heal_is_not_counted_as_damage_dealt() -> None: + """Attrition counts damage only (README "dégâts infligés/subis"): channel + healing restores hp but must leave damage_dealt at 0, while the harm mode + of the same feature still records its damage.""" + oni = _combatant("oni", resources={"channel_energy": 6}, hp_max=30) + goblin = _combatant("goblin", hp_max=6) + engine, me, enemy = _engine(oni, [goblin], rng_queue=[5]) + enemy[0].hp = 2 + + engine._execute( + me, + Action(kind="channel_energy", target_id=me.combatant.id, channel_mode="heal"), + ) + + assert enemy[0].hp == 6 # the heal itself still lands + assert engine._stats["oni"].damage_dealt == 0 + assert engine._stats["oni"].damage_taken == 0 + assert engine._stats["goblin"].damage_taken == 0 + + +def test_oni_channel_harm_is_counted_as_damage_dealt() -> None: + """The damage mode of the same feature still records damage_dealt and + damage_taken (a failed Will save takes full 2d6).""" + oni = _combatant("oni", resources={"channel_energy": 6}, hp_max=30) + lich = _combatant("lich", hp_max=100, creature_type="undead") + engine, me, enemy = _engine(oni, [lich], rng_queue=[11, 5]) + + engine._execute( + me, Action(kind="channel_energy", target_id=lich.id, channel_mode="harm") + ) + + dealt = 1 + 11 # 2d6 scripted, save failed + assert enemy[0].hp == 100 - dealt + assert engine._stats["oni"].damage_dealt == dealt + assert engine._stats["lich"].damage_taken == dealt + + def test_oni_emits_channel_harm_only_for_grouped_undead() -> None: """Channel harm is emitted only when at least 2 undead enemies are grouped at 30ft — living enemies are never harmed by positive energy."""