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:
@@ -12,17 +12,23 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from pf1e_simulator.abilities import (
|
from pf1e_simulator.abilities import (
|
||||||
|
AMBITIOUS,
|
||||||
AXE_TO_GRIND,
|
AXE_TO_GRIND,
|
||||||
BITTER,
|
BITTER,
|
||||||
BORN_DAMNED,
|
BORN_DAMNED,
|
||||||
|
BREEZE_KISSED,
|
||||||
COMBAT_REFLEXES,
|
COMBAT_REFLEXES,
|
||||||
EXALTED_SOCIETY,
|
EXALTED_SOCIETY,
|
||||||
EXTREMELY_FASHIONABLE,
|
EXTREMELY_FASHIONABLE,
|
||||||
|
FOCUSED_STUDY,
|
||||||
FORTIFIED_DRINKER,
|
FORTIFIED_DRINKER,
|
||||||
FRIENDLY_FACE,
|
FRIENDLY_FACE,
|
||||||
|
LARGER_THAN_LIFE,
|
||||||
|
LIKE_THE_WIND,
|
||||||
LONELY,
|
LONELY,
|
||||||
MOUNTED_COMBAT,
|
MOUNTED_COMBAT,
|
||||||
POINT_BLANK_SHOT,
|
POINT_BLANK_SHOT,
|
||||||
|
WHISPERING_WIND,
|
||||||
AbilitySpec,
|
AbilitySpec,
|
||||||
)
|
)
|
||||||
from pf1e_simulator.combat import CombatantState, CombatEngine
|
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
|
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(
|
def _make_battle_combatant(
|
||||||
cid: str, *, features: tuple[AbilitySpec, ...] = (), hp_max: int = 30
|
cid: str, *, features: tuple[AbilitySpec, ...] = (), hp_max: int = 30
|
||||||
) -> Combatant:
|
) -> Combatant:
|
||||||
|
|||||||
Reference in New Issue
Block a user