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:
@@ -347,8 +347,12 @@ Règles modélisées :
|
||||
d'action à round complet, mais les actions swift, libre et immédiate restent
|
||||
permises — CRB « Staggered »), se déplace à **moitié vitesse**, et achever une
|
||||
action non-mouvement (standard ou pénible) lui coûte **1 pv** à la fin de
|
||||
cette action (pv → −1, agonie). Simplification documentée : si l'action
|
||||
elle-même soigne la créature au-dessus de 0 pv, ce coût n'est pas appliqué.
|
||||
cette action (pv → −1, agonie). Les actions de mouvement — dont
|
||||
`retrieve_weapon` (sortir/récupérer une arme) et `repair_weapon` (raccommoder
|
||||
une arme brisée), classées mouvement — ne risquent pas de blessure
|
||||
supplémentaire et coûtent 0 pv (CRB « Disabled »). Simplification documentée :
|
||||
si l'action elle-même soigne la créature au-dessus de 0 pv, ce coût n'est pas
|
||||
appliqué.
|
||||
- `hp < 0` = inactive (mourante ou morte) ; `hp = 0` = active mais invalide.
|
||||
- Initiative : triée sur (total, modificateur, ordre de liste), sans re-jet.
|
||||
- Économie d'action : un tour normal donne une action standard + une action de
|
||||
|
||||
@@ -195,8 +195,18 @@ _HELPLESS_CONDITION_NAMES: frozenset[str] = frozenset(
|
||||
# PF1e (CRB, Disabled): at exactly 0 hp a creature takes only a single move
|
||||
# or standard action (staggered, half speed); move-equivalent and free
|
||||
# activities are not "strenuous" and cost no hp — everything else does.
|
||||
# retrieve_weapon / repair_weapon are move actions (CRB: drawing or mending a
|
||||
# weapon is a move action), so they belong here too.
|
||||
_DISABLED_MOVE_KINDS: frozenset[str] = frozenset(
|
||||
{"move", "5foot_step", "stand_up", "drop_prone", "wait"}
|
||||
{
|
||||
"move",
|
||||
"5foot_step",
|
||||
"stand_up",
|
||||
"drop_prone",
|
||||
"wait",
|
||||
"retrieve_weapon",
|
||||
"repair_weapon",
|
||||
}
|
||||
)
|
||||
# PF1e (CRB, Combat Maneuver): attempting a combat maneuver provokes an
|
||||
# attack of opportunity from the target unless the attacker holds the
|
||||
|
||||
@@ -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 ───────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user