fix(rules): correct the cackle hex

PF1e (APG Cackle): "(Su) Free Action; ... extends the duration of all evil eye, fortune or misfortune hexes on all creatures within 30 feet by 1 round." The engine spent a hex use, acted only on the resolved target, and extended every hex-sourced condition (including slumber, which RAW cackle does not touch). Cackle is now dispatched before the resource/target gate (no cost), occupies the FREE budget slot via _effective_slot (the hex kind stays standard for evil_eye/slumber), and rebuilds the evil_eye buff list of every creature within 30 ft in a single bounded pass. Fortune/misfortune are not implemented, so evil_eye is the only extendable family member — documented in README. test_cackle.py rewritten for the new semantics (extension, range, free slot, no cost).

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-12 12:58:32 +02:00
parent 68699ad6c7
commit b0fe553782
3 changed files with 134 additions and 73 deletions
+17 -8
View File
@@ -393,12 +393,15 @@ Règles modélisées :
`(attack, attack)` → `(attack,)` ; `(charge, attack)` → `(charge,)` ;
`(attack, full_attack, move)` → `(attack, move)` ; `(move, attack, move)` →
`(move, attack)` ; `(full_attack, deed, inspire_courage)` → `(full_attack, deed)`.
Classification (exhaustive sur le `Literal` des kinds, verrouillée par
`tests/test_conformance_action_economy.py`) : **standard** = `attack`,
`maneuver`, `channel_energy`, `hex`, `delay`, `ready`,
`total_defense`, `aid_another` ; `cast_spell` occupe le compartiment que
lui donne son **temps d'incantation** (champ `cast_time` de `SpellSpec`,
voir « Temps d'incantation » plus bas ; standard par défaut) ;
Classification (exhaustive sur le `Literal` des kinds, verrouillée par
`tests/test_conformance_action_economy.py`) : **standard** = `attack`,
`maneuver`, `channel_energy`, `hex`, `delay`, `ready`,
`total_defense`, `aid_another` ; `cast_spell` occupe le compartiment que
lui donne son **temps d'incantation** (champ `cast_time` de `SpellSpec`,
voir « Temps d'incantation » plus bas ; standard par défaut) ; exception
par action (conformance D5 — APG « Cackle ») : `hex` avec
`hex_name="cackle"` occupe le compartiment **libre** (résolu par
`_effective_slot`, le kind `hex` restant standard pour evil_eye/slumber) ;
**mouvement** = `move`, `stand_up`,
`retrieve_weapon` (sortir une arme est une action de mouvement),
`repair_weapon` ; **swift** = `swift`, `deed`,
@@ -1102,8 +1105,14 @@ Règles modélisées :
é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 à
`cackle` (APG, conformance D5) est une **action libre sans coût de
ressource** qui prolonge d'un round tous les sortilèges evil-eye (et
fortune/misfortune — non implémentés, seule extension modélisée : les
buffs evil_eye) portés par **toutes les créatures à 30 ft** de la
sorcière, sur une liste instantanée pour ne pas s'étendre à l'infini.
Simplification documentée : le slot « hex » reste standard pour
evil_eye/slumber ; les autres hexes à prolonger ne sont pas implémentés.
Les sorts à
zone (AoE) utilisent le DD d'incantation du jeteur et chaque créature de la
zone effectue la sauvegarde listée par le sort (Volonté / Réflexes /
Constitution — le sort primaire réutilise son jet déjà effectué) ; un succès