fix(rules): Born Damned and Axe to Grind become documented no-ops (SRD: +2 saves vs curses has no curse descriptor; +1 damage vs foes threatened only by you is not expressible - old Fort-vs-poison and axe-damage bonuses were wrong)
This commit is contained in:
@@ -128,6 +128,7 @@
|
||||
}
|
||||
],
|
||||
"source": "fiches_personnages/misty_sheet.json",
|
||||
"notes": "Traits : 'Born Damned' est un no-op documenté (+2 sauvegardes vs malédictions, descripteur 'curse' absent du moteur) ; 'Axe to Grind' est un no-op documenté (+1 dégâts vs ennemi menacé seulement par soi, condition non exprimable).",
|
||||
"features": [
|
||||
"Harmonic Spell",
|
||||
"Weapon Finesse",
|
||||
|
||||
@@ -259,11 +259,17 @@ WEAPON_FINESSE = AbilitySpec(
|
||||
effects=(StatModifier(target="attack", value=0, condition="weapon_finesse"),),
|
||||
)
|
||||
|
||||
# Axe to Grind: +1 trait bonus on damage with axes
|
||||
# Axe to Grind (Basic Combat trait, APG): "+1 trait bonus on damage against
|
||||
# foes who are threatened by only you." The "threatened by only you" gate
|
||||
# needs the target's threat map at damage-modifier resolution time, which the
|
||||
# StatModifier condition system does not carry (conditions are evaluated from
|
||||
# weapon/range/save-descriptor context only) -> documented traced no-op. The
|
||||
# previous "+1 damage with axes, condition='axe'" was the wrong effect (the
|
||||
# SRD bonus is not tied to wielding an axe at all).
|
||||
AXE_TO_GRIND = AbilitySpec(
|
||||
name="Axe to Grind",
|
||||
category="trait",
|
||||
effects=(StatModifier(target="damage", value=1, condition="axe"),),
|
||||
source_detail="trait (not modeled: +1 damage vs foes threatened only by you)",
|
||||
)
|
||||
|
||||
# Spell Vulnerability (divination): -2 penalty on saves vs divination
|
||||
@@ -276,13 +282,16 @@ SPELL_VULNERABILITY_DIVINATION = AbilitySpec(
|
||||
),
|
||||
)
|
||||
|
||||
# Born Damned (tiefling): darkness 1/day, +2 on saves vs poison
|
||||
# Born Damned (Race trait, Tiefling): "You gain a +2 trait bonus on saving
|
||||
# throws against curses and magical effects that produce curses." The engine
|
||||
# has no "curse" save descriptor (curses are not modeled as spell effects),
|
||||
# so the bonus has nothing to gate on -> documented traced no-op. The
|
||||
# previous "+2 Fort vs poison" was the wrong effect (poison is not a curse).
|
||||
# The tiefling darkness 1/day SLA is likewise not modeled.
|
||||
BORN_DAMNED = AbilitySpec(
|
||||
name="Born Damned (race: tiefling)",
|
||||
category="racial",
|
||||
effects=(
|
||||
StatModifier(target="fort", value=2, condition="poison"),
|
||||
),
|
||||
source_detail="racial (not modeled: +2 saves vs curses; no curse descriptor)",
|
||||
)
|
||||
|
||||
# Gifted Adept: +1 caster level for one spell
|
||||
|
||||
@@ -9,7 +9,8 @@ Then: the modifier applies only when the runtime condition holds;
|
||||
Supported condition families (plan B1):
|
||||
- ``target_within_30ft_ranged`` — ranged-style weapon (or touch with
|
||||
increment) fired at a target within 30 ft (Point-Blank Shot).
|
||||
- ``axe`` — wielded weapon whose name contains "axe" (Axe to Grind).
|
||||
- ``axe`` — wielded weapon whose name contains "axe" (engine capability; the
|
||||
real Axe to Grind trait is a documented no-op, see test_feats.py).
|
||||
- save descriptors (fear, cold, electricity, fey, divination, poison,
|
||||
disease, mind_affecting) — the saving throw's context, threaded from the
|
||||
fear aura and from spell effects/schools (Bitter, Lonely, Breeze-Kissed,
|
||||
@@ -27,7 +28,6 @@ from typing import Literal
|
||||
import pytest
|
||||
|
||||
from pf1e_simulator.abilities import (
|
||||
AXE_TO_GRIND,
|
||||
BITTER,
|
||||
BORN_DAMNED,
|
||||
BREEZE_KISSED,
|
||||
@@ -190,9 +190,16 @@ def test_point_blank_shot_inactive_for_melee_weapons() -> None:
|
||||
assert t.hp == 17 # damage 3, no +1
|
||||
|
||||
|
||||
def test_axe_to_grind_applies_to_axe_weapon() -> None:
|
||||
"""Axe to Grind: +1 damage with a weapon whose name contains 'axe'."""
|
||||
hero = make_combatant("hero", weapon_name="Battleaxe", features=(AXE_TO_GRIND,))
|
||||
_AXE_DAMAGE_PROBE = AbilitySpec(
|
||||
name="Axe Damage Probe",
|
||||
category="feat",
|
||||
effects=(StatModifier(target="damage", value=1, condition="axe"),),
|
||||
)
|
||||
|
||||
|
||||
def test_axe_condition_applies_to_axe_weapon() -> None:
|
||||
"""Engine 'axe' condition: +1 damage with a weapon whose name contains 'axe'."""
|
||||
hero = make_combatant("hero", weapon_name="Battleaxe", features=(_AXE_DAMAGE_PROBE,))
|
||||
foe = make_combatant("foe", ac_total=16, hp=20)
|
||||
a = make_state(hero, "players", (1, 1))
|
||||
t = make_state(foe, "monsters", (1, 2))
|
||||
@@ -202,9 +209,9 @@ def test_axe_to_grind_applies_to_axe_weapon() -> None:
|
||||
assert t.hp == 16 # 3 + 1 (axe)
|
||||
|
||||
|
||||
def test_axe_to_grind_inactive_without_axe() -> None:
|
||||
"""Axe to Grind: no bonus when the weapon is not an axe."""
|
||||
hero = make_combatant("hero", weapon_name="Shortsword", features=(AXE_TO_GRIND,))
|
||||
def test_axe_condition_inactive_without_axe() -> None:
|
||||
"""Engine 'axe' condition: no bonus when the weapon is not an axe."""
|
||||
hero = make_combatant("hero", weapon_name="Shortsword", features=(_AXE_DAMAGE_PROBE,))
|
||||
foe = make_combatant("foe", ac_total=16, hp=20)
|
||||
a = make_state(hero, "players", (1, 1))
|
||||
t = make_state(foe, "monsters", (1, 2))
|
||||
@@ -227,7 +234,6 @@ def test_axe_to_grind_inactive_without_axe() -> None:
|
||||
(FEY_TOUCHED, "will", 19, frozenset({"fey"})),
|
||||
(HALF_FORGOTTEN_SECRETS, "fort", 20, frozenset({"disease"})),
|
||||
(HALF_FORGOTTEN_SECRETS, "will", 20, frozenset({"mind_affecting"})),
|
||||
(BORN_DAMNED, "fort", 20, frozenset({"poison"})),
|
||||
(PINGRE, "fort", 19, frozenset({"poison"})),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -12,6 +12,8 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
from pf1e_simulator.abilities import (
|
||||
AXE_TO_GRIND,
|
||||
BORN_DAMNED,
|
||||
COMBAT_REFLEXES,
|
||||
EXALTED_SOCIETY,
|
||||
FORTIFIED_DRINKER,
|
||||
@@ -253,3 +255,20 @@ def test_fortified_drinker_is_inert_documented_noop() -> None:
|
||||
poison, condition='alcohol'" modifier is gone)."""
|
||||
assert FORTIFIED_DRINKER.effects == ()
|
||||
assert "not modeled" in FORTIFIED_DRINKER.source_detail
|
||||
|
||||
|
||||
def test_born_damned_is_inert_documented_noop() -> None:
|
||||
"""SRD (Tiefling race trait): +2 trait bonus on saves against curses. No
|
||||
'curse' save descriptor exists -> inert no-op (old +2 Fort vs poison was
|
||||
the wrong bonus entirely)."""
|
||||
assert BORN_DAMNED.effects == ()
|
||||
assert "not modeled" in BORN_DAMNED.source_detail
|
||||
|
||||
|
||||
def test_axe_to_grind_is_inert_documented_noop() -> None:
|
||||
"""SRD (APG combat trait): +1 trait bonus on damage against foes
|
||||
threatened by only you. The threat-map gate is not expressible in the
|
||||
StatModifier condition system -> inert no-op (old +1 damage with axes was
|
||||
the wrong effect; the SRD bonus is not tied to axes)."""
|
||||
assert AXE_TO_GRIND.effects == ()
|
||||
assert "not modeled" in AXE_TO_GRIND.source_detail
|
||||
|
||||
Reference in New Issue
Block a user