fix(rules): stop damage reduction from eating energy damage
PF1e (CRB Damage Reduction): "Damage reduction does not negate ... energy damage dealt along with an attack." _resolve_hit_damage now splits the post-crit total and applies DR only to the non-energy share (physical dice + flat bonuses + precision dice); energy components are reduced solely by the per-component energy resistance/immunity step. DR-bypass semantics (multi-component "and" requires every component) are unchanged. Pure-physical weapons keep bit-identical arithmetic and the golden transcripts are untouched. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -19,6 +19,7 @@ from pf1e_simulator.models import (
|
||||
AttackSpec,
|
||||
Combatant,
|
||||
DamageComponent,
|
||||
DamageReduction,
|
||||
Saves,
|
||||
)
|
||||
from pf1e_simulator.rng import ScriptedRng
|
||||
@@ -206,3 +207,39 @@ def test_sneak_precision_not_reduced_by_energy_resistance() -> None:
|
||||
# the 6 precision dice survive intact (the old lumped path dealt 0).
|
||||
assert result.damage == 6
|
||||
assert eng._states[2].hp == 94
|
||||
|
||||
|
||||
def test_dr_spares_the_energy_component_of_a_mixed_attack() -> None:
|
||||
"""(g) A3: DR reduces only the physical share — energy dice pass through.
|
||||
|
||||
PF1e (CRB "Damage Reduction"): "Damage reduction does not negate ...
|
||||
energy damage dealt along with an attack." 1d8 piercing = 3 + 1d6 fire = 6
|
||||
against DR 5: the old lumped path dealt max(0, 9-5) = 4 (DR ate 2 of the
|
||||
fire dice); the fix reduces only the 3 piercing damage (floored at 0) and
|
||||
the full 6 fire damage lands.
|
||||
"""
|
||||
weapon = _attack(
|
||||
"flintlock",
|
||||
(("1d8", ("piercing",)), ("1d6", ("fire",))),
|
||||
)
|
||||
hero = _combatant("hero", attacks=(weapon,))
|
||||
foe = _combatant("foe")
|
||||
foe = foe.model_copy(update={"dr": DamageReduction(amount=5)})
|
||||
eng = _engine([hero, foe], [(0, 0), (0, 1)], ["players", "monsters"],
|
||||
[15, 3, 6])
|
||||
result = eng.resolve_attack(eng._states[0], eng._states[1], weapon)
|
||||
assert result.damage == 6
|
||||
assert eng._states[1].hp == 94
|
||||
|
||||
|
||||
def test_physical_only_damage_vs_dr_unchanged() -> None:
|
||||
"""(h) a physical-only weapon keeps the exact old DR arithmetic (regression
|
||||
lock for the split): 1d8 = 3 vs DR 5 floors to 0."""
|
||||
weapon = _attack("warhammer", (("1d8", ("bludgeoning",)),))
|
||||
hero = _combatant("hero", attacks=(weapon,))
|
||||
foe = _combatant("foe").model_copy(update={"dr": DamageReduction(amount=5)})
|
||||
eng = _engine([hero, foe], [(0, 0), (0, 1)], ["players", "monsters"],
|
||||
[15, 3])
|
||||
result = eng.resolve_attack(eng._states[0], eng._states[1], weapon)
|
||||
assert result.damage == 0
|
||||
assert eng._states[1].hp == 100
|
||||
|
||||
Reference in New Issue
Block a user