37 KiB
finalize-engine - Work Plan
TL;DR (For humans)
What you'll get: Un moteur de combat Pathfinder complet pour la reprise de campagne : les sorts sont enfin lancés (avec concentration, portées, effets de zone, invocations et métamagie), les durées des buffs sont respectées, les conditions (étourdi, paralysé, paniqué…) bloquent réellement les actions, les ressources (grit, channel, hexes) sont comptées, les compétences et les manœuvres de combat (y compris la lutte complète) fonctionnent, et les 8 personnages jouables disposent de leurs dons, sorts et capacités de classe.
Why this approach: Fondations correctes d'abord (durées, flags, DC, ressources) car toute la magie et les systèmes de combat s'appuient dessus ; ensuite les systèmes génériques (compétences, manœuvres) ; enfin le contenu des 8 personnages. Le moteur devient générique : n'importe quelle entrée du contenu Pathfinder complet (hors périmètre) fonctionnera sans nouvelle mécanique.
What it will NOT do: Pas d'encodage de la base complète Pathfinder (9500 entrées), pas de couche LLM (tactiques/rapporteur), pas de tailles Large+, pas de 3D/élévation, aucune modification des fiches Foundry d'origine.
Effort: Large
Risk: Medium - 20 étapes couplées (durées/flags) avec gate stricte à chaque commit
Decisions to sanity-check: (1) DC des sorts = 10 + niveau du sort + modificateur de la stat de casting ; (2) ressources au niveau du personnage (slots/grit/channel/hexes) consommées en combat ; (3) sorts non mappables → effects=[] (inertes) plutôt que de les inventer.
Your next move: approuve le plan, puis lance l'exécution ($start-work). Full execution detail follows below.
TL;DR (machine): Large effort, Medium risk - 20 commits gate-verts pour finaliser le moteur PF1e (fondations → magie → systèmes → contenu 8 PJ → validation), ~401 → ~500 tests.
Scope
Must have
- A. Fondations : durées d'effets/conditions avec expiration (
rounds_left), enforcement des flags comportementaux (cant_act, cant_move, cant_attack, cant_charge, cant_aoo, must_flee, denies_dex_to_ac, speed_mult), formule DC correcte (10 + niveau sort + mod. stat de casting), système de ressources (slots, grit, channel, hexes, rounds bardique). - B. Magie : politique de sort dans
default_policy(les casters lancent enfin des sorts), AoO sur incantation + jets de concentration, AoE/multi-cibles (fireball 20 ft), summoning complet (durée, insertion initiative), métamagie (Empower/Maximize/Extend). - C. Systèmes de combat : compétences complètes (37, checks, rangs, class skills, armor penalty), manœuvres complètes (CMB/CMD : trip, disarm, bull rush, sunder), lutte complète (maintain/pin/escape/release).
- D. Contenu des 8 PJ : 55 feats →
AbilitySpec, 101 sorts →data/spells/*.json, capacités de classe (mount, hex, channel, deed, domain, spell combat, performance), monstre complexe exemple (liche). - E. Validation : transcripts épinglés par système, golden tests 8 PJ (
tests/test_pc_combat_golden.py), gate finale + README à jour.
Must NOT have (guardrails, anti-slop, scope boundaries)
- PAS la base de données complète PF1e (3400 feats, 3100 sorts, 2000 traits) — le moteur les supporte, on ne les encode pas.
- PAS de couche LLM (Phases 2/3 du plan initial : tactiques et rapporteur LLM).
- PAS de modifications de
fiches_personnages/,.omo/(hors ce plan),.codegraph/. - PAS de tailles Large+ (2×2) ni de déplacement en 3D/élévation.
- PAS d'AoE en ligne/cone (burst uniquement), PAS de swallow whole, PAS de phylactère.
- PAS de réécriture des règles d'empilement des bonus (
resolve_modifiers) ni des définitions de conditions existantes.
Verification strategy
Zero human intervention - all verification is agent-executed.
- Test decision: TDD (transcript épinglé ou test d'assertion d'abord, implémentation ensuite) + pytest.
- Evidence:
.omo/evidence/task-<N>-finalize-engine.md(un fichier par todo, documentant commande exécutée + résultat). attemptDir =.omo/evidence/(hors ulw-loop). - Gate par commit (obligatoire, verte avant chaque commit) :
export PATH="$HOME/.local/bin:$PATH" && uv run ruff check src tests && uv run basedpyright src && uv run pytest -q - État initial : 401 tests verts, ruff + basedpyright propres (commit
82148ce).
Execution strategy
Parallel execution waves
Target 5-8 todos per wave. Fewer than 3 (except the final) means you under-split.
- Wave 1 (fondations indépendantes) : 1, 3, 4, 9
- Wave 2 : 2 (←1), 6 (←2), 10
- Wave 3 : 5 (←2+6), 7, 11 (←10), 8
- Wave 4 : 12 (←11+10), 13 (←2+10), 14 (←7+8+9)
- Wave 5 : 15, 16 (←4), 17 (←4+7+14), 18 (←14)
- Wave 6 (finale) : 19 (←tous), 20 (←19)
Dependency matrix
| Todo | Depends on | Blocks | Can parallelize with |
|---|---|---|---|
| 1 Durées | — | 2 | 3, 4, 9 |
| 2 Flags | 1 | 5, 6, 13 | 3, 4, 9 |
| 3 Formule DC | — | 14 | 1, 2, 4, 9 |
| 4 Ressources | — | 16, 17 | 1, 2, 3, 9 |
| 5 Politique de sort | 2, 6 | 19 | 7, 8, 11 |
| 6 AoO + concentration | 2 | 5 | 7, 8, 10, 11 |
| 7 AoE | — | 14, 17 | 5, 8, 11 |
| 8 Summoning | — | 14 | 5, 7, 11 |
| 9 Métamagie | — | 14 | 1, 3, 4 |
| 10 Compétences | — | 11, 12, 13 | 2, 6, 7 |
| 11 Manœuvres | 10 | 12 | 5, 7, 8 |
| 12 Lutte | 11, 10 | 19 | 13, 14 |
| 13 Feats 8 PJ | 2, 10 | 19 | 12, 14 |
| 14 101 sorts | 7, 8, 9 | 17, 18, 19 | 12, 13 |
| 15 Compagnons | — | 19 | 16, 17, 18 |
| 16 Channel/hexes | 4 | 19 | 15, 17, 18 |
| 17 Deeds/spell combat | 4, 7, 14 | 19 | 15, 16, 18 |
| 18 Liche | 14 | 19 | 15, 16, 17 |
| 19 Golden tests | 2, 5, 12, 13, 14, 15, 16, 17, 18 | 20 | — |
| 20 Gate finale | 19 | — | — |
Todos
Implementation + Test = ONE todo. Never separate.
-
1. Durées d'effets et de conditions (expiration) What to do / Must NOT do: Introduire
ActiveBuffetActiveCondition(dataclasses avecrounds_left, source, effet/condition référencés). Remplacerstate.effects: list[StatModifier]parstate.active_buffs: list[ActiveBuff]etstate.conditions: list[Condition]parstate.active_conditions: list[ActiveCondition]. Au début du tour de la créature (_take_turn), DÉCRÉMENTERrounds_leftet expirer les entrées à 0 (au lieu destate.effects.clear()). La pénalité de charge −2 CA devient unActiveBuffde 1 round._stat_modifiersetresolve_attack/resolve_savedoivent lire les buffs actifs via leurs effets. MUST NOT: toucher aux règles d'empilement (resolve_modifiers), ni aux définitions de conditions. Parallelization: Wave 1 | Blocked by: — | Blocks: 2 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/combat.py:169-193(CombatantState),combat.py:284-299(_take_turn— lestate.effects.clear()à corriger),combat.py:380-399(_stat_modifiers),combat.py:829-866(_apply_spell_effects— application des buffs/conditions),combat.py:848-865(logduration_rounds),src/pf1e_simulator/effects.py:49-65(StatModifier),src/pf1e_simulator/conditions.py:21-39(Condition),tests/test_combat.py(pattern transcript ScriptedRng),src/pf1e_simulator/rng.py(ScriptedRng) Acceptance criteria (agent-executable):uv run pytest -qvert avec les nouveaux transcripts ; test épinglé : un buffduration_rounds=3persiste ≥3 tours ; la pénalité de charge disparaît après le tour du chargeur. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "duration or charge"passe ; failure — transcript où un buff expire après 1 tour doit échouer (pin). Evidence.omo/evidence/task-1-finalize-engine.mdCommit: Y | feat(combat): effect and condition durations -
2. Enforcement des flags comportementaux What to do / Must NOT do: Appliquer les flags déclarés dans
conditions.py:31-39partout où le moteur prend des décisions :cant_act→ tour sauté (aucune action) ;cant_move→ aucun déplacement (move/charge/retraite/5ft-step) ;cant_attack→ aucune attaque ;cant_charge;cant_aoo→ la créature ne porte jamais d'AoO (_resolve_aoo) ;must_flee→ la politique renvoie(withdraw,)en s'éloignant de l'ennemi le plus proche ;denies_dex_to_ac→ la cible est traitée en flat-footed (mod DEX retiré de la CA) ;speed_mult→ budget de mouvement multiplié. MUST NOT: modifier les définitions de conditions ni_stat_modifiers. Parallelization: Wave 2 | Blocked by: 1 | Blocks: 5, 6, 13 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/conditions.py:31-39(champs flags),src/pf1e_simulator/combat.py:284-299(_take_turn— gate d'action),combat.py:498-518(_roll_initiative),combat.py:535-544(_enemies_threatening),combat.py:546-558(_resolve_aoo),combat.py:1126-1152(default_policy),combat.py:360-380(mouvement / budget de déplacement),src/pf1e_simulator/models.py(ACProfile — flat-footed) Acceptance criteria (agent-executable):uv run pytest -qvert ; transcripts épinglés : un combattantstunnedne fait aucune action ;panickedfuit (withdraw) ; une cibleblindedperd le mod DEX à la CA ;speed_mult=0.5parcourt la moitié. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "stun or panic or blind or speed"passe ; failure — tour non sauté pour un stun doit échouer. Evidence.omo/evidence/task-2-finalize-engine.mdCommit: Y | feat(combat): enforce behavioral condition flags -
3. Formule DC correcte des sorts What to do / Must NOT do: Ajouter
casting_ability: str(nom de la stat : str/dex/con/int/wis/cha) àCombatant(modèle) et au JSON des combattants (champ optionnel avec défaut). Remplacerdc = spell.base_dc + caster.combatant.level(combat.py:807) pardc = 10 + spell.level + modificateur(casting_ability). Enrichir les 8 JSONdata/pcs/*.jsonavec la bonne stat (tammara: wis, nairda: int, misty: cha (scion) / int, oni: wis, esha: int). MUST NOT: toucher àresolve_saveni aux DC des capacités autres que sorts. Parallelization: Wave 1 | Blocked by: — | Blocks: 14 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/combat.py:781-827(_cast_spell— DC à la ligne ~807),src/pf1e_simulator/models.py:93-115(Combatant),src/pf1e_simulator/spells.py:120-132(load_spell),tests/test_combat.py(test_dc_is_base_plus_levelà mettre à jour),data/pcs/*.json(8 fichiers),src/pf1e_simulator/loaders/combatant.py:107-147(load_combatant) Acceptance criteria (agent-executable):uv run pytest -qvert ; test mis à jour : DC = 10 + niveau du sort + mod. stat ; vérifier un DC concret (ex. sort niv 2, mod wis +3 → DC 15). QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "dc"passe ; failure — ancienne formule (base_dc + level) doit échouer. Evidence.omo/evidence/task-3-finalize-engine.mdCommit: Y | fix(combat): correct spell DC formula -
4. Système de ressources What to do / Must NOT do: Ajouter
resources: dict[str, int](max par combat : slots de sort, grit, channel_energy, hexes, bardic_rounds) àCombatant, etresources_left: dict[str, int]àCombatantState(initialisé depuisresourcesàbuild_states). Méthodespend_resource(name, n=1) -> boolsur l'état. Charger depuis JSON (champ optionnel). Pré-remplirtammara.json(grit: 3) etoni.json(channel_energy: 6). MUST NOT: implémenter les consommateurs (deeds/channel/hexes = todos 16-17) — uniquement le socle + tests. Parallelization: Wave 1 | Blocked by: — | Blocks: 16, 17 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/models.py:93-115(Combatant),src/pf1e_simulator/combat.py:169-193(CombatantState),src/pf1e_simulator/runner.py(build_states— initialisation par combat),src/pf1e_simulator/loaders/combatant.py:107-147,data/pcs/tammara.json,data/pcs/oni.jsonAcceptance criteria (agent-executable):uv run pytest -qvert ; test :spend_resourcedécrémente et refuse en dessous de 0 ; loader remplitresourcesdepuis JSON. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combatant_loader.py -k "resource"+ test unitaire passent ; failure — dépense au-delà du max doit échouer (pin). Evidence.omo/evidence/task-4-finalize-engine.mdCommit: Y | feat(models): combat resources -
5. default_policy lance des sorts What to do / Must NOT do: Étendre
default_policy(combat.py:1126-1152) : (1) soin si un allié est à <50% PV et un sort de soin est disponible ; (2) offensif si un ennemi est dans la portée du sort ; (3) buff sinon ; (4) repli sur le comportement actuel (full_attack/charge/5ft-step/move+attack) sinon ; (5) JAMAIS de sort si menacé sans concentration (voir todo 6). Vérifier la ressource (slots) avant de lancer. MUST NOT: réécrire les branches non-sort existantes, ni la mécanique de_cast_spell. Parallelization: Wave 3 | Blocked by: 2, 6 | Blocks: 19 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/combat.py:1126-1152(default_policy),combat.py:781-827(_cast_spell),src/pf1e_simulator/spells.py(SpellSpec : level, range, effects),data/pcs/nairda.json(32 sorts),tests/test_combat.py(pattern transcript) Acceptance criteria (agent-executable):uv run pytest -qvert ; transcript épinglé : nairda (ou un caster) lance un sort dans un combat Monte Carlo 1v1 ; un caster à court de slots ne lance plus. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "cast"passe ; failure — caster qui n'a jamais lancé en 5 rounds doit échouer. Evidence.omo/evidence/task-5-finalize-engine.mdCommit: Y | feat(combat): spellcasting default policy -
6. AoO sur incantation + concentration What to do / Must NOT do: Étendre
_check_provoked_aoo(combat.py:560) : lancer un sort depuis une case menacée provoque une AoO de chaque ennemi menaçant (une par round, piste_aoo_used). Si touché pendant l'incantation : jet de concentrationDC = 15 + 2 × niveau du sort(bonus Combat Casting +4 le cas échéant) ; échec → sort perdu (fizzle). MUST NOT: modifier les AoO liées au mouvement ni au tir. Parallelization: Wave 2 | Blocked by: 2 | Blocks: 5 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/combat.py:535-544(_enemies_threatening),combat.py:546-558(_resolve_aoo),combat.py:560(_check_provoked_aoo),combat.py:781-827(_cast_spell),src/pf1e_simulator/dice.py(jet),src/pf1e_simulator/rng.py(ScriptedRng pour transcripts) Acceptance criteria (agent-executable):uv run pytest -qvert ; transcript : un lanceur menacé subit l'AoO, échoue sa concentration (DC volontairement dépassé via ScriptedRng), et le sort fizzle (aucun effet appliqué). QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "concentrat"passe ; failure — sort appliqué malgré concentration ratée doit échouer. Evidence.omo/evidence/task-6-finalize-engine.mdCommit: Y | feat(combat): AoO and concentration on casting -
7. AoE / multi-cibles What to do / Must NOT do: Ajouter
AreaEffect(shape="burst",radius_ft,save_for_half: bool) aux types d'effets (spells.py:32-57). Dans_apply_spell_effects(combat.py:829-866), quand l'effet est une zone : cibler une case, collecter toutes les créatures dans le rayon (distance viagrid.distance), résoudre la sauvegarde par cible (demi-dégâts si réussie). Implémenter fireball (20-ft burst) dansdata/spells/. MUST NOT: lignes ni cônes. Parallelization: Wave 3 | Blocked by: — | Blocks: 14, 17 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/spells.py:32-57(types d'effets),combat.py:829-866(_apply_spell_effects),src/pf1e_simulator/grid.py(distance),combat.py:481-496(resolve_save),tests/test_spells.py:113-124(test all-10-spells),data/spells/(10 JSON existants) Acceptance criteria (agent-executable):uv run pytest -qvert ; transcript : un fireball touche ≥2 créatures dans le rayon, la sauvegarde réussie divise les dégâts par 2. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_spells.py -k "area or fireball"passe ; failure — fireball à cible unique doit échouer. Evidence.omo/evidence/task-7-finalize-engine.mdCommit: Y | feat(combat): area-of-effect spells -
8. Summoning complet What to do / Must NOT do: Ajouter
SummonEffect(creature: str,duration_rounds: int) aux types d'effets. À l'incantation : charger le statblock (ex.data/monsters/celestial_wolf.jsonà créer), créer unCombatantStateadjacent au lanceur, l'insérer DANS l'ordre d'initiative juste après le lanceur, même camp, avecdefault_policy; expirer (retrait) aprèsduration_roundstours de la créature. MUST NOT: summon multiples ni contrôle fin (le summoner ne dirige pas les actions tour par tour). Parallelization: Wave 3 | Blocked by: — | Blocks: 14 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/spells.py:32-57(types d'effets),combat.py:829-866(_apply_spell_effects),combat.py:169-193(CombatantState),combat.py:284-299(_take_turn— itération d'initiative),src/pf1e_simulator/runner.py(build_states),src/pf1e_simulator/loaders/combatant.py:107-147(recharger un statblock),data/monsters/goblin.json(schéma à copier pourcelestial_wolf.json) Acceptance criteria (agent-executable):uv run pytest -qvert ; transcript : la créature invoquée agit après le lanceur et disparaît après la durée. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "summon"passe ; failure — créature encore présente après expiration doit échouer. Evidence.omo/evidence/task-8-finalize-engine.mdCommit: Y | feat(combat): summoning -
9. Métamagie What to do / Must NOT do: Support générique feat→modification de
SpellSpecappliqué à la volée au lancement (aucune duplication de données) : Empower (dégâts ×1.5), Maximize (dés max), Extend (durée ×2). Ajoutermetamagic_feats: list[str]àCombatant+ champ JSON. Appliquer dans_cast_spell. MUST NOT: Heighten/Persistent, ni nouvelles entrées de sorts par variante. Parallelization: Wave 1 | Blocked by: — | Blocks: 14 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/spells.py(SpellSpec, DamageEffect, BuffEffect),combat.py:781-827(_cast_spell),src/pf1e_simulator/abilities.py:24-36(AbilitySpec — catégorie metamagic),src/pf1e_simulator/models.py:93-115(Combatant),tests/test_spells.pyAcceptance criteria (agent-executable):uv run pytest -qvert ; transcript : boule de feu Empowered inflige ×1.5 ; buff Extendé dure ×2 rounds. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_spells.py -k "meta"passe ; failure — dégâts non multipliés doit échouer. Evidence.omo/evidence/task-9-finalize-engine.mdCommit: Y | feat(combat): metamagic -
10. Compétences complètes What to do / Must NOT do:
SkillSpec(37 compétences PF1e : nom, stat liée, trained_only) ;Combatant.skills: dict[str, SkillRank](rank, class_skill, armor_penalty) ; étendreStatTarget(effects.py:40-44) aux 37 noms de compétences ;resolve_skill(state, skill, dc)→ d20 + rank + mod. stat + armor penalty ; feat Skill Focus (+3) viaStatModifieravec filtre skill. Loader Foundry : parser les compétences des fiches (rank/total/classSkill/armorPenalty déjà présents). MUST NOT: synergies de compétences ni prises 10/20. Parallelization: Wave 2 | Blocked by: — | Blocks: 11, 12, 13 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/effects.py:40-44(StatTarget),effects.py:49-65(StatModifier — ajouter champskill),src/pf1e_simulator/models.py(Combatant),src/pf1e_simulator/loaders/foundry.py:280-355(load_sheet_detailed),fiches_personnages/*_sheet.json(8 fiches — sections skills),src/pf1e_simulator/combat.py:380-399(_stat_modifiers— filtre skill) Acceptance criteria (agent-executable):uv run pytest -qvert ; test :resolve_skillapplique rank+mod+penalty ; Skill Focus +3 ; loader remplitskillspour les 8 PJ. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combatant_loader.py -k "skill"passe ; failure — skill sans armor penalty sur une fiche à pénalité doit échouer. Evidence.omo/evidence/task-10-finalize-engine.mdCommit: Y | feat(combat): skills system -
11. Manœuvres CMB/CMD What to do / Must NOT do: Ajouter
cmb(BAB + mod STR + taille) etcmd(10 + CMB + mod DEX) àCombatant(calculés par le loader depuis BAB/abilities). NouveauAction(kind="maneuver", maneuver="trip|disarm|bull_rush|sunder"). Résolution :d20 + CMBvsCMDde la cible ; succès → effet (trip: prone ; disarm: arme jetée (non utilisable) ; bull rush: poussée d'une case ; sunder: dégâts sur l'arme).default_policychoisit trip si BAB avantageux et cible en mêlée. MUST NOT: grapple (todo 12). Parallelization: Wave 3 | Blocked by: 10 | Blocks: 12 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/models.py(Combatant, AttackSpec),combat.py:221-239(Action),combat.py:1126-1152(default_policy),src/pf1e_simulator/conditions.py(prone),README.md:260(« Manœuvres de combat » non modélisées),src/pf1e_simulator/dice.pyAcceptance criteria (agent-executable):uv run pytest -qvert ; transcripts : trip → cible prone ; disarm → arme inutilisable ; bull rush → poussée d'une case. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "maneuver or trip or disarm"passe ; failure — trip sans mise à terre doit échouer. Evidence.omo/evidence/task-11-finalize-engine.mdCommit: Y | feat(combat): combat maneuvers CMB/CMD -
12. Lutte complète (grapple) What to do / Must NOT do: Condition
GRAPPLED(ajout àconditions.py), suivigrappled_with(id de l'adversaire). Actions : grapple (standard, d20+CMB vs CMD, initie), maintain (standard : dégâts ou déplacement), pin (maintain réussi vs CMD+5), escape (d20+CMB ou Escape Artist viaresolve_skillvs CMD), release (libre). Effets grapple : pas de mod DEX à la CA, pas de mouvement, −2 attaques, lancer un sort nécessite concentration.default_policy: grapple si pas d'avantage d'arme. MUST NOT: swallow whole, ni grapple multi-adversaires. Parallelization: Wave 4 | Blocked by: 11, 10 | Blocks: 19 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/conditions.py(18 conditions existantes, registry:212-220),combat.py:221-239(Action),combat.py:169-193(CombatantState),combat.py:1126-1152(default_policy),src/pf1e_simulator/effects.py(modificateurs), todo 10 (Escape Artist via resolve_skill) Acceptance criteria (agent-executable):uv run pytest -qvert ; transcript complet : initiate → maintain (dégâts) → pin → escape réussi. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "grapple"passe ; failure — grapplé qui se déplace doit échouer. Evidence.omo/evidence/task-12-finalize-engine.mdCommit: Y | feat(combat): grapple -
13. Mapping des feats des 8 PJ What to do / Must NOT do: Résoudre les 55 feats uniques des 8 PJ →
AbilitySpec: conserver les 8 connus + Weapon Focus/Toughness ; ajouter Combat Reflexes (AoO supplémentaires par round — interagit avec todo 2/_resolve_aoo), Skill Focus (+3 sur une compétence — todo 10), Point-Blank Shot déjà présent ; les autres → passifs minimaux ou no-op avec note (jamais inventer de mécanique). Synchroniser_KNOWN_FEATS(loaders/combatant.py:47) et_FEAT_MAP(loaders/foundry.py:52-61) avec la liste réelle des fiches. MUST NOT: créer de nouvelles mécaniques de feats au-delà de la liste. Parallelization: Wave 4 | Blocked by: 2, 10 | Blocks: 19 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/loaders/combatant.py:47(_KNOWN_FEATS),combatant.py:59-75(_resolve_feat),src/pf1e_simulator/loaders/foundry.py:52-71(_FEAT_MAP,_match_feat),src/pf1e_simulator/abilities.py:24-36(AbilitySpec + factories),fiches_personnages/*_sheet.json(8 fiches — sections feats),src/pf1e_simulator/combat.py:546-558(_resolve_aoo— Combat Reflexes) Acceptance criteria (agent-executable):uv run pytest -qvert ; le loader résout les 55 feats sans warning ni no-op silencieux (tous tracés) ; Combat Reflexes octroie une AoO supplémentaire par round. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combatant_loader.pypasse (24 tests + nouveaux) ; failure — feat inconnu silencieusement ignoré doit échouer (pin). Evidence.omo/evidence/task-13-finalize-engine.mdCommit: Y | feat(loaders): map 8-PC feats -
14. 101 sorts → data/spells/*.json What to do / Must NOT do: Script de génération (dans
loaders/) : créer les 91 JSON manquants depuis les fiches Foundry (nom, niveau, école, portée, sauvegarde ; effets par heuristique Damage/Condition/Heal/Buff/Area/Summon selon les conventions existantes) ; tout sort non mappable →effects=[](inerte mais présent). Mettre à jour le registre (load_spell_registry) ettests/test_spells.py:113-124(test all-10-spells → all-101). MUST NOT: encoder à la main des effets au-delà des heuristiques, ni inventer des stats absentes des fiches. Parallelization: Wave 4 | Blocked by: 7, 8, 9 | Blocks: 17, 18, 19 References (executor has NO interview context - be exhaustive):data/spells/(10 JSON existants — format de référence),src/pf1e_simulator/spells.py:84-132(_parse_effect,load_spell),src/pf1e_simulator/loaders/foundry.py(extraction des sorts depuis les fiches),fiches_personnages/*_sheet.json(8 fiches — spells),tests/test_spells.py:113-124,data/pcs/*.json(champspells) Acceptance criteria (agent-executable):uv run pytest -q tests/test_spells.pyvert ; le registre charge les 101 sorts ;uv run pf1e-sim --helpinchangé ; aucun fichier JSON invalide. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_spells.py+ chargement CLI passe ; failure — un sort connu manquant du registre doit échouer. Evidence.omo/evidence/task-14-finalize-engine.mdCommit: Y | feat(data): 101 spell JSONs -
15. Compagnons et montures What to do / Must NOT do: Créer des JSON
Combatantséparés pour Harvie (animal companion), Ierlieth (court drake), Jeanne (bodyguard) et la monture d'Esha, depuis les fiches Foundry (compétences/attaques/HP présents dans les fiches). Charger via la CLI comme les autres combattants. Noter dansnotesles données absentes. MUST NOT: mouvement simultané cavalier/monture ni règles de monte. Parallelization: Wave 5 | Blocked by: — | Blocks: 19 References (executor has NO interview context - be exhaustive):fiches_personnages/harvie_sheet.json,fiches_personnages/ierlieth_sheet.json,fiches_personnages/jeanne_sheet.json,fiches_personnages/esha_sheet.json,data/pcs/(format existant),src/pf1e_simulator/loaders/combatant.py:107-147,data/monsters/goblin.json(schéma) Acceptance criteria (agent-executable): les 4 JSON se chargent viauv run pf1e-sim --side players data/pcs/<compagnon>.jsonsans erreur de schéma. QA scenarios (name the exact tool + invocation): happy — combat 1v1 compagnon vs gobelin, 100 runs seed 1, exit code 0 ; failure — JSON invalide → exit code 2 (pin). Evidence.omo/evidence/task-15-finalize-engine.mdCommit: Y | feat(data): companions and mounts -
16. Channel energy, domaines, hexes What to do / Must NOT do: Pattern d'action de classe :
Action(kind="class_ability", class_ability="channel_energy|hex"). Oni : channel energy (burst 30 ft, 2d6 soin/dommage, consommechannel_energyressource (6/jour)) ; domaines Animal/Air minimaux. Nairda : hexes Evil Eye (shaken −2 AC ou saves, cible 1 round), Slumber (cible → asleep/disabled si Will échoue), Cackle (prolonge un hex). MUST NOT: listes de sorts de domaine complètes ni autres hexes. Parallelization: Wave 5 | Blocked by: 4 | Blocks: 19 References (executor has NO interview context - be exhaustive):data/pcs/oni.json,data/pcs/nairda.json,src/pf1e_simulator/combat.py:221-239(Action),combat.py:781-827(pattern_cast_spellà réutiliser pour class_ability),src/pf1e_simulator/conditions.py(shaken ; ajouter asleep),src/pf1e_simulator/combat.py:481-496(resolve_save) Acceptance criteria (agent-executable):uv run pytest -qvert ; transcript : channel soigne les alliés dans 30 ft et consomme la ressource ; Slumber endort un gobelin. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "channel or hex"passe ; failure — channel après épuisement de la ressource doit échouer. Evidence.omo/evidence/task-16-finalize-engine.mdCommit: Y | feat(combat): channel energy and hexes -
17. Deeds/grit, spell combat, performance What to do / Must NOT do: Tammara : deed Up Close and Deadly (+1d6 à ≤30 ft, coûte 1 grit), Quick Clear (swift, débloque l'arme) ; ressources depuis todo 4. Misty : Spell Combat (lancer un sort + attaque à outrance dans le même tour), Inspire Courage (buff moral +1 attaque/dégâts, 30 ft, coûte un round bardique). MUST NOT: la liste complète des deeds ni d'autres performances. Parallelization: Wave 5 | Blocked by: 4, 7, 14 | Blocks: 19 References (executor has NO interview context - be exhaustive):
data/pcs/tammara.json,data/pcs/misty.json,src/pf1e_simulator/combat.py:221-239(Action),combat.py:781-827(_cast_spell),combat.py:1126-1152(default_policy),src/pf1e_simulator/effects.py(BuffEffect moral),src/pf1e_simulator/grid.py(distancepour le rayon 30 ft) Acceptance criteria (agent-executable):uv run pytest -qvert ; transcripts : Misty lance + attaque dans le même tour ; le deed de Tammara consomme 1 grit ; Inspire Courage applique +1 attaque/dégâts aux alliés. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_combat.py -k "spell_combat or deed or inspire"passe ; failure — deed sans grit disponible doit échouer. Evidence.omo/evidence/task-17-finalize-engine.mdCommit: Y | feat(combat): class action patterns -
18. Liche (monstre complexe) What to do / Must NOT do:
data/monsters/lich.json: Fear Aura (30 ft, shaken, Will DC 22 negate), Paralyzing Touch (attaque de contact → PARALYZED 1d4+1 rounds), DR 15/bludgeoning+magic (via le système DR existant avec types), liste de sorts (fireball, haste…). MUST NOT: mécanique de phylactère ni de destruction définitive. Parallelization: Wave 5 | Blocked by: 14 | Blocks: 19 References (executor has NO interview context - be exhaustive):data/monsters/goblin.json(schéma),src/pf1e_simulator/models.py(DamageReduction, AttackSpec — touch),src/pf1e_simulator/conditions.py(paralyzed),src/pf1e_simulator/combat.py:781-827(sorts),src/pf1e_simulator/spells.pyAcceptance criteria (agent-executable): la liche se charge via la CLI ; transcript : Fear Aura applique shaken ; Paralyzing Touch applique PARALYZED ; DR 15/bludgeoning+magic absorbe une attaque non-magique. QA scenarios (name the exact tool + invocation): happy —uv run pf1e-sim --side players data/pcs/tammara.json --side monsters data/monsters/lich.json --runs 50 --seed 1exit 0 ; failure — la liche sans Fear Aura au contact doit échouer (pin transcript). Evidence.omo/evidence/task-18-finalize-engine.mdCommit: Y | feat(data): lich monster -
19. Golden tests 8 PJ What to do / Must NOT do:
tests/test_pc_combat_golden.py: pour chaque PJ, combat scripté (ScriptedRng) contre un gobelin (et tammara vs liche) avec transcript épinglé ; assertions sur l'issue (victoire/défaite) et plages de PV/ressources attendues. Tous les tests déterministes. MUST NOT: assertions statistiques flaky ni seeds aléatoires. Parallelization: Wave 6 | Blocked by: 2, 5, 12, 13, 14, 15, 16, 17, 18 | Blocks: 20 References (executor has NO interview context - be exhaustive):src/pf1e_simulator/rng.py(ScriptedRng),tests/test_combat.py(pattern des transcripts existants),tests/test_combatant_loader.py(pattern),data/pcs/*.json(8),data/monsters/goblin.json,data/monsters/lich.json,data/maps/sample_arena.yamlAcceptance criteria (agent-executable):uv run pytest -q tests/test_pc_combat_golden.pyvert ; chaque PJ a ≥1 combat scripté épinglé. QA scenarios (name the exact tool + invocation): happy —uv run pytest -q tests/test_pc_combat_golden.pypasse ; failure — transcript divergent après un changement doit échouer (pin). Evidence.omo/evidence/task-19-finalize-engine.mdCommit: Y | test(combat): PC golden transcripts -
20. Gate finale + README What to do / Must NOT do: Gate complète (
ruff+basedpyright+pytest -q), mise à jour du README : nouvelles règles implémentées (durées, flags, DC, ressources, politique de sort, AoO/concentration, AoE, summoning, métamagie, compétences, manœuvres, lutte, capacités de classe), section « Non modélisé » nettoyée, compteur de sorts (101), route de Phase 1 mise à jour. MUST NOT: créer de nouveaux fichiers de doc, ni toucher aux autres sections. Parallelization: Wave 6 | Blocked by: 19 | Blocks: — References (executor has NO interview context - be exhaustive):README.md(sections « Règles implémentées et limites de la Phase 0 », « Feuille de route »),pyproject.toml,.gitignore,tests/(comptage final) Acceptance criteria (agent-executable): gate complète verte ;uv run pytest -qaffiche ~500+ tests ; README sans référence obsolète (ex. « Manœuvres non modélisées », « 377 tests »). QA scenarios (name the exact tool + invocation): happy — gate complète ; failure —grep -rn "377 tests\|non modélisé" README.mddoit retourner vide (sauf mention historique justifiée). Evidence.omo/evidence/task-20-finalize-engine.mdCommit: Y | docs: finalize engine documentation
Final verification wave
Runs in parallel after ALL todos. ALL must APPROVE. Surface results and wait for the user's explicit okay before declaring complete.
- F1. Plan compliance audit — les 20 todos sont terminés, evidence
.omo/evidence/task-<N>-finalize-engine.mdprésente pour chacun, gate verte sur le commit final. - F2. Code quality review —
uv run ruff check src testsetuv run basedpyright srcpropres, aucune suppression de type (as any/@ts-ignoren/a en Python), pas decatchvide, pas de régression des règles existantes (empilement des bonus, AoO mouvement/tir). - F3. Real manual QA — CLI :
uv run pf1e-sim --map data/maps/sample_arena.yaml --side players data/pcs/tammara.json --side monsters data/monsters/lich.json --runs 100 --seed 1et--side players data/pcs/oni.json --side monsters data/monsters/goblin.json data/monsters/goblin.json; vérifier dans les transcripts : sorts lancés, summon, manœuvres, ressources consommées ; aucun crash. - F4. Scope fidelity — les 8 PJ jouables via CLI ; AUCUNE modification de
fiches_personnages/,.omo/(hors plans/evidence),.codegraph/; pas de couche LLM ; pas d'entrées de la base complète PF1e ;git statusne montre que des fichiers attendus.
Commit strategy
- Un commit par todo (20 commits attendus), message conventionnel
type(scope): summaryen anglais, conforme au style du dépôt (test(loaders): add unit tests for combatant feat resolution…). - Gate (
ruff+basedpyright+pytest -q) VERTE avant chaque commit. En cas d'échec : corriger, re-vérifier, puis committer. - Le push reste manuel (fait par l'utilisateur).
Success criteria
- Gate finale verte :
uv run ruff check src tests && uv run basedpyright src && uv run pytest -q(≈500 tests). - Les 8 PJ jouables via CLI avec sorts lancés (politique de sort), ressources consommées, capacités de classe actives, compétences et manœuvres opérationnelles.
- Scénario doré : tammara vs liche et oni vs gobelins reproductibles et déterministes.
- README exact, sans référence obsolète ; scope respecté (pas de base complète PF1e, pas de LLM,
fiches_personnages/intactes).