fix(spells): set the correct area shapes
SRD-verified area templates: Burning Hands 15-ft cone, Color Spray 15-ft cone, Fear 30-ft cone, Flurry of Snowballs 30-ft cone, Lightning Bolt 120-ft line (all converted from single-target/burst approximations to proper AreaEffect shapes); Fireball 20-ft burst, Sound Burst 10-ft spread, Sleep 10-ft burst, Web 20-ft spread kept. Close-range spells note the 15/30-ft RAW ranges as an approximation in their descriptions. README: cone/scaling limitations moved out of the limitations list, new area-shape + caster-level scaling rules documented, areas.py added to the architecture. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -909,6 +909,35 @@ Règles modélisées :
|
||||
centré sur la créature est inchangé. `default_policy` ne choisit pas de point
|
||||
(disponible via une politique personnalisée). `default_policy` choisit soin / offensif / buff via
|
||||
`_find_heal_spell`, `_find_offensive_spell` et `_find_ally_needing_heal`.
|
||||
- Formes de zone (S1a) et scalage par niveau d'incanteur (S1b) :
|
||||
`AreaEffect.shape` vaut `burst` (défaut), `cone` ou `line` (CRB « Magic —
|
||||
Aiming a Spell »). Le **burst** reste un cercle centré sur la case résolue
|
||||
(cible ou `target_pos`, U7b). Le **cône** (90° — « a quarter-circle », la
|
||||
largeur valant la longueur à chaque distance) et la **ligne** (5 ft de large,
|
||||
longueur = `radius_ft`) partent de la **case du jeteur** et sont pointés
|
||||
vers la cible résolue (`target_id`) ou le point désigné (`target_pos`) — le
|
||||
cône exclut la case d'origine, la ligne commence dans la case du jeteur
|
||||
(« the bolt begins at your fingertips », SRD Lightning Bolt : le jeteur est
|
||||
donc dans sa propre ligne). Toute créature dont une case occupée intersecte
|
||||
le gabarit subit l'effet, avec sauvegarde et RS par créature comme pour un
|
||||
burst (application partagée, géométrie dans `areas.py`). Le snapshot
|
||||
d'observation expose `{shape, radius_ft}` via la clé `area`.
|
||||
Un `DamageEffect` peut déclarer `"scaling": {"dice": "1d6", "per_levels": 1,
|
||||
"max_dice": 10}` (dés par niveau — Fireball/Lightning Bolt « 1d6 per caster
|
||||
level (maximum 10d6) », Burning Hands « 1d4 per level (maximum 5d4) »,
|
||||
Shocking Grasp « 1d6 per level (maximum 5d6) ») et/ou
|
||||
`"scaling_flat": {"amount": 1, "per_levels": 1, "max": 5}` (bonus plat par
|
||||
niveau — Inflict Light Wounds « 1d8 + 1 per caster level (maximum +5) ») ;
|
||||
à l'incantation, `effective_formula` résout dés additionnels =
|
||||
`min(max_dice, niveau // per_levels)` et bonus plat =
|
||||
`min(max, (niveau // per_levels) × amount)` avec la formule de base — le
|
||||
total scalé suit ensuite le critique, la résistance énergétique et la
|
||||
métamagie comme n'importe quelle formule (le chargeur refuse un dé de
|
||||
scalage de type différent du dé de base). Absence de scalage = comportement
|
||||
inchangé. Simplification documentée : le scalage ne s'applique qu'aux
|
||||
dégâts (`DamageEffect`) — les soins par niveau (Cure Light Wounds
|
||||
« +1 per caster level (maximum +5) ») ne sont pas scalés (la fiche dorée
|
||||
épingle `1d8+1`).
|
||||
- Résistance aux sorts (SR) et résistances/immunités énergétiques : un combattant
|
||||
avec `spell_resistance > 0` oppose sa SR à chaque sort qui le cible — jet
|
||||
`d20 + niveau du jeteur` contre la SR ; si le total est inférieur, le sort est
|
||||
@@ -1361,15 +1390,8 @@ plutôt que bricolées) :
|
||||
Dextérité/Force du CMB (conformance L1) tient compte de leur taille.
|
||||
|
||||
Limites de sorts (conformance D6-D9, documentées plutôt que bricolées — le
|
||||
moteur n'a ni gabarit de cône, ni scalage par niveau d'incanteur, ni
|
||||
projectiles multiples, ni sauvegarde partielle) :
|
||||
moteur n'a ni projectiles multiples, ni sauvegarde partielle) :
|
||||
|
||||
- **Burning Hands — ni cône ni dégâts par niveau** (SRD spells/burning-hands.md :
|
||||
« 1d4 points of fire damage per caster level (maximum 5d4) », cône de 15 ft) :
|
||||
le moteur n'a pas de scalage par niveau d'incanteur ni de zone en cône ; le
|
||||
sort est modélisé en dégâts fixes `1d4` feu, sauvegarde de Réflexes pour
|
||||
moitié, sur la cible résolue (la formule `1d4+1` qui surestimait le dégât de
|
||||
base a été corrigée — conformance D6).
|
||||
- **Acid Arrow — sans dégâts continus** (SRD spells/acid-arrow.md : portée
|
||||
**longue** — corrigée, conformance D7 — et « For every three caster levels
|
||||
you possess, the acid, unless neutralized, lasts for another round [...],
|
||||
@@ -1377,14 +1399,13 @@ projectiles multiples, ni sauvegarde partielle) :
|
||||
subsystem de dégâts continus par sort (ni neutralisation d'acide) ; seuls les
|
||||
2d4 de dégâts initiaux sont modélisés, les rounds de dégâts persistants sont
|
||||
hors périmètre.
|
||||
- **Fear — sauvegarde totale et pas de cône** (SRD spells/fear.md : « each
|
||||
- **Fear — sauvegarde totale** (SRD spells/fear.md : « each
|
||||
living creature in the area [...] becomes panicked unless it succeeds on a
|
||||
Will save [...]. If the Will save succeeds, the creature is shaken for 1
|
||||
round » ; cône de 30 ft) : la condition appliquée est bien `panicked`
|
||||
(conformance D8), mais le moteur n'a pas de sauvegarde partielle pour les
|
||||
conditions — un succès annule totalement l'effet au lieu de poser `shaken`
|
||||
1 round — et le gabarit en cône n'existe pas (sort résolu sur la cible) ;
|
||||
la durée « 1 round/niveau » est fixée à 4 rounds.
|
||||
round » ; cône de 30 ft — modélisé, S1a/S1c) : la condition appliquée est
|
||||
bien `panicked` (conformance D8), mais le moteur n'a pas de sauvegarde
|
||||
partielle pour les conditions — un succès annule totalement l'effet au lieu
|
||||
de poser `shaken` 1 round ; la durée « 1 round/niveau » est fixée à 4 rounds.
|
||||
- **Magic Missile — un seul projectile** (SRD spells/magic-missile.md :
|
||||
« You strike [...] with one missile, plus one additional missile per two
|
||||
caster levels beyond 1st [...] maximum of five missiles ») : le moteur
|
||||
@@ -1415,6 +1436,11 @@ projectiles multiples, ni sauvegarde partielle) :
|
||||
- `los.py` — ligne d'effet et couvert par la règle des coins : gate de ligne
|
||||
d'effet dans `weapon_for`, bonus de couvert par niveau (+2/+4/+8 terrain via
|
||||
`cover_bonus`, +4 couvert mou des créatures) dans `resolve_attack`.
|
||||
- `areas.py` — géométrie des gabarits de zone cône et ligne (CRB « Magic —
|
||||
Aiming a Spell ») : `cone_cells` (quart de cercle de 90°, test d'angle ±45°
|
||||
en arithmétique entière exacte, case d'origine exclue) et `line_cells`
|
||||
(raie de Bresenham de 5 ft de large, longue de `radius_ft`, partant de la
|
||||
case du jeteur). Les bursts restent gérés par `_creatures_in_range`.
|
||||
- `combat.py` — `CombatEngine` déterministe : initiative, actions, résolution
|
||||
des attaques (couvert, ligne d'effet, pénalités de portée, CA de contact
|
||||
pour les attaques touch et firearms), tailles et allonges (occupation
|
||||
@@ -1467,7 +1493,10 @@ projectiles multiples, ni sauvegarde partielle) :
|
||||
sauvegarde, DC de base, exigence de contact à distance `requires_ranged_touch`
|
||||
(rayons), temps d'incantation `cast_time`) et types d'effets
|
||||
(`DamageEffect`, `ConditionEffect`,
|
||||
`HealEffect`, `BuffEffect`). Portées PF1e (personal, touch, close, medium,
|
||||
`HealEffect`, `BuffEffect`, `AreaEffect` — burst/cône/ligne, S1a). Un
|
||||
`DamageEffect` porte des specs de scalage optionnels (`scaling` en dés,
|
||||
`scaling_flat` en bonus plat) résolues par `effective_formula(niveau)` au
|
||||
moment de l'incantation (S1b). Portées PF1e (personal, touch, close, medium,
|
||||
long) calculées par `spell_range_ft`. Chargeur JSON (`load_spell`,
|
||||
`load_spell_registry`) pour `data/spells/`, acceptant les codes de budget
|
||||
`cast_time` et leurs alias RAW (« 1 round », « 1 minute »…). Des
|
||||
@@ -1498,7 +1527,7 @@ projectiles multiples, ni sauvegarde partielle) :
|
||||
La gate de validation complète (tests + lint + types) :
|
||||
|
||||
```bash
|
||||
uv run pytest -q # 714 tests
|
||||
uv run pytest -q # 1364 tests
|
||||
uv run ruff check src tests
|
||||
uv run basedpyright src # mode strict
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user