fix(rules): cover the feat/trait no-op batch in test_feats (assert the eight documented inert specs, the two traced-noop names, and misty's resistances/bite data encoding)

This commit is contained in:
2026-09-19 12:47:40 +02:00
parent 2df9dd778c
commit 13e4a2774e
+69
View File
@@ -12,17 +12,23 @@ import json
from pathlib import Path
from pf1e_simulator.abilities import (
AMBITIOUS,
AXE_TO_GRIND,
BITTER,
BORN_DAMNED,
BREEZE_KISSED,
COMBAT_REFLEXES,
EXALTED_SOCIETY,
EXTREMELY_FASHIONABLE,
FOCUSED_STUDY,
FORTIFIED_DRINKER,
FRIENDLY_FACE,
LARGER_THAN_LIFE,
LIKE_THE_WIND,
LONELY,
MOUNTED_COMBAT,
POINT_BLANK_SHOT,
WHISPERING_WIND,
AbilitySpec,
)
from pf1e_simulator.combat import CombatantState, CombatEngine
@@ -305,6 +311,69 @@ def test_extremely_fashionable_is_inert_documented_noop() -> None:
assert "not modeled" in EXTREMELY_FASHIONABLE.source_detail
# ── SRD conformance: feat/trait divergence batch (size/speed/skill gates) ─────
def test_larger_than_life_is_inert_documented_noop() -> None:
"""SRD (Basic Combat trait): considered one size larger for Intimidate
while wielding a firearm. Intimidate and size-for-skill are not modeled
-> inert no-op (the old flat +1 Intimidate was the wrong effect)."""
assert LARGER_THAN_LIFE.effects == ()
assert "not modeled" in LARGER_THAN_LIFE.source_detail
def test_ambitious_is_inert_documented_noop() -> None:
"""SRD (Basic Social trait): +4 trait bonus on Diplomacy vs creatures
with 5+ more HD. The HD-differential gate is not expressible -> inert."""
assert AMBITIOUS.effects == ()
assert "not modeled" in AMBITIOUS.source_detail
def test_focused_study_is_inert_documented_noop() -> None:
"""SRD (human alternate racial trait): +2 on one chosen skill (re-chosen
at 8th/16th). The chosen skill is never resolved in combat -> inert."""
assert FOCUSED_STUDY.effects == ()
assert "not modeled" in FOCUSED_STUDY.source_detail
def test_esha_sylph_traits_are_inert_documented_noops() -> None:
"""SRD (ARG sylph alternate racial traits; Esha's sheet declares race
Sylph so the picks are legal): Whispering Wind = +4 racial Stealth,
Like the Wind = +5 ft base speed, Breeze-Kissed = +2 racial AC vs
nonmagical ranged + 1/day gust. None is expressible (no stealth checks,
speed_ft modifiers unresolved, AC is a final sheet value) -> all inert;
the previously coded Fly/save/speed bonuses were fabricated."""
for spec in (WHISPERING_WIND, LIKE_THE_WIND, BREEZE_KISSED):
assert spec.effects == (), spec.name
assert "not modeled" in spec.source_detail, spec.name
def test_fey_touched_and_pingre_are_traced_noop_names() -> None:
"""Not PF1e features (no SRD entry under those names): Harvie's
"Fey-touched" and Nairda's joke trait "Pingre" join the decorative
traced-noop list; their old coded bonuses were fabricated."""
assert "fey-touched" in _NOOP_FEATS
assert "pingre" in _NOOP_FEATS
for name in ("Fey-touched", "Pingre"):
spec = _resolve_feat(name, 1)
assert spec.name == name
assert spec.effects == ()
def test_misty_scaled_skin_and_maw_or_claw_encoded_in_data() -> None:
"""SRD (ARG tiefling daemon-spawn): Scaled Skin (electricity) = resist
electricity 5 (replaces fiendish resistance - no cold/fire); Maw or Claw
= bite natural attack, 1d6 for a Medium tiefling (the Foundry sheet's
"1d4 estimé" is an author guess, discrepancy recorded in the JSON notes).
Both benefits are Combatant data, not stat modifiers."""
misty = load_combatant(Path("data/pcs/misty.json"))
assert misty.resistances == {"electricity": 5}
bites = [a for a in misty.attacks if a.name.startswith("Bite")]
assert len(bites) == 1
formula = bites[0].damage[0].formula
assert (formula.count, formula.sides, formula.bonus) == (1, 6, 0)
def _make_battle_combatant(
cid: str, *, features: tuple[AbilitySpec, ...] = (), hp_max: int = 30
) -> Combatant: