Compare commits

..

31 Commits

Author SHA1 Message Date
ctan 9aee4f6f6b fix(rules): correct P2/P3 PF1e conformance bugs (flanking, touch, blinded, disabled, death, maneuvers, DR, sneak) 2026-09-10 21:45:50 +02:00
ctan 5afe495c4a fix(rules): correct P1 PF1e conformance bugs (area saves, concentration, prone, slots, helpless, nauseated) 2026-09-10 20:52:28 +02:00
ctan 1ca5aed758 data(spells): add batch 3 of PC spell list (32 spells) 2026-09-10 19:43:07 +02:00
ctan 543da6a931 data(spells): add batch 2 of PC spell list (32 spells) 2026-09-10 19:12:57 +02:00
ctan 07e0d83300 data(spells): add batch 1 of PC spell list (32 spells) 2026-09-10 18:49:54 +02:00
ctan 91f70f19ca feat(conditions): add bleeding and confused rules 2026-09-10 18:15:35 +02:00
ctan b3bff85215 feat(conditions): add blinded/invisible/helpless/fascinated combat rules 2026-09-10 18:00:41 +02:00
ctan 79ac190728 feat(combat): use fly speed for movement
Movement budget is now max(land, fly) speed. Updates the harvie golden: Harvie (land 10 / fly 80) is a flyer and now flies at 80 ft, so he wins vs the goblin instead of being pinned crawling at 10 ft.
2026-09-10 17:46:57 +02:00
ctan ec0397351a test(rules): lock in that size is baked into attack/AC values 2026-09-10 17:25:05 +02:00
ctan 582b6c60d1 feat(observation): expose ranged threat grid 2026-09-10 17:12:54 +02:00
ctan f637f87a8c feat(combat): add aid another and run actions 2026-09-10 16:56:38 +02:00
ctan 06555f7b49 test(llm): demonstrate unblocked engine capabilities via observation-only policy 2026-09-10 15:56:21 +02:00
ctan 26f86f416d feat(combat): allow point-targeted spell areas 2026-09-10 15:19:36 +02:00
ctan 7fafbe43cf feat(observation): expose structured spell areas 2026-09-10 14:14:54 +02:00
ctan 61c9b7fa47 feat(observation): expose remaining attacks of opportunity 2026-09-10 14:08:25 +02:00
ctan 6abd29cbc5 feat(observation): expose attack failure reasons 2026-09-10 13:57:34 +02:00
ctan 8f3bd0d0e0 feat(observation): expose per-pair cover bonus 2026-09-10 13:46:05 +02:00
ctan ed04052244 feat(combat): allow selecting the attacking weapon 2026-09-10 13:36:27 +02:00
ctan b91e1ac215 feat(combat): allow explicit movement destinations 2026-09-10 13:21:54 +02:00
ctan 468c22ff57 feat(combat): add stand up and drop prone 2026-09-10 13:07:58 +02:00
ctan ebdff4f7e5 feat(combat): add total defense and fighting defensively 2026-09-10 12:52:47 +02:00
ctan ab02597bc4 feat(combat): add ready action with triggers 2026-09-10 12:39:28 +02:00
ctan f83e4b8016 feat(combat): add delay action 2026-09-10 12:20:33 +02:00
ctan c5cfa78f87 feat(observation): expose initiative order and totals 2026-09-10 12:04:10 +02:00
ctan 4e115f9176 feat(combat): surface explicit action results and reject invalid actions 2026-09-10 11:51:02 +02:00
ctan e6125b04f6 feat(combat): support partial and improved cover levels 2026-09-10 11:32:08 +02:00
ctan 2eed752f7f feat(combat): add dying stabilization and Improved Critical 2026-09-10 10:43:02 +02:00
ctan 97f4429c2b feat(combat): add concealment miss chance, sneak attack, and Precise Shot 2026-09-10 10:22:50 +02:00
ctan a1c43e83af feat(combat): enforce spell resistance checks and energy resistances 2026-09-06 19:18:10 +02:00
ctan a9ccdd3a56 feat(combat): add activate_power_attack action and consume Up Close and Deadly deed
- New Action kind activate_power_attack: free action that sets
  state.power_attack = True when the combatant has the Power Attack feat;
  logged no-op otherwise. Routed via _execute_movement (free-action branch)
  to keep _execute under the C901 complexity ceiling.
- Consume deed_up_close_and_deadly in resolve_attack: the first attack
  resolved while the flag is set gains +1d6 if it is a ranged attack within
  30 ft; the flag is reset in all cases (benefit lost if out of range or
  not ranged). Extracted into _consume_up_close_and_deadly helper.
- Document activate_power_attack, corrected deed semantics, and the
  power_attack snapshot key in README.
- Add tests/test_engine_fixes.py (5 tests): PA activation, no-feat no-op,
  deed+ranged within 30ft, deed out-of-range, deed melee.
2026-09-06 18:37:58 +02:00
ctan e29c472a67 feat(combat): expose DR, conditions, creature type, features, and spell save semantics in observation 2026-09-06 18:18:13 +02:00
147 changed files with 13439 additions and 338 deletions
+462 -56
View File
@@ -164,7 +164,8 @@ legend: # un caractère = un type de terrain
".": { type: floor, move_cost: 1 }
"T": { type: rubble, move_cost: 2 } # terrain difficile
"~": { type: water, move_cost: 2 }
"C": { type: pillar, move_cost: null, cover: true } # bloque le pas, couvert
"C": { type: pillar, move_cost: null, cover: true } # bloque le pas, couvert (+4)
"W": { type: ruin, move_cost: null, cover_bonus: 2 } # couvert partiel (+2) ; 4 = standard, 8 = amélioré
elevation: | # optionnel : chiffres = hauteur (appliqué : coût de pas + high ground)
....................
@@ -189,7 +190,10 @@ deployment: # obligatoire si zones : nom de camp -> lettre de zone
Valeur du `move_cost` : entier > 0 pour un terrain praticable, `null` pour un
terrain infranchissable (mur, pilier). `blocks_los` et `cover` sont appliqués
par la résolution des attaques (module `los.py`, règle des coins) : pas de
ligne d'effet ⇒ l'attaque est impossible ; couvert ⇒ bonus de +4 CA.
ligne d'effet ⇒ l'attaque est impossible ; couvert ⇒ bonus de CA. Le champ
optionnel `cover_bonus` (`2`, `4` ou `8`) encode le niveau de couvert PF1e
(partiel +2, standard +4, amélioré +8) ; `cover: true` sans `cover_bonus`
vaut couvert standard (+4), idem pour un terrain `blocks_los`.
### Monstre (JSON)
@@ -237,6 +241,8 @@ Champ `range_increment_ft` (optionnel, uniquement pour `kind: ranged` ou
par incrément complet au-delà du premier ; une arme `ranged` reste utilisable
jusqu'à 10 incréments, une arme `touch` jusqu'à 5 (jet/firearm), au-delà elle
n'est pas sélectionnable. `damage_bonus` et `dr` sont optionnels.
`spell_resistance` (entier), `resistances` (dict type → montant) et
`immunities` (liste de types) sont optionnels.
Deux monstres d'exemple sont fournis : gobelin et orc.
### Fiches de personnages (PJ)
@@ -263,9 +269,37 @@ vers la fiche Foundry d'origine.
Règles modélisées :
- 1 naturel = échec automatique ; 20 naturel = touche + menace de critique.
- Critique : (dés + bonus) × multiplicateur ; la RD s'applique après
multiplication, les types perforants annulent la RD, les dégâts plancher à 0.
- Mort quand `hp < min(-10, -CON)` ; `hp ≤ 0` = inactif.
- Critique : (dés + bonus) × multiplicateur ; le 20 naturel au jet de
confirmation confirme automatiquement la critique (conformance P2 — le jet
de confirmation est un jet d'attaque) ; la RD s'applique après
multiplication, un type de contournement annule la RD — une RD à plusieurs
composants (« DR X/Y and Z », conformance P2) exige que l'attaque porte
TOUS les composants (le contournement n'est plus traité comme un OU), les
dégâts plancher à 0.
- Critique améliorée (« Improved Critical ») : le don double la plage de
menace — plage effective = `21 - 2 × (21 - crit_range)` (20 → 19-20,
19 → 17-20, 18 → 15-20). Il n'affecte que le jet de menace, jamais le jet de
confirmation. Simplification moteur : le don s'applique à toutes les armes
de l'attaquant (pas de sélection par type d'arme comme sur table).
- Mourir et stabilisation : une créature à `hp < 0` mais pas morte est
mourante — inconsciente (inactive), elle perd 1 pv par tour si elle échoue
son jet de Constitution DD 10 à son tour d'initiative : `d20 + mod de CON −
|pv négatifs|` contre 10 ; un 20 naturel réussit automatiquement. En cas d'échec, elle
perd 1 pv de plus ; en cas de réussite, elle est stabilisée (plus de perte de
pv, reste inconsciente). Une nouvelle blessure sous le seuil de stabilisation
(`hp < stabilized_hp`) fait reprendre l'agonie (nouveau jet) ; un soin
ramenant à `hp ≥ 0` réinitialise la stabilisation pour toute chute future.
La mort survient quand les pv négatifs atteignent le score de CON
(`hp ≤ -CON`) — conformance P2 : la règle du −10 de D&D 3.5 n'existe pas
en PF1e et n'est plus modélisée.
- Invalide (conformance P2, CRB « Effects of Death ») : à `hp = 0`
exactement, une créature est **invalide** — elle reste active mais
**staggered** (une action de mouvement OU une action standard par tour, pas
d'action à round complet), 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é.
- `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
mouvement (ou une action à round complet), plus swift/free/immediate. La
@@ -275,6 +309,103 @@ Règles modélisées :
suffit à entrer en portée, ou `(move, attack)` sinon. Les actions immédiates
(hors-tour, consomment le prochain swift) sont modélisées via une politique
de réaction opt-in (`reaction_policy`) — voir « Actions immédiates » ci-dessous.
- Retard (`Action(kind="delay")`, action standard) : le combattant renonce à
agir maintenant et agit plus tard dans le **même round** — en fin d'ordre si
`target_id` est absent, sinon juste avant le combattant désigné. La nouvelle
position est encodée dans la clé d'initiative par un total égal à celui de la
cible − 0,5 (ou au minimum des totaux − 0,5 sans cible) et **persiste aux
rounds suivants** ; le reste du tour est perdu, un second retard dans le même
round est rejeté (`already_delayed`), et le drapeau se réinitialise au début
de chaque round. `default_policy` ne choisit pas le retard (disponible via
une politique personnalisée).
- Préparation (`Action(kind="ready", ready_trigger=...)`, action standard) : le
combattant se prépare à agir quand un événement déclencheur survient. Le
payload est encodé dans les champs porteurs de l'action, comme l'immédiate :
`spell_name` → incantation, `maneuver` → manœuvre (avec `direction` /
`trick_condition`), sinon attaque simple sur `target_id` (les capacités de
classe en payload ne sont pas modélisées). Quatre déclencheurs, mappés aux
événements de réaction existants : `enemy_enters_reach` ← pas de mouvement
d'un ennemi (simplification documentée : la portée menacée n'est pas
recalculée, tout pas ennemi déclenche), `enemy_casts` ← incantation d'un
ennemi, `enemy_attacks` ← attaque résolue d'un ennemi, `enemy_hits_ally` ←
dégâts subis par un allié du préparant infligés par un ennemi. Quand
l'événement correspond, le moteur exécute le payload **hors-tour, sans coût
de swift** (au plus une action préparée par événement, sans cascade), puis
l'action préparée est **consommée** (one-shot). Une nouvelle préparation
remplace la précédente ; le reste du tour (action de mouvement) reste
exécutable. Le snapshot expose l'état via la clé `readied`
(`{kind, trigger}` ou `None`). `default_policy` ne se prépare pas
(disponible via une politique personnalisée).
- Défense totale (`Action(kind="total_defense")`, action standard) : le
combattant se couvre entièrement et gagne **+4 d'esquive à la CA** jusqu'au
début de son prochain tour (champ runtime `defensive_ac_bonus`, réinitialisé
avec les autres drapeaux de tour). Le kind ne requiert pas de cible.
Simplification documentée : la progression d'Acrobatie (+6 avec 3 rangs)
n'est pas modélisée. `default_policy` ne la choisit pas (disponible via une
politique personnalisée).
- Combat défensif (`Action(kind="fight_defensively")`, action standard) : le
combattant se bat avec prudence — **−4 à ses jets d'attaque et de
confirmation de critique**, et **+2 d'esquive à la CA** jusqu'au début de son
prochain tour (drapeaux runtime `fight_defensively` + `defensive_ac_bonus`).
Simplification documentée : la progression d'Acrobatie (−2 / +3 avec 3
rangs) n'est pas modélisée. `default_policy` ne le choisit pas (disponible
via une politique personnalisée).
- Se relever (`Action(kind="stand_up")`, action de mouvement) : le combattant à
plat ventre (renversé par trip/overrun ou après s'être jeté à terre) retire la
condition `prone` des **deux** représentations (`active_conditions` durée,
`conditions` sans durée) et redevient valide. Le relèvement **provoque une
attaque d'opportunité** de chaque ennemi menaçant (résolue via la mécanique de
mouvement `_check_move_aoo`, pendant qu'il est encore à plat ventre, CA −4).
Sans condition `prone`, l'action est rejetée (`not_prone`). Le kind ne requiert
pas de cible. `default_policy` ne la choisit pas (disponible via une politique
personnalisée).
- Se jeter à terre (`Action(kind="drop_prone")`, action libre) : le combattant
ajoute `prone` à la liste `conditions` **sans durée**, donc la condition
persiste jusqu'à `stand_up` (contrairement à trip/overrun qui posent
`active_conditions` avec 1 round). Les modificateurs de `prone` s'appliquent
immédiatement : l'attaquant à plat ventre subit −4 à ses attaques (de mêlée
selon le CRB ; les armes à distance sont traitées comme des arbalètes —
utilisables à −4, simplification documentée), la cible à plat ventre subit
−4 CA contre les attaques de mêlée et gagne +4 CA contre les attaques à
distance. Si la condition est déjà
présente, l'action est rejetée (`already_prone`). Le kind ne requiert pas de
cible. `default_policy` ne la choisit pas (disponible via une politique
personnalisée).
- Aider autrui (`Action(kind="aid_another", aid_mode=...)`, action standard,
U5e) : l'aidant **doit avoir une arme de mêlée utilisable** ; il fait un jet
d'attaque `d20 + bonus d'attaque de sa première arme de mêlée utilisable`
contre le CA 10 (20 naturel réussit toujours, 1 naturel échoue toujours —
convention des jets d'attaque du moteur). Sur succès, la cible empile **+2**
soit à son prochain jet d'attaque (`aid_mode="attack"` ou absent — consommé
one-shot par `resolve_attack`, attaque et confirmation de critique), soit à
sa CA contre la prochaine attaque la ciblant (`aid_mode="ac"`). Plusieurs
aides s'additionnent (le bonus s'ajoute, ne se remplace pas) ; tout bonus
inutilisé expire au début du prochain tour du bénéficiaire. Simplifications
documentées : pas de vérification d'adjacence/ligne d'effet vers l'allié,
toute cible résolue peut être aidée, et la progression de compétence d'aide
(+5 avec 5 rangs, +8 avec 10) n'est pas modélisée. Sans cible →
`missing_target` ; sans arme de mêlée utilisable → `no_weapon`.
`default_policy` ne la choisit pas (disponible via une politique
personnalisée).
- Retour d'exécution des actions : chaque action produit un `ActionResult`
explicite (kind, ok, reason) consigné dans le snapshot (`last_turn_results`
au tour suivant). Les actions invalides sont rejetées et journalisées au lieu
d'être des no-op silencieux : cible absente → `missing_target` ; kinds
déclarés mais non implémentés (`swift`, `free`, `full_round` — leur
implémentation générique reste à faire) → `unsupported_kind` ; dégradation
par les filtres staggered/nauseated (ou swift consommé par une immédiate) →
`downgraded:staggered` / `downgraded:nauseated` / `downgraded:immediate_swift` ;
retard valide → `ok=True, reason="delayed"`, second retard du même round →
`ok=False, reason="already_delayed"` ; préparation valide →
`ok=True, reason="ready"` ; relèvement sans `prone` → `ok=False,
reason="not_prone"` ; se jeter à terre déjà `prone` → `ok=False,
reason="already_prone"` ; attaque avec un `weapon_id` choisi inutilisable →
`ok=False, reason="weapon_unavailable"` ; incantation avec un `target_pos`
hors carte ou sans ligne d'effet → `ok=False, reason="invalid_destination"`
(U7b) ; course ou aide sans cible **et** sans destination →
`missing_target` ; aide sans arme de mêlée utilisable → `no_weapon` ;
course vers une `destination` hors du budget 4× →
`unreachable_destination` (U5e).
- Attaques multiples par action : une arme avec `count` > 1 (« 2x Talons »)
résout `count` balayages indépendants dans la même action ; les balayages
restants sont perdus si la cible tombe (inconsciente ou morte) en cours de
@@ -282,16 +413,47 @@ Règles modélisées :
attaques supplémentaires à -5/-10/-15 ; les armes naturelles (`count` > 1)
ne gagnent pas d'itératifs. Plusieurs armes naturelles en une seule attaque
à outrance ne sont pas modélisées.
Choix de l'arme : `Action(kind="attack", weapon_id=...)` et
`Action(kind="full_attack", weapon_id=...)` frappent avec l'arme désignée
(l'arc plutôt que l'épée, l'arme principale plutôt que la secondaire) au
lieu de l'arme auto-sélectionnée par `weapon_for` (première arme utilisable
capable de toucher la cible à cette distance avec ligne d'effet).
L'`id` doit désigner une arme utilisable (non brisée, non volée) pouvant
atteindre la cible ; sinon l'action est rejetée (`weapon_unavailable`) et
aucun jet n'est effectué. Sans `weapon_id`, la sélection automatique est
inchangée. `default_policy` ne choisit pas d'arme (disponible via une
politique personnalisée).
- Mêlée : allonge effective = max(allonge naturelle du gabarit (Small/Medium
5 ft, Large 10 ft, Huge 15 ft, Gargantuan 20 ft, Colossal 30 ft), `reach_ft`
de l'arme). Une arme à allonge (`reach_ft` > 5) menace l'anneau de cases à
distance égale à son allonge — les cases à 5 ft sont une « dead zone » où
elle ne frappe pas ; une arme sans allonge menace les cases adjacentes.
Mouvement : un déplacement parcourt jusqu'à la
vitesse du combattant en cases le long du plus court chemin réel (champ de
coût Dijkstra depuis la cible, diagonales 5-10-5), en s'arrêtant adjacent à
l'ennemi le plus proche — les combattants contournent les murs au lieu
d'osciller contre eux.
Mouvement : un déplacement parcourt jusqu'à la
vitesse du combattant en cases le long du plus court chemin réel (champ de
coût Dijkstra depuis la cible, diagonales 5-10-5), en s'arrêtant adjacent à
l'ennemi le plus proche — les combattants contournent les murs au lieu
d'osciller contre eux.
Vitesse de déplacement (V4) : le budget de tout mouvement (déplacement,
charge 2×, retraite 2×, course 4×) est le **meilleur entre la vitesse au
sol (`speed_land_ft`) et la vitesse de vol (`speed_fly_ft`)** — une créature
dotée d'une vitesse de vol (harvie : vol 80 vs sol 30, ierlieth : vol 40)
se déplace donc à sa vitesse de vol. Simplification documentée : voler
PAR-DESSUS les obstacles ou le terrain difficile n'est PAS modélisé — la
praticabilité et les coûts de terrain de la grille s'appliquent à tout le
budget (pas de vol 3D ni de maniabilité) ; le snapshot expose ce budget
sous la clé additive `speed_effective_ft`.
Destination explicite : `Action(kind="move", destination=(r, c))` et
`Action(kind="5foot_step", destination=(r, c))` déplacent le combattant vers
la case `(r, c)` désignée (monter au couvert, quitter un défilé, se
désengager) au lieu de auto-diriger vers `target_id`. Pour `move`, la case
doit être praticable, libre et atteignable dans le budget de vitesse (sinon
rejet `unreachable_destination`) ; le trajet réutilise la mécanique pas à pas
avec provocation d'AoO. Pour `5foot_step`, la case doit être exactement
adjacente (sinon `unreachable_destination`) et le pas ne provoque jamais
d'AoO. Sans `destination`, le comportement auto-dirigé est inchangé ; avec
`target_id` et `destination` absents, rejet `missing_target` (U1).
`default_policy` ne choisit pas de destination (disponible via une politique
personnalisée).
- Tailles et occupation multi-cases : une créature occupe un footprint carré à
son ancre (`pos`) selon sa taille — Small/Medium 1×1, Large 2×2, Huge 3×3,
Gargantuan 4×4, Colossal 6×6 (taille inconnue → 1×1). Distance, ligne
@@ -299,6 +461,16 @@ Règles modélisées :
manœuvres et invocation sont généralisés aux cellules occupées (distance =
minimum entre cellules, LoS/couvert = meilleure paire) ; le comportement 1×1
reste bit-identique.
- Taille et attaques/CA : les valeurs `ac` (total/touch/flat_footed) et
`attack_bonus` des JSON de créatures sont des valeurs FINALES, déjà majorées
du modificateur de taille PF1e aux attaques/CA (Small +1, Medium 0, Large −1,
Tiny +2…) — vérifié sur toutes les fiches (`data/monsters/*.json`,
`data/pcs/*.json`, verrouillé par `tests/test_size_is_baked_in.py`). Le
moteur n'applique donc JAMAIS de bonus de taille aux jets d'attaque ni à la
CA (ce serait un double comptage) ; la taille n'intervient dans la
résolution que pour les manœuvres de combat (CMB/CMD, table
`_SIZE_MODIFIERS` Small −1 … Colossal +8 — colonne distincte de la table
attaques/CA), l'allonge naturelle, le footprint et l'occupation.
- Élévation : monter d'un niveau de hauteur coûte +1 case de mouvement par
niveau (la descente est gratuite) ; une créature située plus haut que sa
cible gagne +1 à ses jets d'attaque de mêlée et à la confirmation de
@@ -309,18 +481,24 @@ Règles modélisées :
réaction `Action(kind="immediate")` peut être exécutée hors tour — une par
événement — et consomme le prochain swift de l'état (immédiate pendant le
tour : exécutée, consomme aussi le prochain swift — simplification
documentée).
documentée). Les actions préparées (`ready`) sont traitées sur ces mêmes
événements **avant** et indépendamment de la politique de réaction (voir
« Préparation » plus haut).
- Ligne d'effet : une cible entièrement derrière un terrain `blocks_los` ne
peut pas être attaquée — la politique se déplace jusqu'à gagner une ligne de
visée.
- Couvert (règle des coins) : la cible gagne +4 CA sur la touche et la
- Couvert (règle des coins) : la cible gagne le bonus de couvert du terrain
(+2 partiel, +4 standard, +8 amélioré via `cover_bonus`) sur la touche et la
confirmation de critique ; couvert mou : une créature active entre
l'attaquant et la cible octroie aussi +4 CA aux attaques à distance (les
attaques de mêlée ignorent les créatures).
- Flanquement : +2 au jet d'attaque de mêlée si un allié actif (doté d'une arme
de mêlée) menace la cible depuis la bordure ou le coin opposé. Les attaques à
distance ignorent le flanquement ; un allié désarmé ou à distance ne compte
pas pour le flanquement.
- Flanquement : +2 au jet d'attaque de mêlée (toucher de mêlée inclus, voir
« Touch AC ») si un allié actif menace réellement la cible depuis la
bordure ou le coin opposé — conformance P2 : l'allié doit **menacer** une
case de la cible avec une arme de mêlée utilisable (la « dead zone » de 5 ft
d'une arme allongée ne menace pas) et ne pas être incapable d'agir
(étourdi/paralysé ne flanque pas). Les attaques à distance ignorent le
flanquement ; un allié désarmé ou à distance ne compte pas.
- Attaques d'opportunité : un combattant actif doté d'une arme de mêlée menace
les cases à distance de son allonge effective (les 8 cases adjacentes pour
une arme sans allonge ; l'anneau à son allonge pour une arme reach — la
@@ -329,7 +507,9 @@ Règles modélisées :
la case actuelle porte une AoO avant que le mouvant ne la quitte ; (2) tirer
avec une arme à distance depuis une case menacée — chaque ennemi menaçant
porte une AoO avant chaque tir. Une AoO par combattant par round (piste
`_aoo_used`, réinitialisée au début du round) ; les AoO sont toujours portées
`_aoo_used`, réinitialisée au début du round) ; le budget restant est exposé
dans l'Observation via la clé `aoo_remaining` par combattant ; les AoO sont
toujours portées
(PF1e permet de les décliner, le simulateur ne le fait pas). Le pas de
placement (5 ft) ne provoque jamais d'AoO (voir ci-dessous) ; la retraite ne
protège que la case de départ (voir ci-dessous).
@@ -347,6 +527,18 @@ Règles modélisées :
ultérieures provoquent des AoO normalement (résolues pas à pas, comme pour
le mouvement). `default_policy` ne choisit pas la retraite (disponible via
une politique personnalisée).
- Course (`Action(kind="run")`, action à round complet, U5e) : le combattant
se déplace jusqu'à **4× sa vitesse** vers `target_id` (auto-dirigé,
s'arrête adjacent à la cible) ou vers une `destination` explicite (U5c), en
réutilisant exactement la mécanique de `move` (descente du champ de coût
Dijkstra, pas à pas avec provocation d'AoO — courir ne protège pas des
AoO). À l'achèvement, le combattant est `running` jusqu'au début de son
prochain tour : il **perd son bonus de Dextérité à la CA** (résolu par le
même chemin flat-footed/`denies_dex_to_ac` que les conditions). Une
`destination` hors budget 4× (ou invalide) est rejetée
(`unreachable_destination`) ; sans cible ni destination, rejet
(`missing_target`). `default_policy` ne choisit pas la course (disponible
via une politique personnalisée).
- Pas de placement (5-foot step) : action libre qui déplace le combattant d'une
case vers l'ennemi le plus proche sans provoquer d'AoO. Exclusivité mutuelle
avec tout autre mouvement (move, charge, retraite) dans le même tour —
@@ -366,7 +558,11 @@ Règles modélisées :
avec incrément est une attaque à distance : elle provoque des attaques
d'opportunité depuis une case menacée, subit −4 si l'attaquant est à plat
ventre et bénéficie du couvert mou ; un contact de mêlée (sans incrément,
ex. le contact paralysant du liche) n'en fait rien.
ex. le contact paralysant du liche) n'en fait rien. Conformance P2 : le
contact de mêlée (touch sans incrément) est une **attaque de mêlée** — il
bénéficie du flanquement, de la hauteur (+1), du +4 contre une cible inerme
et peut porter une attaque sournoise ; Power Attack reste réservé aux armes
de mêlée (le contact n'en bénéficie pas).
- Jets de sauvegarde : `resolve_save(state, save_type, dc)` — fort/ref/will.
1 naturel = échec automatique, 20 naturel = succès automatique. Les
modificateurs d'effets (`StatModifier` avec `target` correspondant) sont
@@ -382,34 +578,156 @@ Règles modélisées :
condition inconnue ou non supportée reste exclue (False).
- Incantation : chaque `CombatantState` porte `spell_slots_left` (initialisé depuis
le champ `spell_slots` du JSON, ex. `{"1": 4, ...}`) ; `_cast_spell` dépense
l'emplacement du niveau concerné (`spend_spell_slot`). Le DD vaut `10 + niveau du
l'emplacement du niveau concerné (`spend_spell_slot`) — **sans emplacement
disponible, l'incantation est refusée** (journalisée `NO SPELL SLOT`, aucun
jet consommé) ; les cantrips (niveau 0) s'incantent librement. Le DD vaut `10 + niveau du
sort + mod de la caractéristique d'incantation` (`casting_ability`), la sauvegarde
est résolue par `resolve_save`, et la RD ne réduit pas les dégâts des sorts
(`test_dr_does_not_reduce_spell_damage`). Incanter dans une
case menacée provoque des AoO ; tout dégât subi pendant l'incantation impose un
jet de concentration dont le DD dépend de la situation (`_concentration_dc`) :
`10 + dégâts + niveau` si blessé, `10 + CMB de l'adversaire + niveau` si agrippé,
sinon `15 + 2×niveau` (incantation défensive), jet = d20 + niveau + mod de CON
(+4 avec le don Combat Casting). Un sort ciblé avec sauvegarde fait pleins dégâts
sinon `15 + 2×niveau` (incantation défensive), jet = d20 + niveau + mod de
la caractéristique d'incantation (Int/Wis/Cha — jamais Constitution, CRB) ;
le don Combat Casting n'accorde son +4 qu'en incantation défensive ou quand
on est agrippé, jamais pour les dégâts subis pendant l'incantation. Un sort ciblé avec sauvegarde fait pleins dégâts
sur un échec et **aucun dégât sur un succès** (la sauvegarde annule) ; le
demi-dégât sur sauvegarde réussie ne s'applique que là où l'effet le déclare
(`half_on_save: true` pour les rayons/lignes/cônes, `AreaEffect.save_for_half`
pour les explosions). `default_policy` choisit soin / offensif / buff via
pour les explosions). Ciblage par point (U7b) : `Action(kind="cast_spell",
target_pos=(r, c))` centre l'`AreaEffect` de niveau supérieur du sort sur la
case `(r, c)` désignée au lieu de la position de la cible créature — poser un
Fireball au sol, pas seulement sur une tête. Le point doit être dans la carte
et avoir une ligne d'effet depuis le jeteur (sinon rejet `invalid_destination`,
aucun sort lancé). Avec `target_pos` et pas de cible résolue, l'incantation se
fait sur le point : la sauvegarde primaire est sautée et chaque créature de la
zone effectue sa propre sauvegarde (SR incluse). `target_pos` n'a de sens que
pour un sort à zone : pour un sort ciblé sans `AreaEffect`, il est ignoré et
la cible résolue est utilisée normalement. Sans `target_pos`, le comportement
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`.
- 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
résisté (journalisé, tous les effets annulés pour cette cible). Pour les sorts à
zone, la SR est résolue par créature touchée (pas seulement sur la cible
primaire). Les dégâts énergétiques (feu, froid, électricité, acide, son…) sont
annulés par l'immunité (`immunities`) ou réduits par la résistance du type
correspondant (`resistances` : soustraction du maximum parmi les types du dégât,
plancher 0) ; les types physiques (contondant/perforant/tranchant) restent sur le
chemin de la RD. La résistance énergétique s'applique au dégât final (même étape
que la RD), y compris aux dégâts de sorts et aux composantes énergétiques des
armes.
- Concealment (chance de rater) : les conditions `blurred` (20 %, sort flou),
`displaced` (50 %, concealment total) et `invisible` (50 %, concealment total)
portent un champ `miss_chance` ; quand la
cible en porte une, après la touche et la confirmation de critique, le moteur
tire un d100 : si `d100 ≤ miss_chance`, l'attaque rate complètement (aucun
dégât, pas de critique, deed non consommé — journalisé « concealment »). Une
cible avec concealment ne peut pas être la victime d'une attaque sournoise.
Attaquant aveugle (V5a) : un attaquant portant `blinded` considère tous ses
adversaires comme ayant un concealment total — le même jet unique de d100 est
tiré avec `miss_chance = max(concealment de la cible, 50)` et journalisé
« attacker blinded » ; l'attaque peut donc rater même contre une cible sans
concealment.
- Attaquant invisible (V5a) : un attaquant portant `invisible` gagne **+2 à ses
jets d'attaque et de confirmation de critique** et sa cible **perd son bonus
de Dextérité à la CA** (même chemin `denies_dex_to_ac` que flat-footed/course).
L'invisibilité est symétrique : subi comme cible, la condition accorde le
concealment total de 50 % ci-dessus.
- Cible inerme (helpless, V5a + conformance P1) : sont traitées comme
**inertes** les conditions `helpless`, `paralyzed`, `asleep` et
`unconscious` (CRB : inerme = paralysé, immobilisé, ligoté, endormi,
inconscient — `stunned` **ne** rend **pas** inerme). Les attaques de
**mêlée** contre une cible inerme gagnent **+4 au jet d'attaque et à la
confirmation** (aucun bonus à distance) ; la cible a une **Dextérité
effective de 0** : son modificateur de Dextérité est entièrement remplacé
par la pénalité Dex 0 (−5), en plus de la perte du bonus de Dextérité
(CA flat-footed via `denies_dex_to_ac`).
Simplification documentée : l'attaque sournoise automatique du roublard sur
cible inerme n'est pas modélisée (la cible, privée de Dextérité, la déclenche
déjà via le chemin `denies_dex_to_ac`).
- Attaque sournoise : un combattant avec la capacité « Sneak Attack » inflige des
d6 de dégâts de précision ((niveau + 1) // 2 dés : 1d6 au niveau 1, 2d6 au
niveau 3…) si la cible est flanquée (attaque de mêlée uniquement — toucher de
mêlée inclus, conformance P2) ou privée de
son bonus de Dextérité à la CA (condition `denies_dex_to_ac` ; mêlée, ou
attaque à distance à ≤ 30 ft). Les dés sont jetés avec le RNG du moteur et
ajoutés APRÈS le multiplicateur de critique (la précision n'est jamais
multipliée) et AVANT la RD et les résistances énergétiques ; jamais contre une
cible avec concealment. Conformance P2 : les morts-vivants (`undead`) et les
constructeurs (`construct`) sont immunisés aux dégâts de précision — la cible
ne peut pas être la victime d'une attaque sournoise ; les autres immunités RAW
(vase, plante) ne sont pas modélisées (pas de tels types dans le modèle).
- Tirer dans une mêlée / Precise Shot : toute attaque à distance (kind `ranged`
ou `touch` avec incrément) contre une cible engagée dans une mêlée subit −4 au
jet d'attaque ET au jet de confirmation (la cible est « engagée » quand un
ennemi actif autre que l'attaquant, doté d'une arme de mêlée, menace ses
cases) ; la pénalité tombe à −2 si la cible ou l'adversaire menaçant est à
≥ 10 ft (2 cases) de l'attaquant. Le don « Precise Shot » annule la pénalité.
Les attaques de mêlée ne sont pas concernées.
- Couvert : les trois niveaux PF1e sont modélisés via `cover_bonus` sur le
terrain — partiel +2, standard +4 (ou `cover: true`), amélioré +8 ; le
couvert mou des créatures reste +4 (couvert standard).
- Invocation : les créatures invoquées agissent (calées sur la case d'initiative de
leur conjurateur via `_build_round_order`) puis expirent après leur `summon_duration`
(`_expire_summons` décrémente puis retire).
- Conditions effectives : 18 conditions prédéfinies (`conditions.py`). Leurs drapeaux
- Conditions effectives : 28 conditions prédéfinies (`conditions.py`). Leurs drapeaux
comportementaux sont appliqués par le moteur : `cant_act` (étourdi, paralysé, dazed,
endormi) annule le tour ; `cant_attack` (nauséeux) n'autorise qu'une unique action
simple et bloque toute attaque / manœuvre ; `staggered` restreint à une action
endormi, inerme, fasciné) annule le tour ; `cant_attack` (nauséeux) n'autorise qu'une unique action
de mouvement (`move`, `5foot_step`, `stand_up`, `drop_prone`) et bloque toute
attaque, manœuvre **et incantation** (CRB : un nauséeux ne peut ni attaquer,
ni incanter, ni se concentrer) ; `staggered` restreint à une action
simple (pas d'attaque à outrance) ; `cant_move` bloque le déplacement ;
`denies_dex_to_ac` (à plat ventre, paralysé…) fait perdre le bonus de Dextérité à
la CA (CA flat-footed). À plat ventre : −4 CA et −4 aux attaques à distance.
Agrippé : −4 Dextérité, −2 attaque/CA/Réflexes, sans déplacement ni AoO.
`denies_dex_to_ac` (flat-footed, paralysé, aveuglé…) fait perdre le bonus de Dextérité à
la CA (CA flat-footed) ; `miss_chance` (flou 20 %, déplacement 50 %, invisible
50 %) impose la
chance de rater du concealment. À plat ventre : l'attaquant à plat ventre
subit −4 à ses attaques (mêlée selon le CRB ; distance traitée arbalète à
−4, simplification documentée) ; la cible à plat ventre subit −4 CA contre
la mêlée et gagne +4 CA contre la distance (le CRB inverse du « −4 CA
contre tout » précédemment modélisé).
Agrippé : −4 Dextérité — le modificateur cible `dex_skills` s'applique
désormais réellement aux compétences de Dexterity (Acrobaties, Escalade,
Équilibre, Évasion, Vol, Équitation, Manipulation de corde, Discrétion,
Natation, Désamorçage — conformance P2) —, −2 attaque/CA/Réflexes, sans
déplacement ni AoO. Épinglé : même −4 `dex_skills`.
Aveuglé (conformance P2) : −2 CA, perte du bonus de Dextérité à la CA, −4
aux compétences de Dexterity (`dex_skills`) et **aucun malus plat
d'attaque** — la pénalité RAW est la dissimulation totale (50 % d'échec,
modélisée) ; le −2 d'attaque non-RAW qui s'y superposait a été supprimé.
Simplification documentée : la moitié Force du −4 RAW (compétences de
Force) n'est pas modélisée (pas de cible `str_skills`).
Inerme (`helpless`, `paralyzed`, `asleep`, `unconscious`) : ne peut pas agir,
Dextérité effective 0 (−5 CA en plus de la perte du bonus), +4 en mêlée
contre lui (voir « Cible inerme ») ; `stunned` ne rend pas inerte. Fasciné (`fascinated`) : ne peut pas agir.
Invisible (`invisible`) : concealment total subi +2 attaquant / perte de Dex
de la cible (voir « Attaquant invisible »).
Saignement (`bleeding`, V5b) : une créature qui saigne subit `bleed_damage`
points de dégâts au **début de son tour** (avant politique et flags) ; tout
effet restaurant des points de vie (sort de soin, channel energy) stoppe le
saignement (`bleed_damage = 0`) et retire la condition. Simplification
documentée : le jet de compétence Soin DD 15 n'est pas modélisé (le moteur
n'a pas d'action Soin).
Confus (`confused`, V5b) : au début de chaque tour, jet d% — 01-25 agit
normalement (le tour se poursuit) ; 26-50 bave incohéremment (tour perdu) ;
51-75 se inflige 1d8 + modificateur de Force avec l'objet tenu ; 76-100
attaque d'une seule attaque de mêlée la créature la plus proche (allié OU
ennemi, une seule si en allonge ; sinon le tour est perdu, journalisé).
Simplification documentée : la clause « si elle est blessée, riposte
l'agresseur » est réduite à « attaque la créature la plus proche » ; le coup
de grâce n'est pas modélisé.
- Manœuvres de combat : trip, disarm, bull_rush, sunder, grapple, escape,
overrun, dirty trick, steal, reposition et drag sont modélisées, chacune
comme une tentative unique (1d20 + CMB contre CMD) :
comme une tentative unique (1d20 + CMB contre CMD) : le jet de manœuvre est
un jet d'attaque — 20 naturel réussit toujours, 1 naturel échoue toujours
(conformance P2). Conformance P2 : tenter une manœuvre (sauf `escape`)
**provoque une attaque d'opportunité** de la cible, résolue **avant** le jet
de CMB ; le don « Improved <Maneuver> » correspondant (Improved Trip, …)
annule cette provocation ; si l'AoO fait passer l'attaquant sous 0 pv, la
manœuvre est perdue. L'AoO de manœuvre consomme le budget d'AoO du round de
la cible (Combat Reflexes inclus).
trip → `prone` ; disarm → `disarmed` (−4 attaque) ; bull_rush → pousse la
cible (direction sélectionnable via `Action.direction`) ;
grapple → `grappled`/`pinned` ; escape → **une** sauvegarde (bonus statique de la
@@ -433,8 +751,12 @@ Règles modélisées :
- Sortilèges du sorcier (witch hex) : le DD vaut `10 + niveau//2 + mod d'Intelligence`
(evil_eye, slumber, cackle) ; `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, sur une sauvegarde réussie,
ne font que la moitié des dégâts.
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
annule ou divise par deux selon ce que l'effet déclare (`save_negates` /
`half_on_save` / `AreaEffect.save_for_half`) — les zones « sauvegarde
annule » (Sleep, Color Spray, Web) sauvent donc aussi.
- Dons : les 8 PJ portent 55 noms de dons uniques ; le chargeur résout chacun d'eux
soit vers un effet réel (Power Attack actif, Weapon Focus ciblé par arme, Toughness
lié au niveau, dons passifs via `_stat_modifiers`), soit vers un « no-op » tracé en
@@ -446,12 +768,18 @@ Règles modélisées :
moins 2 morts-vivants ennemis sont groupés à 30 ft → les blesse en 2d6 ; le camp de la
cible n'est pas pris en compte — seul `creature_type` compte, `selective_channeling` non
modélisé), sortilège `evil_eye` (nairda), deed `up_close_and_deadly`
(tammara, action bonus +1d6 à ≤ 30 ft), `inspire_courage` + `spell_combat` (misty)
(tammara, action bonus +1d6 à ≤ 30 ft — consommé sur la première attaque
résolue : si elle est à distance ET à ≤ 30 ft, +1d6 s'ajoute aux dégâts ;
sinon le bonus est perdu ; le drapeau est réinitialisé dans tous les cas),
`inspire_courage` + `spell_combat` (misty)
— couvertes par `tests/test_class_abilities.py`.
- Le Liche (`data/monsters/lich.json`) : `creature_type: "undead"` (sensible à l'énergie
positive canalisée), aura de terreur (30 ft, Volonté DD 22 ou
`shaken` 1 round, au début de son tour) et contact paralysant (au toucher,
`paralysé` 1d4+1 rounds), RD 15/magie+contondant ; son JSON porte un dictionnaire
`paralysé` 1d4+1 rounds), RD 15/contondant **et** magie (conformance P2 :
les deux composants sont requis — une arme contondante non magique ne
contourne plus la RD ; lacune documentée : le modèle d'arme n'expose pas de
propriété « magique », donc aucun équipement ne contourne cette RD) ; son JSON porte un dictionnaire
`spell_slots` sans clés dupliquées.
- Snapshot d'observation (contrat LLM, Phase 2) : avant chaque appel de
politique, le moteur construit une `Observation` sérialisable (`to_dict()` →
@@ -460,34 +788,104 @@ Règles modélisées :
se branche via `CombatEngine(..., llm_policy=...)` (`LLMPolicy(observation,
state) -> tuple[Action, ...]`). Schéma enrichi (E1-E5) :
- Par combattant : id, side, pos, occupied_cells, hp/hp_max, active/dead,
conditions (noms + rounds_left), resources_left (+ `spell_slot_{level}`),
dying/stabilized, conditions (noms + rounds_left), resources_left (+ `spell_slot_{level}`),
weapon_broken/weapon_stolen, grappled_with, moved_this_turn, power_attack,
name, level, size, speed_land_ft, speed_fly_ft, ac {total, touch,
flat_footed}, saves {fort, ref, will}, cmb, cmd, attacks (référentiel
complet : id, name, kind, attack_bonus, damage, damage_bonus, crit_range,
crit_mult, reach_ft, range_increment_ft, count, firearm — y compris les
armes broken/stolen), features (noms des capacités), spells (résumés
enrichis du registre : name, level, school, range_ft, save, dc, summary).
name, level, size, dr {amount, bypass} ou None (C1), creature_type (C3),
bab (E1), initiative_mod (E2) et initiative_total (U2 : total du jet
d'initiative courant = d20 + mod, 0 si pas encore de clé — invocation
fraîche), casting_ability ou None pour non-jeteur
(E3), abilities {str/dex/con/int/wis/cha: score brut} (Medium), skills
{nom: {rank, class_skill, armor_penalty}} uniquement rank > 0 (Medium),
metamagic_feats (tuple trié), weapon_damage {weapon_id: dégâts courants},
summon_duration (int ou None), immediate_used, deed_up_close_and_deadly
(flags actuels), active_buffs {source, rounds_left, mods: tuple de
{target, value, bonus_type}} (E5), speed_land_ft, speed_fly_ft,
speed_effective_ft (V4 : budget de déplacement effectif = max(land, fly),
en pieds), ac {total,
touch, flat_footed}, saves {fort, ref, will}, cmb, cmd, attacks
(référentiel complet : id, name, kind, attack_bonus, damage, damage_bonus,
crit_range, crit_mult, reach_ft, range_increment_ft, count, firearm —
y compris les armes broken/stolen), features (noms des capacités),
feature_details (C4 : tuple de {name, category, source_detail, effects:
tuple de {target, value, bonus_type, condition, weapon_filter}}),
spells (résumés enrichis du registre : name, level, school, range_ft,
save, dc, summary, half_on_save, save_negates (E4), area {shape,
radius_ft} | None (U7a : premier AreaEffect de niveau supérieur du sort,
gabarit de zone structuré, None si le sort n'a pas de zone)),
spell_resistance (int), resistances {type: montant}, immunities
(tuple triée des types immunisés), sneak_dice (dés de précision disponibles :
(niveau + 1) // 2 avec « Sneak Attack », sinon 0), engaged_in_melee (bool —
une cible engagée dans une mêlée, signal pour le tir dans la mêlée et
l'attaque sournoise), dying (bool — `hp < 0` et pas morte : agonisante,
inconsciente), stabilized (bool — stabilisée pendant son agonie, ne perd
plus de pv tant qu'aucune nouvelle blessure ne la remet en agonie),
last_turn_results (tuple de {kind, ok, reason} — résultat d'exécution du
dernier tour, pour que la policy apprenne les actions rejetées/dégradées),
readied ({kind, trigger} de l'action préparée ou None — préparation en
cours, consommée quand le déclencheur la fait tirer),
defensive_ac_bonus (int — bonus d'esquive à la CA de la défense totale
(+4) ou du combat défensif (+2), réinitialisé au début du tour du
combattant), fight_defensively (bool — combat défensif actif : −4 aux
jets d'attaque et de confirmation jusqu'au prochain tour), aoo_remaining
(int — attaques d'opportunité restantes ce round : `_aoo_allowance`
(1 par défaut, 1 + mod de Dex avec Combat Reflexes, plancher 1) moins les
AoO déjà consommées, réinitialisé à chaque nouveau round, jamais négatif),
aid_attack_bonus (int — bonus d'aide autrui (+2 par aide réussie, cumulatif)
ajouté au prochain jet d'attaque du combattant puis consommé, expiré au
début de son tour s'il est inutilisé), aid_ac_bonus (int — bonus d'aide
autrui (+2 par aide réussie, cumulatif) ajouté à sa CA, expiré au début de
son tour), running (bool — course en cours jusqu'au prochain tour du
combattant : il perd son bonus de Dextérité à la CA), bleed_damage (int —
dégâts de saignement subis au début du tour du combattant, 0 = ne saigne
pas ; V5b ; `bleeding`/`confused` sont visibles via `conditions`).
- Par paire combattant actif × cible : target_index, distance_ft, weapon_for,
range_penalty, can_charge, can_step5_to_attack, has_line_of_effect,
flanking, movement_cost_ft, target_threatens_me, enemies_threatening_me.
flanking, movement_cost_ft, target_threatens_me, enemies_threatening_me,
cover_bonus (int — bonus de couvert que la cible gagne contre une attaque
de l'acteur, calculé comme dans `resolve_attack` : terrain +2/+4/+8 via la
règle des coins, couvert mou +4 des créatures interposées pour les seules
attaques à distance ; 0 si `weapon_for` est None), weapon_reason (str |
None — None si `weapon_for` a choisi une arme ; sinon la raison normalisée
de l'indisponibilité d'attaque, dans le vocabulaire `ACTION_RESULT_REASONS` :
`no_weapon` (aucune arme utilisable), `no_line_of_effect` (ligne d'effet
bloquée), `out_of_range` (arme existante mais cible hors de portée)),
charge_reason (str | None — None si `can_charge` est vrai ; sinon la
raison de `ChargeResult` verbatim (`no_line_of_effect` / `no_charge_path`)
ou `no_weapon` si aucune arme de mêlée utilisable).
- Top-level : round, acting_combatant, combatants, pairs, terrain, elevation,
threatened (grille bool des cases menacées par les ennemis),
threatened (grille bool des cases menacées au contact par les ennemis —
AoO), ranged_threatened (U6d : grille bool des cases sous tir ennemi à
distance — champ de tir des armes à distance utilisables des ennemis
actifs (portée maximale + ligne d'effet, bornée à la carte) ; les armes à
distance ne menacent pas pour les AoO en PF1e : c'est une carte de champ
de tir, pas une menace d'AoO ; la grille `threatened` de mêlée reste
inchangée),
initiative_order (U2 : tuple d'ids dans l'ordre d'initiative du round
courant — le premier est le prochain à agir ; les invocations héritent de la
case de leur conjurateur),
terrain_semantics (un dict par type de terrain : type, move_cost,
blocks_los, cover).
blocks_los, cover, cover_bonus), condition_definitions (C2 : tuple trié de (nom,
{mods, flags, speed_mult, miss_chance}) — définitions complètes des 28
conditions de CONDITIONS_BY_NAME pour que la LLM interprète les drapeaux,
les modificateurs et les chances de rater (concealment)).
Les helpers publics du moteur (L5) sont désormais embarqués EN DONNÉES dans
le snapshot (`threatened`, `terrain_semantics`, `movement_cost_ft`,
`target_threatens_me`, `enemies_threatening_me`) — une `LLMPolicy` ne peut
pas appeler `engine.*`, elle lit leurs résultats. Mapping des clés
`resources_left` (vérifié par grep sur `spend_resource`/`resources_left[` :
`channel_energy`, `hexes`, `grit`, `arcane_pool`, `bardic_rounds`, plus
`spell_slot_{level}` ajouté par le snapshot). Limites documentées : pas de
grille `reachable_ft` budget=speed (délibérément rejetée —
`movement_cost_ft` par paire + `terrain_semantics` + `speed_land_ft`
suffisent), pas de couvert par paire (déductible via `has_line_of_effect` +
`terrain_semantics` + positions). Les API de décision exposées (items L1-L8)
`spell_slot_{level}` ajouté par le snapshot). Limites documentées : pas de
grille `reachable_ft` budget=speed (délibérément rejetée —
`movement_cost_ft` par paire + `terrain_semantics` + `speed_effective_ft`
suffisent). Le couvert par paire est exposé via la clé `cover_bonus`
(calcul identique à `resolve_attack`, couvert mou des créatures inclus).
Les API de décision exposées (items L1-L8)
s'y retrouvent : `direction`/`trick_condition`/`weapon_id` sur `Action`,
`can_charge` → `ChargeResult`, kinds `retrieve_weapon`/`repair_weapon`.
Le kind `activate_power_attack` (action libre) active le don Power Attack
(`state.power_attack = True`) si le combattant possède le don ; sans le don,
c'est un no-op journalisé. Le snapshot expose l'état courant via la clé
`power_attack` (bool) par combattant.
Non modélisé : la couche `markers` (points nommés de la carte) est validée à
la lecture mais non appliquée dans la résolution.
@@ -506,8 +904,8 @@ la lecture mais non appliquée dans la résolution.
règle des coins (`diagonal_allowed`), Dijkstra `reachable` avec ou sans
budget.
- `los.py` — ligne d'effet et couvert par la règle des coins : gate de ligne
d'effet dans `weapon_for`, bonus de couvert +4 CA (terrain et couvert mou
des créatures) dans `resolve_attack`.
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`.
- `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
@@ -527,10 +925,12 @@ la lecture mais non appliquée dans la résolution.
enhancement…) gardent la valeur la plus élevée. Les pénalités suivent les
mêmes règles. Fondation pour sorts, conditions, dons et capacités de classe.
- `conditions.py` — conditions PF1e : `Condition` (dataclass avec
modificateurs + flags comportementaux) et 18 conditions prédéfinies
modificateurs + flags comportementaux + `miss_chance`) et 28 conditions prédéfinies
(shaken, sickened, fatigued, exhausted, entangled, dazzled, blinded,
deafened, flat-footed, prone, stunned, paralyzed, nauseated, dazed,
staggered, frightened, panicked, cowering). Les modificateurs sont intégrés
staggered, frightened, panicked, cowering, helpless, fascinated, invisible,
bleeding, confused).
Les modificateurs sont intégrés
à `resolve_attack` (attaque, dégâts, CA) et `resolve_save` (jets de
sauvegarde) via `_stat_modifiers` qui collecte effets + conditions ; les
drapeaux comportementaux (`cant_act`, `cant_attack`, `cant_move`, staggered,
@@ -538,8 +938,9 @@ la lecture mais non appliquée dans la résolution.
- `abilities.py` — dons et capacités : `AbilitySpec` (dataclass avec
catégorie feat/class/racial/trait) et fabriques de dons (Weapon Focus ciblé
par arme, Toughness lié au niveau, Power Attack actif −X attaque / +2X
dégâts, X = BAB//4 + 1, mêlée uniquement, declared as free action at turn
start via `state.power_attack`). Les modificateurs sont collectés par
dégâts, X = BAB//4 + 1, mêlée uniquement, activé comme action libre via le
kind `activate_power_attack` qui pose `state.power_attack`). Les
modificateurs sont collectés par
`_stat_modifiers` avec filtrage par arme (`weapon_filter`). Le champ
`features` de `Combatant` contient les aptitudes permanentes ; le chargeur
(`loaders/combatant.py`) résout les noms de dons des fiches vers des
@@ -589,7 +990,7 @@ uv run basedpyright src # mode strict
## Feuille de route
- **Phase 1** — magie et états : largement réalisée. Sorts par emplacements
(`spells.py` + base `data/spells/`, 11 sorts), DD et sauvegardes, RD non
(`spells.py` + base `data/spells/`, 107 sorts), DD et sauvegardes, RD non
appliquée aux dégâts de sorts, concentration par situation, invocation,
conditions effectives (`conditions.py`), manœuvres de combat, dons (55 noms
résolus, `abilities.py` + chargeur) et capacités de classe (channel energy,
@@ -602,6 +1003,11 @@ uv run basedpyright src # mode strict
- **Phase 2** — couche tactique LLM : stratégies en langage naturel traduites
en politiques, balayage de matrices de positionnement. Le contrat
d'observation (snapshot sérialisable `Observation` + wrapper `LLMPolicy`)
est en place ; la couche LLM elle-même reste à implémenter.
est en place ; la couche LLM elle-même reste à implémenter. Les capacités
désormais débloquées du moteur (ready, delay, défense totale, destination
de déplacement, choix d'arme, boucle de feedback) sont démontrées par une
politique lisant uniquement l'`Observation` dans
`tests/test_llm_engine_capabilities.py` (démo de capacités, pas une
implémentation de la couche LLM).
- **Phase 3** — rapporteur LLM local : agrégation des statistiques et
rédaction du rapport d'équilibrage en français. (Non implémentée.)
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Abundant Ammunition",
"level": 0,
"school": "transmutation",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip that conjures duplicate ammunition: attacks with the affected ammunition do not expend it. No combat-mechanical effect in the engine (ammunition is not tracked)."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Acid Splash",
"level": 0,
"school": "conjuration",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "1d3",
"types": ["acid"],
"half_on_save": false
}
],
"description": "You fire a small orb of acid at the target, dealing 1d3 points of acid damage (ranged touch attack, no save)."
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "Aid",
"level": 1,
"school": "enchantment",
"range": "long",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "attack", "value": 1, "bonus_type": "morale" },
{ "target": "will", "value": 1, "bonus_type": "morale" }
],
"duration_rounds": 10
}
],
"description": "The subject gains a +1 morale bonus on attack rolls and on saving throws against fear effects (modeled as Will). Duration 10 min/level approximated as 10 rounds."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Air Bubble",
"level": 1,
"school": "conjuration",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "You create a sphere of air around your head (or a touched creature's) that lasts 10 min/level, allowing breathing underwater. No combat-mechanical effect in the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Alarm",
"level": 1,
"school": "abjuration",
"range": "medium",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "Wards a area (20-ft radius, 20 ft high) against intrusion for 2 hours/level, alerting you mentally. No combat-mechanical effect in the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Arcane Mark",
"level": 0,
"school": "divination",
"range": "touch",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "Creates a permanent visible inscribed mark of your choosing on an object or surface, optionally invisible and keyed to you. Utility cantrip, no combat effect."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Bane",
"level": 1,
"school": "enchantment",
"range": "medium",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "shaken",
"duration_rounds": 10,
"save_negates": true
}
],
"description": "Enemies within range take a -1 penalty on attack rolls and saving throws (approximated with the shaken condition, -2). Will negates. Concentration 1 min/level approximated as 10 rounds."
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "Bear's Endurance",
"level": 2,
"school": "transmutation",
"range": "touch",
"save": "fort",
"base_dc": 10,
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "fort", "value": 2, "bonus_type": "enhancement" },
{ "target": "hp", "value": 2, "bonus_type": "enhancement" }
],
"duration_rounds": 60
}
],
"description": "The subject gains a +4 enhancement bonus to Constitution, granting +2 Fortitude and +2 hit points (approximated as +2/+2). Fortitude negates (harmless). Duration 10 min/level approximated as 60 rounds."
}
+18
View File
@@ -0,0 +1,18 @@
{
"name": "Bladed Dash",
"level": 1,
"school": "transmutation",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "speed_ft", "value": 30, "bonus_type": "enhancement" }
],
"duration_rounds": 1
}
],
"description": "You move up your speed as a move action and make one melee attack at the end. Simplified as a +30 ft speed buff for 1 round; the extra attack is not modeled."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Bleed",
"level": 0,
"school": "necromancy",
"range": "close",
"save": "fort",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "bleeding",
"duration_rounds": 1,
"save_negates": true
}
],
"description": "A cantrip that causes a wound to bleed: the target takes 1 point of damage per round until healed. Fortitude negates. Modeled as the bleeding condition for 1 round."
}
+26
View File
@@ -0,0 +1,26 @@
{
"name": "Bless",
"level": 1,
"school": "enchantment",
"range": "medium",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 20,
"save_for_half": false,
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "attack", "value": 1, "bonus_type": "morale" },
{ "target": "will", "value": 1, "bonus_type": "morale" }
],
"duration_rounds": 10
}
]
}
],
"description": "All allies within 20 ft/level (modeled as a 20-ft burst applied to all creatures in area) gain a +1 morale bonus on attack rolls and on saving throws against fear effects (modeled as Will). Duration 1 min/level approximated as 10 rounds."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Blindness/Deafness",
"level": 2,
"school": "necromancy",
"range": "medium",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "blinded",
"duration_rounds": 100,
"save_negates": true
}
],
"description": "The target becomes blind or deaf (modeled as blinded) until removed by a caster of higher level. Permanent duration approximated as 100 rounds. Will negates."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Charm Person",
"level": 1,
"school": "enchantment",
"range": "medium",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "fascinated",
"duration_rounds": 100,
"save_negates": true
}
],
"description": "The charmed person regards the caster as a trusted friend (modeled as the fascinated condition; the charmed condition is not in the engine). Duration 1 hour/level approximated as 100 rounds. Will negates."
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "Color Spray",
"level": 1,
"school": "enchantment",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 15,
"save_for_half": false,
"effects": [
{
"type": "condition",
"condition_name": "stunned",
"duration_rounds": 1,
"save_negates": true
}
]
}
],
"description": "A cone of chaotic colors (modeled as a 15-ft burst) blinds and stuns creatures with fewer HD than your level for 1d4 rounds (approximated as 1 round of stunned). Will negates."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Comprehend Languages",
"level": 0,
"school": "divination",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip that lets you understand the meaning of any spoken or written language you encounter for 1 min/level. Utility spell, no combat effect."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Create Alcool",
"level": 0,
"school": "conjuration",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip (Create Alcohol, spelled as on the PC sheet) that transforms one liquid per level into an alcoholic drink of equal volume. Utility spell, no combat effect."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Create Water",
"level": 1,
"school": "conjuration",
"range": "medium",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "Conjures 4 gallons of clean water per level in an open space or container. Utility spell, no combat effect."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Dancing Lights",
"level": 0,
"school": "conjuration",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip that creates up to four flickering torch-like lights you move as desired (concentration, 1 min/level). Utility spell, no combat effect."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Darkness",
"level": 2,
"school": "evocation",
"range": "touch",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 20,
"save_for_half": false,
"effects": []
}
],
"description": "All light in a 20-ft radius (spread from the touched point) becomes supernatural darkness; creatures inside lose visual sight-based advantages for 10 min/level. The engine does not model area concealment, so the burst carries no mechanical effect."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Daze",
"level": 0,
"school": "enchantment",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "dazed",
"duration_rounds": 1,
"save_negates": true
}
],
"description": "A cantrip that clouds the mind of a humanoid creature with 5 or fewer HD, making it unable to act for 1 round. Will negates."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Deja Vu",
"level": 0,
"school": "divination",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip that lets you reroll one attack roll, saving throw, or skill check made in the past round (once per casting). Not modeled by the engine's action flow, so it carries no mechanical effect."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Delay Disease",
"level": 3,
"school": "abjuration",
"range": "touch",
"save": "fort",
"base_dc": 10,
"effects": [],
"description": "You suppress the progression of one disease the subject carries for 1 hour/level, keeping it dormant. Utility spell, no combat-mechanical effect in the engine. Fortitude negates (harmless)."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Delay Poison",
"level": 2,
"school": "abjuration",
"range": "touch",
"save": "fort",
"base_dc": 10,
"effects": [],
"description": "You temporarily stave off incoming poison or eliminate one already in the subject's system for 1 hour/level. Utility spell, no combat-mechanical effect in the engine. Fortitude negates (harmless)."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Detect Fiendish Presence",
"level": 0,
"school": "divination",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip that reveals whether a creature of the outsider (evil) subtype has been in the area within 10 minutes/HD, and hints at its nature. Utility spell, no combat effect."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Detect Magic",
"level": 0,
"school": "divination",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip that detects magical auras in a 60-ft cone (2 rounds to identify strength and school). Utility spell, no combat effect."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Detect Poison",
"level": 0,
"school": "divination",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip that determines whether a creature, object, or area is poisoned, venomous, or afflicted by acid. Utility spell, no combat effect."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Disrupt Undead",
"level": 0,
"school": "necromancy",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "1d6",
"types": ["force"],
"half_on_save": false
}
],
"description": "A cantrip: a line of crackling energy strikes one undead creature, dealing 1d6 points of damage (ranged touch attack, no save). The engine does not restrict the damage to undead targets."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Divine Favor",
"level": 1,
"school": "evocation",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "You draw upon divine power for 1 round/level: a +1 luck bonus on attack rolls and weapon damage per 4 caster levels. Simplified: the engine has no luck-bonus self-buff that preserves the pinned default-policy casting order, so no mechanical effect is applied."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Doom",
"level": 1,
"school": "necromancy",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "shaken",
"duration_rounds": 10,
"save_negates": true
}
],
"description": "The target is paralyzed with fear and becomes shaken (-2 on attacks and saves; on a successful save it takes -1 instead, not modeled). Will negates. Duration 1 min/level approximated as 10 rounds."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Drunkard's Breath",
"level": 1,
"school": "abjuration",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "For 1 hour/level you gain DR 5/- against alcohol effects and cannot become drunk. The engine has no DR buff target, so no mechanical effect is applied."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Endure Elements",
"level": 1,
"school": "abjuration",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "For 24 hours you tolerate comfortable extremes of heat and cold (from -50 to 140 degrees F) without ill effect. Environmental protection, no combat-mechanical effect in the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Enhance Water",
"level": 2,
"school": "abjuration",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "You purify and bless 1 gallon/level of water (or one vial of holy/unholy water). Utility spell, no combat-mechanical effect in the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Enhanced Diplomacy",
"level": 0,
"school": "divination",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A cantrip: the subject gains a +2 competence bonus on a single Diplomacy or Intimidate check, declared before the roll (duration 1 minute or until discharged). Will negates (harmless). Social skill effect only: the engine does not model skill-check modifiers, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Face of the Devourer",
"level": 1,
"school": "transmutation",
"range": "touch",
"save": "fort",
"base_dc": 10,
"effects": [],
"description": "The target's face becomes hideous: it gains a +4 circumstance bonus on Intimidate checks and, if it lacks one, a bite attack (1d6 for Medium) for 1 min/level. Fortitude negates (harmless). The skill bonus and the granted natural attack are not modeled by the engine, so the spell has no mechanical effects here."
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "Flurry of Snowballs",
"level": 2,
"school": "evocation",
"range": "close",
"save": "ref",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 20,
"save_for_half": true,
"effects": [
{
"type": "damage",
"formula": "4d6",
"types": ["cold"],
"half_on_save": true
}
]
}
],
"description": "A flurry of snowballs hurtles at your foes: any creature in the area takes 4d6 points of cold damage. Reflex half. The RAW 30-ft cone-shaped burst is approximated as a 20-ft burst centered on the target point."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Forbid Action",
"level": 1,
"school": "enchantment",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "dazed",
"duration_rounds": 1,
"save_negates": true
}
],
"description": "You forbid the target a single course of action (attack, cast, communicate, draw, or move), which it avoids to the best of its ability for 1 round. Will negates. Simplification: the chosen-category restriction is approximated as dazed (the target loses its next turn); the specific forbidden action is not modeled."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Grasp",
"level": 0,
"school": "divination",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip cast as an immediate action when you fail a Climb check and would fall: you immediately retry the Climb check at a -2 penalty (increasing by 2 per successive use in the same situation); on success you don't fall. Climbing is not modeled by the engine, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Guidance",
"level": 0,
"school": "divination",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A cantrip: the subject gains a +1 competence bonus on a single attack roll, saving throw, or skill check, declared before the roll (duration 1 minute or until discharged). Will negates (harmless). Single-roll discharge is not modeled by the engine, so the spell has no mechanical effects here."
}
+21
View File
@@ -0,0 +1,21 @@
{
"name": "Heroism",
"level": 2,
"school": "enchantment",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "attack", "value": 2, "bonus_type": "morale" },
{ "target": "fort", "value": 2, "bonus_type": "morale" },
{ "target": "ref", "value": 2, "bonus_type": "morale" },
{ "target": "will", "value": 2, "bonus_type": "morale" }
],
"duration_rounds": 10
}
],
"description": "The subject gains a +2 morale bonus on attack rolls, saving throws, and skill checks. Duration 10 min/level approximated as 10 rounds; the skill-check bonus has no engine-wide target and is not modeled. Will negates (harmless)."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Hex Vulnerability",
"level": 1,
"school": "necromancy",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "The target becomes susceptible to a repeat use of your harmful hexes for 1 round/level, bypassing the once-per-day targeting limit. Will negates. No combat effect: the hex-reuse permission is outside the engine's model, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Hidden Diplomacy",
"level": 1,
"school": "enchantment",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "As part of casting, you attempt a single Intimidate check to make the target act friendly; its memory of the threats is clouded, so its attitude does not worsen afterward. Will negates. Social effect only: no combat mechanics are modeled."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Hide from Undead",
"level": 1,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "Undead cannot perceive warded creatures (nonintelligent undead ignore them automatically; intelligent undead get a single Will save) until the subject attacks, channels energy, or touches undead. Duration 10 min/level. Saving throw entries here cover the harmless-target save; the undead-perception effect is not modeled by the engine."
}
+30
View File
@@ -0,0 +1,30 @@
{
"name": "Ice Slick",
"level": 2,
"school": "evocation",
"range": "close",
"save": "ref",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 5,
"save_for_half": true,
"effects": [
{
"type": "damage",
"formula": "1d6",
"types": ["cold"],
"half_on_save": true
},
{
"type": "condition",
"condition_name": "prone",
"duration_rounds": 1,
"save_negates": true
}
]
}
],
"description": "A blast of intense cold coats a 5-ft-radius burst in ice: creatures in the area take 1d6 points of cold damage (Reflex half) and fall prone (no prone on a successful save). The lingering slippery surface (half speed, Acrobatics checks) is not modeled."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Ill Omen",
"level": 1,
"school": "enchantment",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "The target is afflicted with bad luck: on its next d20 roll (one per 5 caster levels, up to five) it must roll twice and take the less favorable result; duration 1 round/level. No saving throw; spell resistance applies. The forced worst-of-two reroll mechanic is not modeled by the engine, so the spell has no mechanical effects here."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Inflict Light Wounds",
"level": 1,
"school": "necromancy",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "1d8+1",
"types": ["negative"],
"half_on_save": true
}
],
"description": "Touching a creature channels negative energy, dealing 1d8 points of damage + 1 per caster level (maximum +5); at caster level 1 this is modeled as 1d8+1. Will half. Undead are instead cured of a like amount of damage - the engine only models the damaging side against living targets."
}
+18
View File
@@ -0,0 +1,18 @@
{
"name": "Ironskin",
"level": 2,
"school": "transmutation",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "ac", "value": 4, "bonus_type": "natural" }
],
"duration_rounds": 60
}
],
"description": "Your skin hardens like iron: you gain a +4 enhancement bonus to your natural armor bonus (rising to +7 at caster level 15; the scaling is not modeled). Duration 1 min/level approximated as 60 rounds. The ability to dismiss the spell to negate one confirmed critical or sneak attack is not modeled."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Light",
"level": 0,
"school": "evocation",
"range": "touch",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip: a touched object glows like a torch, shedding normal light in a 20-foot radius (plus dim light for 20 more feet) for 10 min/level. No combat effect; illumination is not modeled by the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Mage Hand",
"level": 0,
"school": "transmutation",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip: a spectral hand lifts and moves one unattended nonmagical object weighing up to 5 lb. from up to 25 ft. away, as far as 15 feet per move action while you concentrate. No combat effect; object manipulation is not modeled by the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Make Whole",
"level": 2,
"school": "transmutation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "Repairs 1d6 points of damage per caster level (maximum 5d6) to one object or construct creature (10-minute casting). Will negates (harmless, object). Object/construct repair is outside the engine's combat model, so the spell has no mechanical effects here."
}
@@ -0,0 +1,10 @@
{
"name": "Masterwork Transformation",
"level": 2,
"school": "transmutation",
"range": "touch",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "After 1 hour of casting (plus masterwork-priced reagents), a non-masterwork weapon, armor, shield, tool, or skill kit becomes its masterwork equivalent. Item crafting is outside the engine's combat model, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Mending",
"level": 0,
"school": "transmutation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A cantrip: after 10 minutes, the spell repairs 1d4 hit points of damage to one object (up to 1 lb./level), removing the broken condition if restored to at least half HP. Will negates (harmless, object). Object repair is outside the engine's combat model, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Message",
"level": 0,
"school": "transmutation",
"range": "medium",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip: you whisper messages and receive replies with targeted creatures within range for 10 min/level; bystanders hear nothing without a DC 25 Perception check. Communication is not modeled by the engine, so the spell has no mechanical effects here."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Mirror Image",
"level": 2,
"school": "illusion",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "blurred",
"duration_rounds": 10,
"save_negates": false
}
],
"description": "Illusory doubles (1d4 + 1 per 3 caster levels, max 8) occupy your space, making attacks risk striking a figment instead. Duration 1 min/level approximated as 10 rounds. Simplification: the image-destruction mechanic is approximated as the blurred condition (20% miss chance); the real spell can deflect attacks entirely until the images are used up."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Oath of Anonymity",
"level": 0,
"school": "illusion",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip: you swear an oath of secrecy - your voice, body language and gear are magically distorted, granting a +10 bonus on Disguise checks to conceal your identity (not to impersonate a specific person) for 10 min/level. Social skill effect only: the engine does not model skill-check modifiers, so the spell has no mechanical effects here."
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "Obscuring Mist",
"level": 1,
"school": "conjuration",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 20,
"save_for_half": false,
"effects": [
{
"type": "condition",
"condition_name": "blurred",
"duration_rounds": 10,
"save_negates": false
}
]
}
],
"description": "A mist cloud spreads in a 20-ft radius from you for 1 min/level (approximated as 10 rounds): a creature 5 feet away has concealment (20% miss chance), farther away total concealment. Simplification: everyone in the area (allies included) gains the blurred 20% miss chance; the distance-scaled total concealment beyond 5 ft is not modeled."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Open/Close",
"level": 0,
"school": "transmutation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A cantrip: you open or close one unsecured object or portal weighing up to 30 lb. at up to 25 ft. away (fails if a bar or lock resists). Will negates (object). Object manipulation is not modeled by the engine, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Owl's Wisdom",
"level": 2,
"school": "transmutation",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "The subject gains a +4 enhancement bonus to Wisdom for 1 min/level, adding the usual benefit to Wisdom-based skills, Will saves and spell DCs. Will negates (harmless). Ability-score increases are not modeled by the engine, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Prestidigitation",
"level": 0,
"school": "universal",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip: minor magical tricks for 1 hour - lift 1 lb. of material, color/clean/soil items in a 1-ft cube, chill/warm/flavor 1 lb. of nonliving material, or create crude trinkets. It can deal no damage and duplicates no other spell. No combat effect; the trivial manipulations are not modeled by the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Protection from Evil",
"level": 1,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A barrier wards the subject from evil for 1 min/level: +2 deflection bonus to AC and +2 resistance bonus on saves against attacks and effects created by evil creatures, renewed saves and suppression of mental control, and lawful summoned evil creatures cannot touch it. Will negates (harmless). The engine has no alignment system, so alignment-restricted bonuses cannot be modeled and the spell has no mechanical effects here."
}
@@ -0,0 +1,10 @@
{
"name": "Protection from Evil, Communal",
"level": 2,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "Functions like protection from evil (+2 deflection to AC and +2 resistance on saves vs evil creatures, protection from mental control), except the 1 min/level duration is divided in 1-minute intervals among the touched creatures. Will negates (harmless). Alignment-restricted bonuses and duration-sharing are not modeled, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Protection from Law",
"level": 1,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A barrier wards the subject from lawful creatures for 1 min/level: +2 deflection bonus to AC and +2 resistance bonus on saves against attacks and effects created by lawful creatures, plus renewed saves against mental control by lawful creatures. Will negates (harmless). The engine has no alignment system, so alignment-restricted bonuses cannot be modeled and the spell has no mechanical effects here."
}
@@ -0,0 +1,10 @@
{
"name": "Protection from Law, Communal",
"level": 2,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "Functions like protection from law (+2 deflection to AC and +2 resistance on saves vs lawful creatures, protection from mental control), except the 1 min/level duration is divided in 1-minute intervals among the touched creatures. Will negates (harmless). Alignment-restricted bonuses and duration-sharing are not modeled, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Purify Food and Drink",
"level": 0,
"school": "transmutation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A cantrip: cleanses 1 cu. ft./level of spoiled, rotten, diseased or poisoned food and water, making it pure and drinkable (no effect on creatures or magic potions). Will negates (object). Non-combat utility: the engine does not model objects, so the spell has no mechanical effects here."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Putrefy Food and Drink",
"level": 0,
"school": "transmutation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A cantrip: instantly rots 1 cu. ft./level of edible food and makes water and liquids brackish and undrinkable (can also spoil a single potion; no effect on creatures or unholy water). Will negates (object). Non-combat utility: the engine does not model objects, so the spell has no mechanical effects here."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Ray of Enfeeblement",
"level": 1,
"school": "necromancy",
"range": "close",
"save": "fort",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "fatigued",
"duration_rounds": 4,
"save_negates": true
}
],
"description": "A coruscating ray strikes the target (ranged touch attack), dealing 1d6 points of Strength damage +1 per two caster levels (maximum 1d6+5); a successful Fortitude save halves the damage. Simplification: the engine has no ability damage, so the Strength penalty is modeled as the fatigued condition (attack/damage penalties, no charge) for 4 rounds (1 round/level at caster level 4); Fortitude halves is approximated as Fortitude negates."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Ray of Frost",
"level": 0,
"school": "evocation",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "1d3",
"types": ["cold"],
"half_on_save": false
}
],
"description": "A cantrip: a ray of freezing air and ice projects from your pointing finger. You must succeed on a ranged touch attack to deal 1d3 points of cold damage to the target. No save."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Read Magic",
"level": 0,
"school": "divination",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip: you can decipher magical inscriptions on objects that would otherwise be unintelligible, at one page per minute, and you can thereafter read that particular writing without the spell. Duration 10 min./level. No combat effect; reading magical writing is not modeled by the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Resistance",
"level": 0,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A cantrip: the subject gains a +1 resistance bonus on saving throws for 1 minute. Will negates (harmless). Simplified: the engine has no resistance bonus type and no self-buff encoding that preserves the pinned default-policy casting order, so no mechanical effect is applied."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Restoration, Lesser",
"level": 2,
"school": "conjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "Casting time 3 rounds. Dispels any magical effects reducing one of the subject's ability scores or cures 1d4 points of temporary ability damage to one ability score, and eliminates any fatigue (improving exhausted to fatigued). Will negates (harmless). No combat effect encoded: the engine has no ability damage, fatigue-from-spell, or magical-effect-reduction state to restore."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Sanctuary",
"level": 1,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "You ward a creature against attacks for 1 round/level: any opponent attempting to directly attack the warded creature must succeed on a Will save or be unable to follow through with that attack and cannot directly attack the subject for the duration. The subject cannot attack without ending the spell. Will negates. No combat effect encoded: the engine has no attack-refusal-by-save mechanic, so the protection cannot be represented."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Saving Finale",
"level": 1,
"school": "evocation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "Immediate action, requires an active bardic performance: when a creature affected by your performance fails a saving throw, end the performance to let it immediately reroll the failed save and take the new result. Will negates (harmless). No combat effect encoded: conditional save rerolls tied to bardic performance are not modeled by the engine."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Scorching Ray",
"level": 2,
"school": "evocation",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "4d6",
"types": ["fire"],
"half_on_save": false
}
],
"description": "You blast your enemy with a searing beam of fire: each ray requires a ranged touch attack and deals 4d6 points of fire damage. You fire one ray, plus one additional ray per four levels beyond 3rd (max three at 11th) — the engine models a single ray. No save."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Shard of Chaos",
"level": 2,
"school": "evocation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A dart-shaped projectile of chaotic energy (ranged touch attack): lawful creatures take 1d8 per two caster levels (max 5d8); lawful outsiders take 1d6 per caster level (max 10d6) and are slowed 1 round. A successful Will save halves the damage and negates the slow. Half damage to neutral creatures, no effect on chaotic ones. No combat effect encoded: the engine has no alignment, so the alignment-gated damage cannot be represented."
}
+18
View File
@@ -0,0 +1,18 @@
{
"name": "Shield",
"level": 1,
"school": "abjuration",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "ac", "value": 4, "bonus_type": "deflection" }
],
"duration_rounds": 10
}
],
"description": "An invisible shield of force hovers in front of you, granting a +4 shield bonus to AC for 1 min./level (modeled as 10 rounds; the shield bonus type is approximated as deflection since the engine has no shield bonus type). Negating magic missile and the ranged-attack deflection are not modeled."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Shield of Faith",
"level": 1,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A shimmering magical field grants the target a +2 deflection bonus to AC (+1 per six caster levels, max +5) for 1 min./level. Will negates (harmless). Simplified: the engine has no self-buff encoding that preserves the pinned default-policy casting order, so no mechanical effect is applied."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Shocking Grasp",
"level": 1,
"school": "evocation",
"range": "touch",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "damage",
"formula": "1d6",
"types": ["electricity"],
"half_on_save": false
}
],
"description": "Your successful melee touch attack deals 1d6 points of electricity damage per caster level (maximum 5d6) — modeled at caster level 1 (1d6). No save; the +3 attack bonus against metal-armored foes is not modeled."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Silence",
"level": 2,
"school": "illusion",
"range": "long",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "Casting time 1 round. A 20-ft.-radius emanation centered on a creature, object, or point in space: all sound is stopped in the area, spells with verbal components cannot be cast, and creatures inside are immune to sonic and language-dependent effects. Duration 1 round/level. Will negates (object). No combat effect encoded: the engine has no verbal component or sound propagation model."
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "Sleep",
"level": 1,
"school": "enchantment",
"range": "medium",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 10,
"save_for_half": false,
"effects": [
{
"type": "condition",
"condition_name": "asleep",
"duration_rounds": 10,
"save_negates": true
}
]
}
],
"description": "A magical slumber comes upon 4 HD of living creatures in a 10-ft.-radius burst (fewest HD first; the HD budget is not modeled). Sleeping creatures are helpless (modeled as the asleep condition, 1 min./level approximated as 10 rounds). Will negates. Constructs and undead are immune (not modeled)."
}
+30
View File
@@ -0,0 +1,30 @@
{
"name": "Sound Burst",
"level": 2,
"school": "evocation",
"range": "close",
"save": "fort",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 10,
"save_for_half": true,
"effects": [
{
"type": "damage",
"formula": "1d8",
"types": ["sonic"],
"half_on_save": true
},
{
"type": "condition",
"condition_name": "stunned",
"duration_rounds": 1,
"save_negates": true
}
]
}
],
"description": "A tremendous cacophony blasts a 10-ft.-radius spread: every creature in the area takes 1d8 points of sonic damage and must succeed on a Fortitude save or be stunned for 1 round (Fortitude partial — modeled as save for half damage and negates the stun). Creatures that cannot hear take the damage but are not stunned (not modeled)."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Spark",
"level": 0,
"school": "evocation",
"range": "close",
"save": "fort",
"base_dc": 10,
"effects": [],
"description": "A cantrip: you create a tiny spark that ignites one unattended Fine flammable object within close range, in any weather (works as flint and steel but much faster). Fortitude negates (object). No combat effect: the spell targets objects, not creatures."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Spear of Purity",
"level": 2,
"school": "evocation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A spear of good energy (ranged touch attack): evil creatures take 1d8 per two caster levels (max 5d8); evil outsiders take 1d6 per caster level (max 10d6) and are blinded 1 round. A successful Will save halves the damage and negates the blind. Half damage to neutral creatures, no effect on good ones. No combat effect encoded: the engine has no alignment, so the alignment-gated damage cannot be represented."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Spiritual Weapon",
"level": 2,
"school": "evocation",
"range": "medium",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A force weapon appears and attacks foes at a distance as you direct it (1d8 force damage +1 per three caster levels), using your base attack bonus plus Wisdom modifier, once per round on your turn for 1 round/level. No combat effect encoded: the engine has no autonomous floating-attack creature to summon, and the weapon's per-round attacks are not modeled."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Stabilize",
"level": 0,
"school": "conjuration",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "A cantrip: you touch a living creature with -1 or fewer hit points (up to close range) and stabilize it automatically, so it stops losing hit points until it takes more damage. Will negates (harmless). No combat effect encoded: the engine's dying/stabilization track is not exposed to the spell-effect system."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Status",
"level": 2,
"school": "divination",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "You mentally monitor the relative position and general condition (unharmed, wounded, staggered, unconscious, dying, poisoned, and so on) of up to one creature per three caster levels for 1 hour/level. Will negates (harmless). No combat effect: out-of-combat scrying is not modeled by the engine."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Stave Off Corruption",
"level": 2,
"school": "abjuration",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "You ward the target to slow the advancement of its corruption, granting a +2 circumstance bonus on saving throws against the advancement of that corruption for 1 day per 2 caster levels. Will negates (harmless). Simplified: the engine has no corruption track and no self-buff encoding that preserves the pinned default-policy casting order, so no mechanical effect is applied."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Summon Instrument",
"level": 0,
"school": "conjuration",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip (casting time 1 round): you summon one handheld musical instrument of your choice into your hands or at your feet; it plays only for you and disappears when the spell ends (1 min./level). No combat effect: summoned objects are not modeled by the engine."
}
+16
View File
@@ -0,0 +1,16 @@
{
"name": "Summon Monster I",
"level": 1,
"school": "conjuration",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [
{
"type": "summon",
"creature": "data/monsters/celestial_wolf.json",
"duration_rounds": 4
}
],
"description": "Casting time 1 round: summons one extraplanar creature from the 1st-level list (e.g. dire rat, dog, eagle) to fight for you for 1 round/level. Simplified: the engine bestiary holds no 1st-level summon, so the celestial wolf model is reused and the duration is set to 4 rounds (1 round/level at caster level 4)."
}
@@ -0,0 +1,10 @@
{
"name": "Suppress Charms and Compulsions",
"level": 2,
"school": "abjuration",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "You bolster the target's willpower: either a +4 morale bonus on saving throws against charm and compulsion effects for 10 minutes, or (as concentration, max 1 round/level) suppression of all charm and compulsion effects on the targets. Will negates (harmless). Simplified: the engine has no conditional save bonus and no self-buff encoding that preserves the pinned default-policy casting order, so no mechanical effect is applied."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Surmount Affliction",
"level": 2,
"school": "abjuration",
"range": "personal",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "You temporarily overcome one harmful condition on yourself (blinded, confused, dazed, dazzled, deafened, fatigued, frightened, paralyzed, shaken, or sickened) for 1 round/level — the condition's effect is suspended, not ended. No combat effect encoded: the engine has no condition-suspension mechanic."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Tears to Wine",
"level": 2,
"school": "transmutation",
"range": "close",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "You transmute up to 1 cubic foot of nonmagical liquid per two caster levels (including spoiled, poisoned, or contaminated drink, tears, seawater) into mead or wine of average quality for 10 min./level (RAW range 10 ft., approximated as close). Will negates (object). No combat effect: liquid transmutation is not modeled by the engine."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Touch of Fatigue",
"level": 0,
"school": "necromancy",
"range": "touch",
"save": "fort",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "fatigued",
"duration_rounds": 4,
"save_negates": true
}
],
"description": "A cantrip: a melee touch attack channels negative energy, making the target fatigued for 1 round/level (modeled as 4 rounds at caster level 4; the effect ends with the duration rather than by long rest). No effect on an already fatigued creature or on creatures immune to fatigue. Fortitude negates."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Unseen Servant",
"level": 1,
"school": "conjuration",
"range": "close",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "An invisible, mindless, shapeless force performs simple tasks at your command for 1 hour/level: fetching, opening unstuck doors, holding chairs, cleaning or mending (effective Strength 2, speed 15 ft., no attacks, DC 10 skill limit). No combat effect: object manipulation by invisible servants is not modeled by the engine."
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Vanish",
"level": 1,
"school": "illusion",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [
{
"type": "condition",
"condition_name": "invisible",
"duration_rounds": 5,
"save_negates": false
}
],
"description": "As invisibility, but only for 1 round/level (maximum 5 rounds) — the spell ends if the subject attacks. Will negates (harmless) — modeled as always accepted by the target. Modeled as the invisible condition (total concealment for the target; the attack-ends-spell rule is not modeled)."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Virtue",
"level": 0,
"school": "transmutation",
"range": "touch",
"save": "none",
"base_dc": 10,
"effects": [],
"description": "A cantrip: with a touch you infuse a creature with a tiny surge of life, granting it 1 temporary hit point for 1 minute. Spell resistance yes (harmless). Simplified: the engine has no temporary hit points and no self-buff encoding that preserves the pinned default-policy casting order, so no mechanical effect is applied."
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Weapon of Awe",
"level": 2,
"school": "transmutation",
"range": "touch",
"save": "will",
"base_dc": 10,
"effects": [],
"description": "The touched weapon becomes an awe-inspiring instrument for 1 min./level: it gains a +2 sacred bonus on damage rolls, and a creature struck by a critical hit from it becomes shaken for 1 round (a mind-affecting fear effect). Will negates (harmless, object). Simplified: the engine has no weapon-targeted buff track and no self-buff encoding that preserves the pinned default-policy casting order, so no mechanical effect is applied."
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "Web",
"level": 2,
"school": "conjuration",
"range": "medium",
"save": "ref",
"base_dc": 10,
"effects": [
{
"type": "area",
"radius_ft": 20,
"save_for_half": false,
"effects": [
{
"type": "condition",
"condition_name": "grappled",
"duration_rounds": 2,
"save_negates": true
}
]
}
],
"description": "You conjure a many-layered mass of sticky strands filling a 20-ft.-radius spread (modeled as a 20-ft. burst). Creatures caught in the area are grappled by the fibers (Reflex negates) until they break free; the web also makes the area difficult terrain and provides cover (not modeled). Duration 10 min./level approximated as 2 rounds of the grappled condition."
}
+2184 -192
View File
File diff suppressed because it is too large Load Diff
+64 -7
View File
@@ -37,6 +37,7 @@ class Condition:
cant_aoo: bool = False
must_flee: bool = False
speed_mult: float = 1.0
miss_chance: int = 0
# ── Fear conditions ──────────────────────────────────────────────────────────
@@ -142,12 +143,15 @@ DAZZLED = Condition(
),
)
# Blinded: -2 AC, loses Dex, -4 STR/DEX skill checks (CRB)
# Blinded: -2 AC, loses Dex, -4 Dexterity skill checks; NO flat attack
# penalty (the -2 was a non-RAW double penalty on top of the engine's
# modeled 50% total-concealment miss). The Str-skill half of the -4 is not
# modeled (no str_skills target). (CRB)
BLINDED = Condition(
name="blinded",
modifiers=(
StatModifier(target="ac", value=-2),
StatModifier(target="attack", value=-2),
StatModifier(target="dex_skills", value=-4),
),
denies_dex_to_ac=True,
)
@@ -204,18 +208,34 @@ DAZED = Condition(
cant_act=True,
)
# Asleep/Unconscious: helpless, can't act, -4 AC vs melee, no Dex to AC (CRB)
# Asleep/Unconscious: helpless (CRB) — can't act, no Dex to AC. The old flat
# -4 AC approximation is superseded by the engine's Dex-0 (-5) adjustment for
# helpless targets, applied in resolve_attack (CombatEngine._is_helpless).
ASLEEP = Condition(
name="asleep",
modifiers=(
StatModifier(target="ac", value=-4),
),
denies_dex_to_ac=True,
cant_act=True,
cant_move=True,
)
# Helpless: helpless for all intents, treated as Dex 0 (loses Dex to AC),
# can't act; melee attackers gain +4 (engine logic in resolve_attack). The
# engine also treats paralyzed/asleep/unconscious as helpless (CRB: "helpless"
# = paralyzed, held, bound, sleeping, unconscious; stunned is NOT helpless) —
# see CombatEngine._is_helpless (CRB)
HELPLESS = Condition(
name="helpless",
denies_dex_to_ac=True,
cant_act=True,
)
# Fascinated: distracted, takes no actions other than paying attention (CRB)
FASCINATED = Condition(
name="fascinated",
cant_act=True,
)
# Grappled: -4 Dex, -2 attack, no movement, no AoO, can't cast without concentration (CRB)
GRAPPLED = Condition(
name="grappled",
@@ -253,6 +273,42 @@ STAGGERED = Condition(
name="staggered",
)
# ── Concealment conditions ───────────────────────────────────────────────────
# Blurred (blur spell): concealment — 20% miss chance on attacks (CRB)
BLURRED = Condition(
name="blurred",
miss_chance=20,
)
# Displaced (displacement spell): total concealment — 50% miss chance (CRB)
DISPLACED = Condition(
name="displaced",
miss_chance=50,
)
# Invisible: total concealment against attacks targeting it; the attacker-side
# +2 on attack rolls and the target's lost Dex bonus are engine logic (CRB)
INVISIBLE = Condition(
name="invisible",
miss_chance=50,
)
# ── Damage-over-time and mental conditions ───────────────────────────────────
# Bleeding: takes the listed bleed damage at the beginning of its turn (CRB).
# The damage-over-time is engine-driven (CombatantState.bleed_damage); the
# condition itself carries no stat modifiers or behavioral flags.
BLEEDING = Condition(
name="bleeding",
)
# Confused: at the beginning of each turn the subject rolls d% to act randomly
# (CRB). The d% behavior is engine logic in ``_take_turn``; simplification:
# the "if attacked, attack the attacker" clause is reduced to "nearest
# creature". No stat modifiers or behavioral flags.
CONFUSED = Condition(
name="confused",
)
# ── Registry ─────────────────────────────────────────────────────────────────
CONDITIONS_BY_NAME: dict[str, Condition] = {
c.name: c
@@ -260,6 +316,7 @@ CONDITIONS_BY_NAME: dict[str, Condition] = {
SHAKEN, SICKENED, FATIGUED, EXHAUSTED, ENTANGLED, DAZZLED,
BLINDED, DEAFENED, FLAT_FOOTED, PRONE, STUNNED, PARALYZED,
NAUSEATED, DAZED, STAGGERED, FRIGHTENED, PANICKED, COWERING,
ASLEEP, GRAPPLED, PINNED,
ASLEEP, GRAPPLED, PINNED, BLURRED, DISPLACED,
HELPLESS, FASCINATED, INVISIBLE, BLEEDING, CONFUSED,
]
}
+100 -1
View File
@@ -21,6 +21,8 @@ if TYPE_CHECKING:
Pt = tuple[int, int] # lattice point (x, y) = (col, row)
_Rat = tuple[int, int] # exact rational as (numerator, denominator), den > 0
_STANDARD_COVER_BONUS = 4 # PF1e: standard cover, and soft cover from a creature
def _corners(pos: Pos) -> tuple[Pt, Pt, Pt, Pt]:
row, col = pos
@@ -75,11 +77,13 @@ class _LineGrid:
exclude: frozenset[Pos],
blocking: Callable[[TerrainType], bool],
occupied: frozenset[Pos] = frozenset(),
bonus_of: Callable[[TerrainType], int] | None = None,
) -> None:
self._grid = grid
self._exclude = exclude
self._blocking = blocking
self._occupied = occupied
self._bonus_of = bonus_of
def blocked(self, p1: Pt, p2: Pt) -> bool:
min_x = min(p1[0], p2[0])
@@ -97,9 +101,49 @@ class _LineGrid:
return True
return False
def bonus(self, p1: Pt, p2: Pt) -> int:
"""Highest cover bonus among the blocking cells the segment p1->p2 hits.
0 when the segment crosses no blocking cell (or when this view was
built without a bonus function, i.e. for plain line-of-effect).
Occupied cells always grant soft cover (+4, CRB).
"""
if self._bonus_of is None:
return 0
min_x = min(p1[0], p2[0])
max_x = max(p1[0], p2[0])
min_y = min(p1[1], p2[1])
max_y = max(p1[1], p2[1])
best = 0
for col in range(min_x - 1, max_x + 1):
for row in range(min_y - 1, max_y + 1):
pos = (row, col)
if pos in self._exclude or not self._grid.in_bounds(pos):
continue
terrain = self._grid.terrain_at(pos)
is_occupied = pos in self._occupied
if not (is_occupied or self._blocking(terrain)):
continue
if not _segment_hits_cell(p1, p2, row, col):
continue
value = self._bonus_of(terrain)
if is_occupied:
value = max(value, _STANDARD_COVER_BONUS)
best = max(best, value)
return best
def terrain_cover_bonus(terrain: TerrainType) -> int:
"""Cover bonus a terrain square grants: its level, else legacy +4 for cover."""
if terrain.cover_bonus > 0:
return terrain.cover_bonus
if terrain.cover or terrain.blocks_los:
return _STANDARD_COVER_BONUS
return 0
def _grants_cover(terrain: TerrainType) -> bool:
return terrain.cover or terrain.blocks_los
return terrain_cover_bonus(terrain) > 0
def has_line_of_effect(grid: Grid, a: Pos, b: Pos) -> bool:
@@ -179,3 +223,58 @@ def has_cover_cells(
for cell_a in attacker_cells
for corner_a in _corners(cell_a)
)
def cover_bonus(
grid: Grid,
attacker: Pos,
target: Pos,
*,
ranged: bool,
occupied: frozenset[Pos] = frozenset(),
) -> int:
"""Effective cover bonus (0/2/4/8) for an attack between two single cells."""
return cover_bonus_cells(
grid,
frozenset({attacker}),
frozenset({target}),
ranged=ranged,
occupied=occupied,
)
def cover_bonus_cells(
grid: Grid,
attacker_cells: frozenset[Pos],
target_cells: frozenset[Pos],
*,
ranged: bool,
occupied: frozenset[Pos] = frozenset(),
) -> int:
"""Effective cover bonus (0/2/4/8) for an attack between two sets of cells.
0 when there is no cover (see has_cover_cells for the corner rule);
otherwise the highest cover level among the cells blocking the
attacker-corner to target-corner lines. Interposed creatures grant soft
cover (+4) on ranged attacks only, exactly as in has_cover_cells.
"""
if not attacker_cells or not target_cells:
return 0
if not has_cover_cells(
grid, attacker_cells, target_cells, ranged=ranged, occupied=occupied
):
return 0
lines = _LineGrid(
grid,
attacker_cells | target_cells,
_grants_cover,
occupied if ranged else frozenset(),
bonus_of=terrain_cover_bonus,
)
return max(
lines.bonus(corner_a, corner_t)
for cell_a in attacker_cells
for corner_a in _corners(cell_a)
for cell_t in target_cells
for corner_t in _corners(cell_t)
)

Some files were not shown because too many files have changed in this diff Show More