fix(rules): exempt retrieve and repair weapon from the disabled hit-point cost

PF1e (CRB, Disabled): a disabled creature can take move actions without further injuring itself. retrieve_weapon and repair_weapon are budget-classified move actions (_ACTION_ECONOMY_MOVE_KINDS) but were absent from _DISABLED_MOVE_KINDS, so a creature at 0 hp paid 1 hp to draw or mend a weapon. Added both kinds to the exempt set.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-09-11 21:22:03 +02:00
parent fa01ed74c5
commit 4f6b076a49
3 changed files with 68 additions and 3 deletions
+51
View File
@@ -338,6 +338,57 @@ def test_disabled_move_action_costs_no_hp() -> None:
assert not any("costs 1 hp" in ln for ln in eng._transcript)
def _disabled_one_action_policy(action: Action) -> PolicyFn:
def policy(_engine: CombatEngine, state: CombatantState) -> tuple[Action, ...]:
if state.combatant.id == "hero":
return (action,)
return (Action(kind="wait"),)
return policy
def test_disabled_retrieve_weapon_costs_no_hp() -> None:
# PF1e (CRB, Disabled): "Taking move actions doesn't risk further injury."
# Drawing/retrieving a weapon is a move action, so it must not cost 1 hp.
hero = _combatant("hero")
foe = _combatant("foe")
eng = _engine(
[hero, foe], [(0, 0), (0, 7)], ["players", "monsters"], [],
policy=_disabled_one_action_policy(
Action(kind="retrieve_weapon", weapon_id="hero-sword")
),
)
s = eng._states[0]
s.hp = 0
s.weapon_stolen.add("hero-sword") # make the retrieve meaningful
eng._current_round = 1
eng._take_turn(s)
assert s.hp == 0
assert "hero-sword" not in s.weapon_stolen # the retrieve ran
assert not any("costs 1 hp" in ln for ln in eng._transcript)
def test_disabled_repair_weapon_costs_no_hp() -> None:
# Mending a broken weapon is likewise a move action: no hp cost while 0 hp.
hero = _combatant("hero")
foe = _combatant("foe")
eng = _engine(
[hero, foe], [(0, 0), (0, 7)], ["players", "monsters"], [],
policy=_disabled_one_action_policy(
Action(kind="repair_weapon", weapon_id="hero-sword")
),
)
s = eng._states[0]
s.hp = 0
s.weapon_damage["hero-sword"] = 10 # a broken weapon, repairable
s.weapon_broken.add("hero-sword")
eng._current_round = 1
eng._take_turn(s)
assert s.hp == 0
assert "hero-sword" not in s.weapon_broken # the repair ran
assert not any("costs 1 hp" in ln for ln in eng._transcript)
# ── B12: death threshold is -CON, no -10 floor ───────────────────────────────