From fdc91274835668cd7566cd8318e60c9054bcd4a6 Mon Sep 17 00:00:00 2001 From: ctan Date: Wed, 16 Sep 2026 19:58:30 +0200 Subject: [PATCH] fix(rules): Toughness is +3 hp below 4 Hit Dice (max(3, level)) --- src/pf1e_simulator/abilities.py | 8 ++++---- tests/test_feats.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/pf1e_simulator/abilities.py b/src/pf1e_simulator/abilities.py index 7cc7a2c..c120b1a 100644 --- a/src/pf1e_simulator/abilities.py +++ b/src/pf1e_simulator/abilities.py @@ -49,16 +49,16 @@ def weapon_focus(weapon_name: str) -> AbilitySpec: ) -# Toughness: +1 HP per Hit Die (approximated as +level HP, untyped, CRB) -# For simplicity at this stage, we model it as a flat HP bonus. -# The engine doesn't yet compute per-HD bonuses, so the caller sets the value. +# Toughness (CRB): "+3 hit points. If the character has 4 or more Hit Dice, +# she gains +1 hit point per Hit Die." => max(3, HD). The engine doesn't yet +# compute per-HD bonuses, so the caller passes the level. def toughness(level: int) -> AbilitySpec: return AbilitySpec( name="Toughness", category="feat", source_detail="feat lv1", effects=( - StatModifier(target="hp", value=level), + StatModifier(target="hp", value=max(3, level)), ), ) diff --git a/tests/test_feats.py b/tests/test_feats.py index 00baa28..95a4aee 100644 --- a/tests/test_feats.py +++ b/tests/test_feats.py @@ -135,6 +135,21 @@ def test_spot_check_toughness_level_tied() -> None: assert any(e.value == 5 for e in spec.effects) +def test_toughness_is_plus3_below_four_hd_and_plus_hd_above() -> None: + # CRB Toughness: "+3 hit points. If the character has 4 or more Hit + # Dice, she gains +1 hit point per Hit Die." + def hp_value(level: int) -> int: + spec = _resolve_feat("Toughness", level) + values = [e.value for e in spec.effects if e.target == "hp"] + assert len(values) == 1 + return values[0] + + assert hp_value(1) == 3 + assert hp_value(2) == 3 + assert hp_value(3) == 3 + assert hp_value(4) == 4 + + # ── Expected mapping table (name -> resolved display name or traced-no-op) ───── _EXPECTED_RESOLUTION: dict[str, str] = {