From 65a5e24609c1ee40c7a348fb35abafd7c2d5face Mon Sep 17 00:00:00 2001 From: ctan Date: Sat, 19 Sep 2026 11:50:53 +0200 Subject: [PATCH] fix(rules): Combat Casting's +4 no longer applies to the entangled concentration check (SRD combat-casting: the feat covers only casting defensively or while grappled) --- src/pf1e_simulator/combat.py | 24 ++++++++++++++++++------ tests/test_concentration.py | 8 +++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/pf1e_simulator/combat.py b/src/pf1e_simulator/combat.py index 1adffce..18274f7 100644 --- a/src/pf1e_simulator/combat.py +++ b/src/pf1e_simulator/combat.py @@ -3809,14 +3809,19 @@ class CombatEngine: damage_taken: int, *, dc_override: int | None = None, + combat_casting_applies: bool = True, ) -> bool: """Concentration check: DC depends on situation. PF1e (CRB): the check is d20 + caster level + the ability modifier used to determine bonus spells (Int/Wis/Cha) — NOT Constitution. - Combat Casting's +4 applies only when casting defensively, while - grappled/pinned, or while entangled, never for damage taken while - casting. ``dc_override`` pins the DC (used by the entangled gate). + Combat Casting's +4 applies only when casting defensively or while + grappled (SRD feats/combat-casting.md: "a +4 bonus on concentration + checks made to cast a spell or use a spell-like ability when + casting on the defensive or while grappled") - never for damage + taken while casting, never while entangled. ``combat_casting_applies`` + is passed False by the situations outside the feat (entangled); + ``dc_override`` pins the DC (used by the entangled gate). """ dc = ( self._concentration_dc(caster, spell_level, damage_taken) @@ -3824,7 +3829,9 @@ class CombatEngine: else dc_override ) injured_while_casting = damage_taken > 0 and caster.grappled_with is None - has_combat_casting = self._has_feat(caster, _COMBAT_CASTING_FEAT) + has_combat_casting = ( + combat_casting_applies and self._has_feat(caster, _COMBAT_CASTING_FEAT) + ) bonus = 0 if injured_while_casting else (4 if has_combat_casting else 0) roll = self._rng.d20() total = ( @@ -6168,9 +6175,14 @@ class CombatEngine: if self._has_condition_named(caster, "entangled"): # PF1e (SRD Table 9-1 Concentration Check DCs): "Entangled while # casting" requires a DC 15 + spell level check or the spell is - # lost. Combat Casting's +4 applies (not an injury check). + # lost. Combat Casting's +4 does NOT apply: the feat covers only + # "casting on the defensive or while grappled" (SRD + # feats/combat-casting.md). dc = self._entangled_concentration_dc(spell_level) - if not self._check_concentration(caster, spell_level, 0, dc_override=dc): + if not self._check_concentration( + caster, spell_level, 0, + dc_override=dc, combat_casting_applies=False, + ): self._log( f"round {self._current_round} {caster.combatant.id}: " f"casting while entangled: concentration failed (DC {dc}), " diff --git a/tests/test_concentration.py b/tests/test_concentration.py index 07eec70..afb55df 100644 --- a/tests/test_concentration.py +++ b/tests/test_concentration.py @@ -211,10 +211,12 @@ class TestEntangledCasting: engine, c = self._entangled_engine(11) assert engine._spell_cast_interrupted(c, 1, cast_defensively=False) is False - def test_combat_casting_helps_while_entangled(self) -> None: - # d20 7 + level 5 + wis 0 + 4 = 16 >= 16 -> PASS with the feat. + def test_combat_casting_does_not_help_while_entangled(self) -> None: + # SRD feats/combat-casting.md: the +4 covers only "casting on the + # defensive or while grappled" - NOT the entangled check. d20 7 + + # level 5 + wis 0 = 12 < DC 16 -> spell lost even with the feat. engine, c = self._entangled_engine(7, combat_casting=True) - assert engine._spell_cast_interrupted(c, 1, cast_defensively=False) is False + assert engine._spell_cast_interrupted(c, 1, cast_defensively=False) is True def test_entangled_carries_minus4_dex_skills(self) -> None: mods = {m.target: m.value for m in ENTANGLED.modifiers}