fix(rules): set the slumber hex duration to the witch level
PF1e (APG Slumber): the target "remains asleep for a number of rounds equal to the witch's level" (Will negates). _hex_slumber hardcoded 1 round; it now uses the caster's level. The RAW clause that the hex ends when the target takes damage is not modeled (no wake-on-damage hook in the engine) — documented in README. Two behavioral tests pin the failed-save duration (= level) and the saved case. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1098,6 +1098,10 @@ Règles modélisées :
|
||||
**une seule option** — l'option choisie est modélisée par les jets d'attaque —
|
||||
pendant **3 rounds** ; une sauvegarde de Volonté réussie ne l'annule pas mais
|
||||
**réduit la durée à 1 round** (« A Will save reduces this to just 1 round ») ;
|
||||
`slumber` (APG, conformance D4) endort la cible pour un **nombre de rounds
|
||||
égal au niveau de la sorcière** (Volonté annule ; simplification documentée :
|
||||
la fin RAW du sortilège quand la cible subit des dégâts n'est pas modélisée,
|
||||
le moteur n'a pas de crochet de réveil) ;
|
||||
`cackle` prolonge d'un round les sortilèges déjà
|
||||
actifs, sur une liste instantanée pour ne pas s'étendre à l'infini. Les sorts à
|
||||
zone (AoE) utilisent le DD d'incantation du jeteur et chaque créature de la
|
||||
|
||||
@@ -4783,7 +4783,10 @@ class CombatEngine:
|
||||
def _hex_slumber(
|
||||
self, state: CombatantState, target: CombatantState, hex_dc: int
|
||||
) -> None:
|
||||
"""Slumber: asleep/disabled if Will fails."""
|
||||
"""Slumber (APG): the target remains asleep for a number of rounds
|
||||
equal to the witch's level; a successful Will save negates.
|
||||
Simplification documented in README: the RAW ending of the hex when
|
||||
the target takes damage is not modeled (no wake-on-damage hook)."""
|
||||
save_result = self.resolve_save(target, "will", hex_dc)
|
||||
if save_result.success:
|
||||
self._log(
|
||||
@@ -4791,16 +4794,17 @@ class CombatEngine:
|
||||
f"{target.combatant.id} -> SAVED"
|
||||
)
|
||||
return
|
||||
rounds = state.combatant.level
|
||||
target.active_conditions.append(
|
||||
ActiveCondition(
|
||||
condition=CONDITIONS_BY_NAME["asleep"],
|
||||
rounds_left=1,
|
||||
rounds_left=rounds,
|
||||
source=f"hex: slumber by {state.combatant.id}",
|
||||
)
|
||||
)
|
||||
self._log(
|
||||
f"round {self._current_round} {state.combatant.id}: slumber on "
|
||||
f"{target.combatant.id} -> asleep (1r)"
|
||||
f"{target.combatant.id} -> asleep ({rounds}r)"
|
||||
)
|
||||
|
||||
def _hex_cackle(self, state: CombatantState, target: CombatantState) -> None:
|
||||
|
||||
@@ -294,6 +294,44 @@ def test_evil_eye_saved_deals_minus_two_attack_one_round() -> None:
|
||||
assert hex_buffs[0].modifiers == (StatModifier(target="attack", value=-2),)
|
||||
|
||||
|
||||
def test_slumber_failed_save_puts_target_asleep_for_witch_level() -> None:
|
||||
"""APG Slumber: the target "remains asleep for a number of rounds equal to
|
||||
the witch's level". Given: a level-5 witch and a target failing the Will
|
||||
save (d20 2 +4 < DC 14)
|
||||
When: the hex is cast
|
||||
Then: the asleep condition carries rounds_left = 5."""
|
||||
nairda = _combatant("nairda", resources={"hexes": 5})
|
||||
goblin = _combatant("goblin", hp_max=6)
|
||||
engine, me, enemy = _engine(nairda, [goblin], rng_queue=[2])
|
||||
|
||||
engine._execute(
|
||||
me, Action(kind="hex", target_id=enemy[0].combatant.id, hex_name="slumber")
|
||||
)
|
||||
|
||||
asleep = [
|
||||
ac for ac in enemy[0].active_conditions if ac.condition.name == "asleep"
|
||||
]
|
||||
assert len(asleep) == 1
|
||||
assert asleep[0].rounds_left == 5
|
||||
|
||||
|
||||
def test_slumber_saved_leaves_target_awake() -> None:
|
||||
"""APG Slumber: Will negates. Given: a target saving (natural 20)
|
||||
When: the hex is cast
|
||||
Then: no asleep condition is applied."""
|
||||
nairda = _combatant("nairda", resources={"hexes": 5})
|
||||
goblin = _combatant("goblin", hp_max=6)
|
||||
engine, me, enemy = _engine(nairda, [goblin], rng_queue=[20])
|
||||
|
||||
engine._execute(
|
||||
me, Action(kind="hex", target_id=enemy[0].combatant.id, hex_name="slumber")
|
||||
)
|
||||
|
||||
assert not any(
|
||||
ac.condition.name == "asleep" for ac in enemy[0].active_conditions
|
||||
)
|
||||
|
||||
|
||||
# --- tammara: deeds ---
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user