Compare commits

...

140 Commits

Author SHA1 Message Date
ctan f61fc5c61f docs: reorganize documentation structure
- Clean README.md with installation, usage, CLI, examples
- Move data formats to docs/data-formats/ (map.md, monster.md, pc-sheets.md, index.md)
- Move architecture to docs/architecture/ (index.md, technical-architecture.md, phase-2-llm-architecture.md)
- Add contributing guidelines in docs/contributing.md
- Clean README.md: install, usage, CLI, data-formats/rules/architecture links, contributing link
2026-09-26 20:12:05 +02:00
ctan b0bd0b95f4 docs(phase2): record the P5.3 report writer
- Mark P5.3 (rapport FR) as completed in architecture doc
- Update test count to 2238 (2211 + 27 new tests)
- Document report.py module, prompt pack, CLI flags, budget integration
2026-09-26 10:43:14 +02:00
ctan 98985ee6fc feat(phase2): llm report writer (P5.3)
- New module src/pf1e_simulator/report.py with ReportContext, build_report_context,
  LLMReportPolicy, ReportBudget, format_llm_report, build_report_prompt
- Prompt pack fixture: tests/fixtures/prompt_pack_fs/report.md (FR narrator)
- CLI integration: --llm-report + --output flags, mutually exclusive with --transcript
- Budget enforcement: ReportBudget integrates with CostBudget, allows max_retries+1 calls
- Tests: test_report_context, test_report_prompt_pack, test_llm_report_policy,
  test_format_llm_report, test_cli_llm_report, test_report_budget (27 new tests)
2026-09-26 10:40:58 +02:00
ctan c454a67b84 docs(phase2): record the P5.2 few-shot prompts and repair loop 2026-09-25 23:24:29 +02:00
ctan dc890fc8fd feat(phase2): few-shot prompts + bounded repair loop (P5.2) 2026-09-25 23:24:27 +02:00
ctan d4db4f6d33 docs(phase2): record the P5.1 provider client and plan status 2026-09-25 22:22:30 +02:00
ctan 42802f7bf5 feat(phase2): OpenAI-compatible provider client (P5.1) 2026-09-25 22:22:03 +02:00
ctan 790df17623 docs(phase2): record the P4 robustness measurements and plan status 2026-09-25 21:34:13 +02:00
ctan cd69312ff4 feat(phase2): adversary and robustness cartography (P4) 2026-09-25 21:34:11 +02:00
ctan 016d6e1459 docs(phase2): record the P3 optimizer playbook and plan status 2026-09-22 15:44:06 +02:00
ctan dd64c2ebd6 feat(phase2): optimizer and scenario compiler (P3) 2026-09-22 15:23:05 +02:00
ctan 115f54a7e5 docs(phase2): record the P2 sweep measurements and plan status 2026-09-22 13:48:01 +02:00
ctan dbd1a5822e feat(phase2): strategy compiler and sweep runner (P2) 2026-09-22 13:46:05 +02:00
ctan 15d24be638 feat(phase2): P1c 2026-09-22 12:21:23 +02:00
ctan de4645838b feat(phase2): provider-agnostic LLM client + composite cache (P1b) 2026-09-22 09:45:18 +02:00
ctan f0617a0de6 feat(phase2): external config + versioned prompt pack (P1a)
config.py centralizes phase-2 path/env reads (no hard-coded data paths,
section 15). prompt.py loads the EXTERNAL, versioned prompt pack (section
14): pack version + per-prompt content hash feed prompt_version for the
response-cache composite key (D6); missing/invalid packs raise a clear
PromptPackError, never a silent fallback. Minimal fixture pack under
tests/fixtures/prompt_pack/ pins CI to a local, versioned pack.
2026-09-22 09:21:28 +02:00
ctan 26c5b51b98 docs(phase2): record the runner entry points (P0.4) and replay (P0.5)
run_encounter gains llm_policy and per-run policy_factory (mutually
exclusive with policy/llm_policy); replay.py logs every (observation,
action) keyed by call_seq to JSONL and replay() reproduces the recorded
battle bit-identically (wins/draws/rounds/transcript) with mismatch,
exhausted, duplicate and out-of-order guards. P0 frontier complete: the
recorded run replays identically.
2026-09-22 08:45:52 +02:00
ctan 6c6de16e60 feat(phase2): replay log and replay policy (P0.5)
Add replay.py: a ReplayLog records every (observation, action) pair a
policy produced, keyed by monotonic call_seq, plus the EncounterSpec,
seed and round_cap, and serializes the whole battle to JSONL. replay()
turns a log back into an observation-only LLMPolicy that returns each
recorded action, raising ReplayMismatchError on a differing observation
and ReplayExhaustedError past the last call.

Observations are matched by their canonical JSON (observation_key), not
raw dataclass equality, because JSON collapses tuples and lists and the
snapshot's inner dict[str, object] holds both. Action and the spec are
dumped through Pydantic TypeAdapters (mode=json) so tuple fields survive
as arrays and are coerced back on load.

Move TrickCondition/ReadyTrigger out of the TYPE_CHECKING block in
combat.py: TypeAdapter(Action) must resolve them at runtime to serialize
the dataclass, so the Literal aliases now live at module level.
2026-09-21 15:08:34 +02:00
ctan 334f8f9d05 feat(phase2): runner llm_policy and policy_factory entry points 2026-09-21 14:46:25 +02:00
ctan ac7fe20054 docs(phase2): record the compact view measurements (P0.2) and candidates (P0.3)
Compact render is 15,691 bytes (~3.9k tokens) = 24.4% of the raw 64,204-byte
to_dict() snapshot on the representative 7-combatant encounter, hitting the
~2-4k token target; grids are sparse and determinism is pinned in tests.
candidates() derives plausible actions from the pairs with no second legality
engine (swift/free/full_round excluded).
2026-09-21 14:36:24 +02:00
ctan 8b5043072a feat(phase2): candidate actions 2026-09-21 13:46:05 +02:00
ctan 9da9537995 feat(phase2): compact observation view for LLM prompts
Build a decision-sufficient ObservationView from the raw snapshot and
render it as a deterministic line-based prompt string. The raw to_dict()
is ~64 KB (~16k tokens) for a 7-combatant encounter; the compact view
renders at ~15.7 KB (~3.9k tokens, 24% of raw), under the 4k-token
ceiling of the phase-2 architecture doc.

Compaction: sparse terrain/elevation/threatened grids, per-combatant
key selection (occupied_cells, abilities, skills, initiative_mod/total,
temp_hp_rounds_left, features, carried_items, weapon_damage, aware,
alignment, armor_spell_failure, arcane_caster, run_rounds dropped),
attacks compacted to formula+type damage strings, spells to 7
decision-relevant fields, pair target_index replaced by the stable
combatant id, condition_definitions dropped. Attacks and spells render
as positional tuples declared by attack_fields/spell_fields legend
lines, with '-' for missing values.

Tests pin the size bounds (compact < 0.25 x raw and < 16000 bytes), the
sparse-grid contract, the dropped-key contract and the deterministic
id/render contract on a caster-heavy 3v4 representative battle.
2026-09-21 13:21:33 +02:00
ctan 25418f164f docs(phase2): record the executed spike (measurements + first danger map)
Measures: observation 64 KB (~16k tokens) for 7 combatants / 22 KB for a 2v2;
124 policy calls per battle, i.e. 124k calls per 1000-run sweep cell -> a
per-turn LLM in a sweep is infeasible (D9 validated) and a compact view is
mandatory. Danger map (default vs focus-fire monsters, 100 runs): the default
policy understates danger (PC deaths 13%->24% and 0%->19%, TPK 0%->2%) and the
win rate alone misleads (rises to 98% while deaths rise) -> D12/D13 validated.
Notes the spike limits and the remaining P0.2-P0.5 items.
2026-09-21 12:15:16 +02:00
ctan de88543c5c feat(phase2): danger-cartography spike - per-run outcomes, objectives, focus-fire adversary
run_encounter gains an opt-in collect_runs flag returning one RunOutcome
per battle (winner, rounds, per-combatant id/side/hp/hp_max/dead) next to
the EncounterReport; default behavior is unchanged. objectives.py provides
the pure optimizer primitives: P(survives all), P(TPK), P(>=1 death),
expected hp lost, expected rounds and a worst-decile (CVaR-style) tail
metric. phase2_spike.py holds the spike-level strengthened monster policy
(focus fire on the lowest-AC enemy, ties to the wounded; party side stays
on default_policy). tests/test_phase2_danger_map.py prints the danger-map
sketch: warband and lich scenarios measured default vs focus-fire at
100 runs / seed 11, pinning the delta deterministically.
2026-09-21 12:07:23 +02:00
ctan e5c4892a57 test(phase2): measurement spike - observation size and policy calls per battle
Representative 7-combatant caster-heavy encounter on sample_arena,
measured through the real engine path (engine._build_observation at each
policy call, delegating to default_policy):
- observation: 64,204 bytes at first call, 67,712 max mid-battle (~16.9k tokens)
- policy calls/battle: 124 (7 combatants, round_cap=20), 12 for a 2v2 duel
- projected calls per sweep cell: 124 / 12,400 / 124,000 at runs 1/100/1000
Loose bounds (obs < 200 KB, calls/battle < 200) + seed-determinism pinned.
2026-09-21 11:26:24 +02:00
ctan 3c3e30634e feat(phase2): observation-only LLMPolicy entry (D1) + pinned schema_version (P0.1)
- LLMPolicy is now Callable[[Observation], tuple[Action, ...]]: the snapshot
  is the policy's sole information source; the live CombatantState is no
  longer passed to new-style policies.
- CombatEngine._llm_adapter keeps a compatibility path for pre-D1
  two-argument callables: the signature is inspected ONCE at wrap time
  (_accepts_state) and legacy policies still receive (observation, state).
- Observation.to_dict() carries schema_version (OBSERVATION_SCHEMA_VERSION=1)
  as its first pinned key; bump only on removal/meaning change.
- Pinned keyset tests updated (test_observation, test_observation_enriched);
  new tests: observation-only e2e, legacy 2-arg e2e through the adapter,
  schema_version pin + JSON-serializability.
- README: documents the new entry signature and the schema_version key.
2026-09-21 11:10:28 +02:00
ctan 3fd9860b5c docs(phase2): record the arbitrated decisions (external prompts, cartography-first)
All recommendations accepted by the user except prompt location: prompts live
outside this repo (separate pack, configurable path, versioned; prompt_version
feeds the cache key and the replay record; a pinned pack for integration, a
minimal local fixture for unit tests, no silent fallback). Also records the
repo-scope decision (code only: calculation + LLM + report) with the final
detachment of data/sheets/prompts as a backlog item and the design constraint
to avoid hardcoded data paths in the new code. Priority: cartography first.
2026-09-21 10:49:00 +02:00
ctan d794828c7e docs(phase2): add the optimizer and adversarial-cartography layers
The execution tiers answer 'how to run a tactic', not 'which tactic is best'
nor 'where is the danger'. Adds two decision layers: an optimizer over
StrategySpec (objectives incl. tail risk, coarse-to-fine search, LLM as
candidate seeder) and an adversarial cartography (AdversarySpec + alternating
min-max + scenario sweeps + danger frontier + sensitivity), giving the danger
view for BOTH sides. Reframes the LLM as hypothesis generator / red team /
explicator / reporter, never the optimizer or per-turn executor in sweeps.
Adds D12-D14 and phases P3 (optimizer) / P4 (robustness).
2026-09-21 10:27:11 +02:00
ctan 630f5321e0 docs(phase2): revise the LLM architecture after critique
Key correction: the LLM must not sit on the per-turn hot path of a Monte
Carlo sweep (runs x rounds x combatants calls, no cross-run cache reuse).
Adopts a two-tier design - a compiled, parameterised deterministic Policy for
quantitative sweeps (tier A) and a per-turn NLPolicy for a handful of showcase
battles (tier B). Also fixes: 'engine unchanged' -> rules unchanged/additive
boundary; replay keyed by a monotonic call_seq (delay re-queues, summons);
composite response-cache key incl. model id; ObservationView as a structured
object; candidates derived from pairs instead of a second legality engine;
per-run PolicyFactory; cost guardrails in P1; concurrency; D1 to settle in P0.
2026-09-21 09:55:53 +02:00
ctan be64451ba4 docs(phase2): architecture and implementation plan for the LLM decision layer
Design-only document (no code): target architecture (compact versioned
observation view, legal-actions API, LLM client + cache, NL policy with
parse/validate/repair/fallback, strategy sweep, replay/eval harness), the
concrete gaps in the current runner/CLI wiring, the decisions to settle and a
phased implementation plan (P0 foundations, P1 client+policy, P2 strategy
sweep, P3 provider+report) with exit criteria and risks.
2026-09-21 09:51:05 +02:00
ctan 7e4a9f5edf docs(rules): conformance journal round 15 - nonlethal, splash, skills and flight (out of scope -> modeled)
Brings in the last four out-of-scope mechanics: the nonlethal damage track
(unarmed strikes, minimum damage), splash weapons (direct + scatter),
combat skills (skill checks, Acrobatics Tumbling, Stealth/opposed Perception)
and 3D flight over ground terrain (vertical line of effect deferred). Updates
the out-of-scope and residual tables to drop the now-modeled items and marks
the round-2 disposition tables as historical. Gate 1981.
2026-09-20 22:35:55 +02:00
ctan 9210a54589 docs(rules): document 3D flight over ground terrain (airborne view: difficult terrain cost 1, cross non-LoS impassable cells, blocks_los still stops flight; elevation climb still applies; LoE/cover/high-ground unchanged) and the deferred 3D line-of-effect/maneuverability with SRD citations (Movement, Position and Distance; Aiming a Spell); map legend + grid.py notes, test count 1981 2026-09-20 22:29:05 +02:00
ctan 2ae86ab29f feat(rules): 3D flight over ground terrain - airborne creatures (fly >= land, V4 budget from fly speed) move on Grid.airborne_view: ground difficult terrain costs 1 and cells impassable to walkers (pits, water, rubble, low obstacles) are crossable at cost 1, EXCEPT blocks_los cells (solid barriers, assumed full height) which still stop flight; run/charge/5-foot-step gates and diagonal corner rule follow the same view (flying past a pit is legal, past a wall corner is not); elevation climb cost still applies (step heights, not wall heights - documented); line of effect, cover and high ground use the unchanged real grid so combat geometry is unaffected by flight (SRD Movement, Position and Distance: flying creatures not hampered by difficult terrain, able to avoid most obstacles), 11 tests 2026-09-20 22:21:40 +02:00
ctan 1257bb3849 docs(rules): document skill checks, Acrobatics Tumbling and Stealth in README (rule list, observation schema hidden_from, out-of-scope updates, test count 1970) 2026-09-20 21:48:02 +02:00
ctan 2c40994610 feat(rules): Stealth + opposed Perception - opt-in hide move action (Stealth d20 + trained class + ACP + size mod vs each enemy Perception with -1/10ft distance; observed creatures cannot be hidden from), per-observer hidden_from state treated as total concealment + unseen-attacker +2/deny-Dex for failed observers, attacking or running/charging breaks stealth, move/5-foot-step/withdraw re-check the opposed roll, observation exposes hidden_from (SRD Stealth/Perception/Skill Checks), 13 tests 2026-09-20 21:39:54 +02:00
ctan 61686b5f4e feat(rules): Acrobatics TUMBLING opt-in on move - half speed budget, one Acrobatics check per crossed opponent (DC = CMD, +5 fresh entry into its space trained-only, +2 per additional opponent beyond the first), success spends the opportunity without a strike, failure provokes the AoO and ends the movement (SRD Acrobatics Tumble / Moving Through a Square), transcript logs each check, 9 tests 2026-09-20 21:06:12 +02:00
ctan e0fb5beae2 feat(rules): skill-check subsystem foundation - public skill_check helper (d20 + ranks + ability + trained-class-skill +3 + armor check penalty + typed bonuses, one-shot skill track discharged, no natural 1/20 auto-success/fail per SRD Skill Checks), resolve_skill delegates to it, class-skill +3 now requires invested ranks (Table 4-2 Trained Class Skill row), 8 tests 2026-09-20 20:48:26 +02:00
ctan 678a4d8b31 feat(rules): splash weapon test suite - direct hit full damage + 1 splash to adjacent only, miss scatters d8 clockwise one range increment dealing splash alone (thrower can be caught), energy resistance/immunity apply, DR does not, splash never crit-multiplied, no precision damage, non-splash RNG stream bit-identical, schema rejects splash on melee, observation exposes splash; transcript logs splash after the damage segment 2026-09-20 19:33:12 +02:00
ctan a1b1c29ca3 feat(rules): expose splash in observation attack reference + AttackResult.splash, document splash weapon rule and JSON schema in README (SRD Throw Splash Weapon) 2026-09-20 19:23:15 +02:00
ctan 2e60bbb419 feat(rules): splash weapon resolution - direct hit splashes 1 energy to all within 5 ft (target excluded), miss scatters d8 (1=short toward thrower, 2-8 clockwise) one range increment and splashes the landing + adjacent squares; energy resistance applies, DR and crit never; no precision damage (SRD Throw Splash Weapon) 2026-09-20 19:18:46 +02:00
ctan 9cfcd67f70 feat(rules): SplashSpec data model - data-driven splash marker on thrown weapons, Alchemist's Fire splash 1 fire on oni/tammara (SRD Throw Splash Weapon, Alchemist's fire) 2026-09-20 19:11:39 +02:00
ctan 83c6e541ac feat(rules): nonlethal sources - unarmed strikes deal nonlethal by default (SRD Unarmed Attacks), Minimum Damage floor lands 1 nonlethal (CRB Damage), transcript + AttackResult expose the track 2026-09-20 18:54:22 +02:00
ctan cba7bbf3c9 feat(rules): nonlethal damage track - separate total, derived staggered/unconscious, max-hp overflow, undead/construct immunity, healing removal (SRD Nonlethal Damage) 2026-09-20 18:41:53 +02:00
ctan 3d0fd0c101 docs(rules): conformance journal round 14 - transcript/format audit (14 bugs fixed)
A read-only audit of the transcript rendering, map and loader formats found
14 confirmed bugs (4 Med, 10 Low: duplicate --side, id-disambiguation
collision, range_increment_ft division by zero, silent spell_slots/features
drops, negative round-cap, multi-char legend keys, blank rows, id parsing,
duplicate YAML keys, move_cost default) - all fixed. The transcript/format
area is audited; only the structural out-of-scope items remain. Gate 1900.
2026-09-20 18:08:29 +02:00
ctan dd0b42b73b docs(map): make omitted move_cost explicitly mean impassable 2026-09-20 18:03:46 +02:00
ctan ee22f7548a fix(map): reject duplicate YAML keys via a SafeLoader duplicate-check 2026-09-20 18:00:27 +02:00
ctan 53c6dc0b43 fix(models): reject combatant ids that misparse the transcript format 2026-09-20 17:51:40 +02:00
ctan bce0a59e60 fix(map): fail on blank rows inside a layer, keep trailing-space cells verbatim 2026-09-20 17:48:47 +02:00
ctan 205ddece0b fix(map): require single-character legend keys 2026-09-20 17:43:50 +02:00
ctan c46e636d12 fix(cli): require --round-cap >= 1 2026-09-20 17:40:29 +02:00
ctan 10f811e80e fix(models): require a positive range_increment_ft (reject 0 and negatives) 2026-09-20 17:36:45 +02:00
ctan e9c2a63122 fix(runner): disambiguation suffix skips ids already taken by explicit combatants 2026-09-20 17:33:35 +02:00
ctan de9309ca43 fix(cli): reject duplicate --side names with a clear error 2026-09-20 17:29:10 +02:00
ctan 130087a556 fix(map): reject a zones layer without a deployment section
The README documents deployment as required when zones exist, but load_map
only enforced the reverse direction (deployment without zones). A map with
a zones layer and no deployment now fails with MapValidationError instead
of loading with unreachable deployment zones.
2026-09-20 17:15:29 +02:00
ctan a8c7e37f8e fix(loaders): resource pools validated consistently before grant merge
_merge_resource_grants silently dropped non-int pool values when features
were present (a "5" pool vanished, leaving only the feat grant) while
Pydantic coerced the same value when features were absent. _validated_resources
now runs the JSON resources through TypeAdapter(dict[str, int]) -- the same
lax coercion as Combatant.resources -- so both paths coerce-or-fail
identically, and grants are added to the coerced ints.
2026-09-20 17:13:09 +02:00
ctan 9adc87c9e1 fix(loaders): coerce combatant level with Pydantic-consistent rules
Feat resolution read level with an isinstance(int) check and fell back
to 1, while Pydantic accepts numeric strings and lossless floats: a
level-4 sheet authored as "4" got Toughness for level 1. _parse_level
now runs the value through TypeAdapter(int) (same lax coercion as the
model) and raises CombatantLoadError for values the model would reject.
2026-09-20 17:05:36 +02:00
ctan b8e96294a6 fix(loaders): non-list features and blank/non-string entries fail cleanly
features was popped and only re-added when it was a list, so a bare
string (or dict/null) loaded with zero feats - silent data loss. Now a
present non-list features raises CombatantLoadError, and the list
resolution adopts an accept-string-or-error contract: non-string or
blank entries raise instead of being silently dropped.
2026-09-20 17:02:39 +02:00
ctan 9656ae820a fix(loaders): malformed spell_slots key raises CombatantLoadError, not bare ValueError
int(k) on a non-numeric JSON key escaped the loader as an unhandled
ValueError traceback (CLI exit 1) instead of the documented exit code 2.
Route the conversion through _parse_spell_slot_keys which wraps it in a
CombatantLoadError with a clear message. Audited the other loader-side
int()/conversions: foundry.py guards every int() with isinstance/isdigit
checks and combatant.py has no other unguarded conversion.
2026-09-20 16:58:57 +02:00
ctan 5911602736 docs(rules): conformance journal round 13 - objects/inventory (out of scope -> modeled)
Sunder now uses object hardness/HP with the SRD broken condition (usable at
-2), steal transfers real carried items and unarmed disarm auto-picks up the
dropped weapon. Objects/inventory leave the out-of-scope list; skills,
nonlethal, splash, 3D flight and the transcript remain. Gate 1861.
2026-09-20 16:10:00 +02:00
ctan d0f7f3a107 feat(rules): carried items + real steal transfers, unarmed-disarm auto-pickup
- Combatant.carried_items (APG Steal: non-wielded, non-closely-worn gear),
  loaded from JSON, seeded into CombatantState, exposed in the snapshot.
- _maneuver_steal: success transfers the first carried item victim -> thief
  (logged); empty list keeps the documented nothing-stealable behaviour.
- _maneuver_disarm: unarmed attacker's success auto-picks the dropped weapon
  into its carried_items (SRD Disarm); armed disarms unchanged. Dropped-weapon
  recovery via retrieve_weapon (move action) verified + pinned by tests.
- data/pcs: gear transcribed from Foundry sheet inventories (esha 13,
  jeanne 3, misty 9, nairda 7, oni 6, tammara 20); harvie/ierlieth/
  esha_mount and all monsters empty (no stealable inventory), noted in README.
2026-09-20 16:05:07 +02:00
ctan 74f5d64c43 feat(rules): sunder deals real weapon damage vs object hardness/hp; broken weapons usable at -2 with restricted crits
SRD Sunder + CRB 'Smashing an Object' / 'Conditions - Broken': weapons
carry Table 7-12 object stats (AttackSpec.hardness/hit_points/enhancement,
defaults by weapon class); a successful sunder deals the attacker's actual
physical weapon damage minus the target weapon's hardness, subtracted from
its hp. At <= half hp the weapon gains the broken condition - still usable
at -2 attack/damage, crits only on a natural 20 at x2. At <= 0 it is
destroyed (weapon_destroyed, unusable). The old flat str_mod+2 / durability
10 / synthetic 'disarmed' condition is gone. Firearm misfire carve-out kept
(broken firearms stay unusable until Quick Clear - misfire escalation not
modeled, documented).
2026-09-20 15:43:45 +02:00
ctan 544d870688 docs(rules): conformance journal round 12 - spell components and arcane spell failure
107/108 spells carry their SRD components; arcane_caster and
armor_spell_failure are set from the sheets; the deafened 20% verbal
failure and the arcane spell failure roll are modeled. Components/ASF
leave the out-of-scope list. Gate 1850.
2026-09-20 14:38:42 +02:00
ctan 9f72948d23 docs(rules): README - component/ASF data now encoded, dedup snapshot bullet
The components/ASF bullet no longer defers the data encoding (done in
the two preceding commits): 107/108 registry spells carry their KB
components (Create Alcool is a homebrew cantrip with no KB entry) and
all 15 combatants carry arcane_caster plus an effective
armor_spell_failure - zero everywhere because the shipped arcane casters
are unarmored or wear light armor whose ASF their magus/bard feature
annuls for their own-list spells. Also collapses an accidental
duplicated armor_spell_failure/arcane_caster description in the
observation snapshot bullet.
2026-09-20 14:34:33 +02:00
ctan f6292f8bdc feat(rules): encode arcane_caster and armor_spell_failure from the sheets
- arcane_caster true: misty (Magus/Bard sheet class), nairda (Witch/
  Sorcerer), thrune_agent (Human magus 8, KB bestiary/thrune-agent.md);
  lich already true. False: oni (Cleric), tammara (Gunslinger/
  Warpriest - divine), esha (Cavalier/Rogue, spell-like only), the
  companions harvie/ierlieth/jeanne/esha_mount and the remaining
  monsters.
- armor_spell_failure stays 0 everywhere, as the effective PF1e value:
  nairda and the lich wear no armor (sheet note "Pas d'armure", KB lich
  block); misty's mithral shirt (RAW ASF 10%, magic-items/mithral-shirt
  .md) and the thrune agent's +1 studded leather (15%) are light armor
  and every spell they cast is on their own class list - magus and bard
  cast their own spells in light armor with no arcane spell failure
  (classes/magus.md, classes/bard.md).
- tests/test_spell_failure.py: the lich/oni smoke test becomes a
  full-data table pinning all 15 combatants' flags with per-entry
  sources. No shipped combatant incurs a non-zero ASF; the failure roll
  itself stays covered by the synthetic armored-caster tests.
2026-09-20 14:30:57 +02:00
ctan 91666d84b9 feat(rules): encode SRD spell components for all 108 registry spells
- Every data/spells/*.json now carries a "components" field with the
  V/S/M/F/DF tokens from its KB Components line (material/focus
  descriptions dropped; M/DF and F/DF alternatives kept verbatim).
- 107/108 encoded. Create Alcool (data/spells/create_alcool.json) is
  left without the field: it is a homebrew cantrip (level 0) with no KB
  entry - the closest real spell, Enhance Water (Inner Sea Gods, V, S),
  is already a separate registry entry.
- summon_monster_i/ii map to the KB's numeric files (summon-monster-1/2);
  hidden_diplomacy maps to its KB name Egorian Diplomacy (same spell,
  Spymaster's Handbook rename): V, S, M.
- tests/test_spell_failure.py: the placeholder asserting the registry was
  not yet encoded is replaced by an encoded-state smoke test (all spells
  but Create Alcool declare components; five spot-checks against the SRD
  stat blocks). Golden transcripts unchanged: the component gates still
  need deafened/ASF conditions no golden combat has.
2026-09-20 14:21:15 +02:00
ctan 1f8f068dd1 feat(rules): spell components + deafened verbal failure and arcane spell failure rolls
- SpellSpec.components (frozenset of V/S/M/F/DF, JSON key accepting the RAW
  string notation "V, S, M/DF" or a token list; slash = alternative; unknown
  token is a data error). Default empty = not encoded yet -> no failure.
- Combatant.armor_spell_failure (0-100 percent, authored on the JSON - the
  model has no equipment) and arcane_caster (divine casters never incur ASF);
  both load directly through load_combatant.
- _cast_spell: after the start-of-casting gates and before delivery, two
  conditional d100 rolls (convention mirrors the concealment roll, <=):
  deafened + V component spoils the spell on <=20 (SRD cast-a-spell.md);
  arcane_caster + S component + armor_spell_failure > 0 fails the spell on
  <= the ASF percent (SRD arcane-spells-and-armor.md). The slot stays spent
  (mirrors the concentration fizzle). No condition -> no roll: the RNG
  stream is bit-identical for every current combat (registry encodes no
  components yet - full encoding is a follow-up task).
- Observation: spell summary gains "components" (sorted tuple), combatant
  snapshot gains "armor_spell_failure"/"arcane_caster"; README documents
  the keys and the two former out-of-scope bullets now describe the modeled
  rule with its data-encoding follow-up.
- Data smoke test: lich arcane_caster true / ASF 0, oni arcane_caster false.
- tests/test_spell_failure.py: 21 tests (parsing, thresholds <=20 / <=ASF,
  divine/no-ASF/no-component never roll, roll order, slot stays spent,
  observation keys). Golden transcripts unchanged.
2026-09-20 14:02:10 +02:00
ctan a483070e13 docs(rules): conformance journal round 11 - alignment subsystem (out of scope -> modeled)
Adds the alignment field + axis flags, the AlignmentGate on spell damage
(Shard of Chaos, Spear of Purity) and the attacker-alignment-conditional
wards (Protection from Evil/Law + communal). Alignment leaves the
out-of-scope list; objects/inventory, skills, nonlethal and ASF are recorded
as the remaining out-of-scope mechanics. Gate 1829.
2026-09-20 13:02:15 +02:00
ctan 3be22b9c55 feat(rules): alignment-gated protection from evil/law wards via attacker context
Add attacker-alignment modifier conditions (attacker_is_evil/lawful/good/
chaotic) evaluated against an optional attacker context threaded through
_stat_modifiers/_modifier_condition_holds, _attack_ac_mods (resolve_attack
AC path), resolve_save and _area_save (spell save paths), plus the fear
aura. Without a source in scope the condition is False, so existing rolls
are untouched (golden transcripts bit-identical).

Give the four protection_from_evil/law(+communal) JSONs their SRD buff:
+2 deflection to AC and +2 resistance on fort/ref/will, gated on the
attacker/caster alignment (SRD: bonuses apply 'against attacks or effects
created by evil creatures'); duration 1 min/level approximated as 10
rounds (engine convention). Descriptions document the unmodeled mental-
control suppression and communal duration-split clauses. Level/range/
save/SR verified against the KB.
2026-09-20 12:57:29 +02:00
ctan 6a18d456f6 feat(rules): alignment-gated spell damage for Shard of Chaos and Spear of Purity
DamageEffect gains an optional AlignmentGate (axis law_chaos/good_evil):
the immune side (chaotic/good) is wholly unaffected (no damage dice rolled),
neutral-on-axis targets take half damage with no condition (gate halving
stacks after the save halving), and the full side (lawful/evil) takes full
damage plus the gate condition on a failed save. Shard of Chaos maps the
SRD slowed to the engine staggered condition (the -1 penalty and half-speed
of slow are not modeled); the outsider-only 1d6/level variants are not
modeled (no outsider subtype). Non-gated damage spells are bit-identical.
Loader validates axis/side pairing with clear ValueErrors.
2026-09-20 12:32:14 +02:00
ctan f9e51e377a feat(rules): add alignment field to Combatant with axis gating flags
Adds the PF1e two-axis alignment code (LG/NG/CG/LN/N/CN/LE/NE/CE,
default N) to the Combatant model with a strict validator, derived
is_lawful/is_chaotic/is_good/is_evil properties, exposure in the
observation snapshot, and alignment values for all monsters and PCs
sourced from the bestiary KB and the Foundry sheets.
2026-09-20 11:49:04 +02:00
ctan 40088e1869 docs(rules): conformance journal round 10 - utility spells (guidance, resistance, virtue, stabilize)
Guidance (new OneShotEffect), resistance (+1 resistance save buff, with the
cantrip-skip policy guard), virtue (temporary HP track) and stabilize
(auto-stabilization) are now modeled; spiritual_weapon and saving_finale
remain deferred with a precise engine reason. Gate 1734.
2026-09-20 10:52:22 +02:00
ctan f7886135bd fix(rules): stabilize is a StabilizeEffect that auto-stabilizes a dying creature 2026-09-20 10:48:08 +02:00
ctan a8a65b18cd fix(rules): virtue is 1 temporary hit point for 1 minute 2026-09-20 10:32:05 +02:00
ctan 1238c40e43 fix(rules): resistance is a +1 resistance bonus on fort/ref/will saves for 1 minute - SRD spells/resistance.md grants the subject a +1 resistance bonus on saving throws for 1 minute (Will negates, harmless); the resistance bonus type already exists in the stacking model (effects.py BonusType), so the buff is expressible as a BuffEffect and was only deferred because _find_buff_spell returns the FIRST BuffEffect in known-spell order and cantrips cost no slots - encoding it would have made default_policy cast Resistance every round (for Tammara it would preempt Divine Favor and never kill anything); resistance.json now carries a BuffEffect (+1 resistance on fort/ref/will, 10 rounds = 1 minute) and _find_buff_spell skips level-0 spells (cantrips have no spell slot in the engine's budget - spell_slots_left has no level-0 key - and the policy deliberately never auto-casts them, a cantrip buff would be cast every round degrading the policy), so Resistance cannot preempt a leveled buff (Divine Favor) in the known-spell order; leveled buffs keep the historical first-match behavior (if the first one's slots are exhausted the buff branch gives up - the golden pins that); the resistance bonus type does not stack with itself (resolve_modifiers keeps the higher value); +9 tests (test_resistance.py: buff shape, cast on a willing ally consumes no d20 and applies without a save roll, the bonus rides save rolls, no self-stacking, expiry at 10 rounds, _find_buff_spell skips the slot-less cantrip / returns None without a leveled buff, _choose_spell_action proposes Divine Favor and never Resistance); golden untouched; 1714 tests green 2026-09-20 10:10:32 +02:00
ctan 08f9af9e3e fix(rules): guidance is a one-shot +1 competence bonus on a single attack roll - SRD spells/guidance.md grants '+1 competence bonus on a SINGLE attack roll, saving throw, or skill check' (duration 1 minute or until discharged); the engine had no spell effect that could write the one-shot attack track (aid_attack_bonus is hardwired to aid-another +2 and expires at the start of the beneficiary's next turn), and a persistent BuffEffect would misrepresent the discharge rule (it would boost every roll for a minute) and would become the first buff spell in the casters' known-spell order, degrading default_policy; a new OneShotEffect type is added (track/value/bonus_type/duration_rounds, parser 'one_shot', SpellEffect union, summary 'one-shot +X type on track'), stored as ActiveOneShot on CombatantState (one_shot_bonuses, decremented with the other duration-tracked effects, exposed in the snapshot), discharged by the FIRST roll of the encoded track via _discharge_one_shot (best value wins, each casting is its own charge) and folded into the resolve_modifiers pool so bonus-type stacking applies; the attack track rides the attack roll only, NOT the crit-confirm roll (the SRD grants it to a SINGLE attack roll, so unlike aid another the confirmation is untouched - _attack_roll_modifiers returns (pool_mod, attack_roll_mod) and resolve_attack uses the latter for the total while the confirm keeps the former); the save and skill tracks are wired in the mechanism (resolve_save folds a save-track shot into save_mods, resolve_skill discharges a skill-track shot into _skill_static_bonus via extra_mods) but guidance.json encodes the attack track only - the SRD 'attack OR save OR skill' choice has no declaration channel in the engine, so the attack-roll variant is encoded (documented); _apply_targeted_spell_effect dispatches OneShotEffect to _apply_one_shot_effect (a successful save negates it, 'one-shot negated'); guidance.json now carries the effect (1 minute = 10 rounds) and default_policy never casts it (cantrips have no spell slot and the one-shot type is not a buff/heal/offensive pick); _spell_effect_summary split into _damage_effect_summary/_non_damage_effect_summary to stay under the PLR0911 return cap; test_spell_registry_batch2._EFFECT_TYPES gains OneShotEffect; +14 tests (test_one_shot_guidance.py: cast-and-discharge on the next attack, not on the crit-confirm roll, save/skill tracks, stacking by bonus type, expiry at 10 rounds, save negation, snapshot exposure, no auto-cast by default_policy); golden untouched; 1705 tests green 2026-09-20 09:53:53 +02:00
ctan 154c3f69a0 docs(rules): conformance journal round 9 - assumed residuals closed (10 fixes, 1 deferred)
Rounds 1-3 closed the assumed residuals: run bounded by CON endurance + Run
feat, harmless spells forgo the save on willing targets, self-centered bursts
for bless/bane, Selective Channeling, the lich stat block aligned to the KB,
summon-stats disambiguation, healing_done exposure, multi-cell difficult-terrain
move pricing, and the grazed-caster-cell cone/line fix. The burst intersection
origin is deferred with a precise rationale. Rewrites the residual table to
drop the now-fixed and stale rows, leaving only the genuinely remaining items.
2026-09-20 00:46:08 +02:00
ctan d5ad3c3aac fix(rules): a grazed caster cell no longer shortens a line spell - SRD Aiming a Spell: a line 'starts from any corner of your square and extends to the limit of its range' (Lightning Bolt: 'The bolt begins at your fingertips'), so the caster's own squares are origin space the line starts PAST and must not consume one of the length_cells squares; line_cells took a fixed ray[1:length_cells+1] window and dropped caster cells inside it, so a 2x2 caster's line came out one square short per grazed footprint cell (the old code documented this as a simplification); the ray is now extended (reach = length_cells + 1 + len(caster_cells)) and caster cells are skipped WITHOUT spending a length slot, so a multi-cell line runs its full length starting just past the footprint; 1x1 casters are bit-identical (the only skipped cell is the origin at ray[0], already outside the counted window); the barrier/LoE stop and out-of-bounds semantics are unchanged (a straight ray never re-enters a convex map); golden untouched (no line spell in it); +3 tests (grazed-cell full-length template, 1x1 bit-identical regression, engine-level reach of a foe the short line missed) 2026-09-20 00:41:02 +02:00
ctan d347dde3fc fix(rules): a multi-cell creature moving under mixed terrain pays the difficult rate for its whole footprint - SRD Movement, Position and Distance: 'If you occupy squares with different kinds of terrain, you can move only as fast as the most difficult terrain you occupy will allow'; the charge/run/5-foot-step already gated on the whole footprint (_footprint_touches_difficult) but the move/withdraw DESCENT priced every step by the anchor cell alone (grid.step_cost), so a 2x2 creature straddling rubble crossed at full speed; the descent now prices a step by the most difficult square under the footprint via _footprint_step_cost -> a new grid.step_cost_with_terrain (the 5-10-5 + elevation formula stays centralized in grid.py, step_cost delegates to it); 1x1 creatures are bit-identical (footprint == anchor cell); the direction Dijkstra field remains anchor-priced (making it creature-aware rewrites grid.reachable wholesale - the documented deferral); golden untouched (open-floor arena, no difficult terrain); +7 tests (trailing-square orthogonal/diagonal cost, 1x1 bit-identical regression, full-lane move budget 3-vs-6 squares, withdraw hampering) 2026-09-20 00:29:36 +02:00
ctan b775d46850 fix(rules): document the burst center-to-center convention as a deferred SRD approximation - the SRD (aiming-a-spell.md) measures a burst from a grid INTERSECTION with the far-edge rule ('count from intersection to intersection'; a square is inside only 'if the far edge of a square is within the spell's area'), but the engine's burst origin is a CELL (target_pos, centered_on_caster, resolved target) counted center-to-center 5-10-5 in _creatures_in_range; matching RAW exactly needs an intersection coordinate added to AreaEffect/Action.target_pos/the observation area snapshot contract plus a per-caster corner choice (the far-edge boundary result depends entirely on which of the cell's four corners is the origin), so it is DEFERRED with a precise explanation in areas.py and a pointer from _creatures_in_range; no behavior change, golden untouched 2026-09-20 00:16:18 +02:00
ctan e6021a6dda fix(rules): expose healing_done in per-combatant stats and the CLI report - the channel/spell healing deliberately removed from damage_dealt (2e2873f) was invisible; CombatantStats/_LiveStats gain a healing_done counter booked with the hp actually restored (capped at the target's hp_max, overheal uncounted) by _channel_heal and by spell HealEffect on living targets, while cure-as-damage on undead stays in damage_dealt and the damage counters keep their exact semantics; the runner aggregates and averages the new field and the French report prints it as 'soins prodigués'; +4 tests (channel heal, cure spell, undead cure guard, run_encounter aggregate) +1 CLI assertion 2026-09-19 23:23:00 +02:00
ctan 5e7436b210 fix(rules): duplicate summons get disambiguated ids - the engine now counts id occurrences (seeded from the deployed roster) and gives each new summon of the same creature a build_states-style suffix (celestial_wolf, celestial_wolf-2, ...), so two summons no longer share one _LiveStats slot (the second summon used to reset the first one's counters and their attrition merged); run_encounter aggregation verified to report each slot separately; runner 'known limitation' comment updated; +4 tests (id split, distinct slots, per-slot attrition, run_encounter aggregate) 2026-09-19 23:13:06 +02:00
ctan 4f8a131ba0 fix(rules): lich stat block aligned to the KB entry - bestiary/lich.md gives hp 111 (11d6+55 plus 15 false life), Str 10, Int 22, Init +2 and 'Aura fear (60-ft. radius, DC 18)'; the JSON carried scenario-tuned 100 hp / Str 12 / Int 20 / Init +6 and the engine's feature-keyed aura constants carried 30 ft / DC 22 (BAB/touch/AC/saves/CON already conformed); the sim's own spell list is kept (the KB's prepared list includes spells outside data/spells/) and the choice is documented in the JSON notes and the README; golden tammara-vs-lich re-pinned for the two SRD-correct line changes (initiative d20=10+2=12 from the KB +2, Magic Missile DC17 = 10+1+6 from Int 22; damage unchanged - the 75 ft deployment distance stays beyond even the widened 60 ft aura, so no save die is consumed); +2 aura range tests (75 ft outside, 60 ft inside) 2026-09-19 23:05:25 +02:00
ctan 5fe2b72339 fix(rules): selective channeling modeled as a policy-chosen exclusion - Action(kind=channel_energy) gains channel_exclude, honored only with the Selective Channeling feat and capped at the Charisma modifier (SRD feats/selective-channeling.md: 'choose a number of targets in the area up to your Charisma modifier... not affected by your channeled energy'); without the feat the list is ignored and logged (SRD Normal: all targets affected); observable in current data because heal mode also heals living ENEMIES in the burst (deny them the heal) and harm mode can spare an undead ally; default_policy never excludes (opt-in, like weapon_id/destination/metamagic); golden transcripts unchanged; +5 tests 2026-09-19 22:49:40 +02:00
ctan 9f0d86273a fix(rules): self-centered bursts for bless and bane - AreaEffect gains a data-driven 'centered_on_caster' flag (bless.json/bane.json set it per SRD bless.md '50-ft. burst, centered on the caster' and bane.md 'centered on you'); the burst origin is the caster's square regardless of the resolved target or a U7b target_pos, and with no chosen origin there is no primary save - every creature in the area rolls its own (mirror of the point-cast path); snapshot area key exposes centered_on_caster; golden transcript unchanged; +6 tests 2026-09-19 22:38:39 +02:00
ctan 4665a1c245 fix(rules): harmless spells on willing targets forgo the save roll - SpellSpec gains a data-driven 'harmless' flag set on the 29 SRD '(harmless)' spells (verified one by one against the KB); a same-side/self target consumes NO save die (SRD saving-throw.md: '(harmless) ... can attempt a saving throw if it desires' + 'Voluntarily Giving up a Saving Throw'), while hostile targets and undead receiving a cure (harmful to them, Will half for undead damage) keep the roll; snapshot spell summaries expose 'harmless'; golden transcript unchanged (no harmless-save cast in it); +8 tests, 3 fixtures re-pinned for the removed die 2026-09-19 22:27:46 +02:00
ctan ea835293a0 fix(rules): run is bounded by Constitution endurance and the Run feat - after a number of consecutive run rounds equal to the CON score each further round requires a Constitution check (DC 10 +1 per prior check, no auto nat-1/20, undead/constructs exempt with no CON score); failure stops the run and forces the SRD 1-minute (10-round) rest; the Run feat grants +4 on the check and keeps the Dex bonus to AC while running (SRD Full-Round Actions - Run); heavy-armor x3 speed clause documented out-of-scope (no armor field); +5 tests 2026-09-19 22:02:36 +02:00
ctan 28bc624d1e docs(rules): conformance journal round 8 - adversarial verification and 8 new fixes
An independent pass confirmed the sampled High/Med fixes are genuinely
implemented, then found previously-undocumented divergences in the default
policy (spell combat without a slot, out-of-range heal wasting the turn,
wasted grit on a melee deed, fixed hex range), dice (wrong exception for
d1), the channel-heal trigger, and the foundry migration tool. All fixed;
corrects the stale 'goblin dies round 4' note (now round 5) and records the
Bless centering residual.
2026-09-19 19:15:27 +02:00
ctan 1b43f4eef1 fix(docs): README states data/pcs/*.json are the canonical PC sheets and foundry.py is a one-shot incomplete migration tool - re-running it and regenerating the JSON would regress the manual/SRD conformance fixes (natural/secondary/firearm flags, Jeanne hooves reach 10, Ierlieth fly 40, Misty bite 1d6, feats beyond its 8-entry map, spell_slots/resources/casting_ability); refresh dev test count 1634 -> 1647 2026-09-19 19:10:38 +02:00
ctan 238e5f201e fix(combat): witch hex trigger uses the SRD close range scaled by caster level (25 ft + 5 ft per 2 levels, extracted as spells.close_range_ft and shared with spell_range_ft) instead of the fixed _HEX_RANGE_FT 25 - a level-5 witch reaches 35 ft; + scaling regression test 2026-09-19 19:07:41 +02:00
ctan 6f01a8ebdd fix(combat): _find_ally_needing_heal counts only living allies (SRD Channel Energy: heal mode heals living creatures, positive energy never heals undead) - an undead ally below half triggered heal mode, spending the channel to heal nobody; + trigger regression test 2026-09-19 19:01:03 +02:00
ctan c23fc366e4 fix(combat): _find_ally_needing_heal uses hp*2 < hp_max (strictly below half) - the floored hp < hp_max//2 was off by one for odd hp_max (5 -> threshold 2, so 2/5 hp counted healthy); + threshold regression test 2026-09-19 18:58:03 +02:00
ctan f93c55b3fb fix(combat): _swift_bonus_actions emits up_close_and_deadly only when the turn will actually fire a firearm attack within 30 ft - the deed burns 1 grit at execution and its +1d6 rides only a firearm attack, so emitting it alongside a melee swing or a spell cast wasted grit for a lost bonus; + 2 policy tests, deed test now uses a firearm-only tammara 2026-09-19 18:55:17 +02:00
ctan e8b7a73d1f fix(dice): parse_dice validates sides >= _MIN_SIDES so d1/1d1 raise DiceParseError instead of leaking the model validator's pydantic ValidationError; drop the dead not-sides_str guard (regex \\d+ already guarantees non-empty) + 3 rejected-notation cases (d1, 1d1, 2D6) 2026-09-19 18:51:38 +02:00
ctan bdf15aa8fe fix(combat): _choose_spell_action heal branch gates on spell range + line of effect - a touch-range Cure on a distant ally was rejected by _cast_spell (OUT OF RANGE / NO LINE OF EFFECT) and wasted the caster's whole turn; extracted the offensive branch's reach check as _spell_reaches and reused it (the duplicated below-half check in the branch was a no-op copy of _find_ally_needing_heal, dropped) + 3 policy tests 2026-09-19 18:48:58 +02:00
ctan 31d47aee79 fix(combat): _spell_combat_actions gates on _has_slot - with exhausted slots the cast is rejected (NO SPELL SLOT) and spell_combat resolves as a bare full attack at -2, strictly worse than the plain full attack; the policy now falls through (mirrors _choose_spell_action's slot gate) + 2 regression tests 2026-09-19 18:41:46 +02:00
ctan 657f48474c docs(rules): conformance journal round 7 - spell registry closed (108/108) and subsystem fixes
Records the 12 fixes of the final pass (engine Lows, LOS area templates,
runner/metrics/observation/CLI): dying creatures occupy their square,
blinded cannot charge/run/withdraw, cone/line aim and caster footprint,
square_size_ft validation, channel-heal no longer counted as damage dealt,
summon aggregation no longer KeyErrors, non-attack damage counted, README
examples refreshed, ACTION_RESULT_REASONS completed, zone-letter validation.
The remaining 35 spells were field-verified against the KB with zero
outcome divergences, closing the spell coverage gap.
2026-09-19 17:21:54 +02:00
ctan 9a087a444b fix(docs): refresh stale README example numbers against the live engine - Quick start (3 goblins vs 2 orcs, seed 1): 96.2%/962 band [94.4%,98.0%], monsters 3.6%/36, 2 draws, 6.6 rounds; Exemple verifie (1 goblin vs 2 orcs): 33.0% band [28.5%,37.5%], 6 draws, 7.1 rounds, prose adjusted to the new distribution; dev test count 1596 -> 1634 2026-09-19 17:18:31 +02:00
ctan ba5eb4c95f fix(map): load_map rejects two deployment sides bound to the same zone letter (build_states resets its placed set per side, so shared letters deployed camps on top of each other) - fail-fast MapValidationError + README rule, reject/accept tests 2026-09-19 17:12:42 +02:00
ctan 2ee19d34e4 fix(docs): correct the Observation 'frozen' claim - the dataclass is frozen at the top level but the inner per-combatant/pair dicts are mutable copies; wording softened in the docstring and README (deep-freeze rejected: it would change the LLM contract and require touching pinned observation tests; copies are never read back by the engine, so policy mutation is already inert) 2026-09-19 17:09:08 +02:00
ctan 9459adee02 fix(actions): add downgraded:immediate_swift to ACTION_RESULT_REASONS (emitted since conformance L10 but absent from the published vocabulary); grep showed no other emitted-but-missing ActionResult reason (no_charge_path is a ChargeResult field) - drift guard test pins every _REASON_* constant against the frozenset 2026-09-19 17:06:30 +02:00
ctan 0ad627c97c fix(stats): non-attack damage now lands in damage_taken - bleed ticks, recurring spell damage (Acid Arrow) and cure-spells-damage-undead (SRD Cure Light Wounds negative-energy hit, attributed to the caster's damage_dealt) were invisible to attrition while attacks were counted; transcripts and RNG untouched 2026-09-19 17:02:21 +02:00
ctan 7fefce4653 fix(runner): aggregation creates per-id accumulators on demand so summoned creature ids in result.stats no longer KeyError run_encounter (totals were seeded from the deployed roster only); duplicate summons of one creature id share a stats slot - noted as a limitation, pinned by a summon-encounter report test 2026-09-19 16:56:48 +02:00
ctan 2e2873f331 fix(stats): channel-energy heal is no longer counted as damage_dealt (attrition tracks damage only, README 'dégâts infligés/subis'; consistent with spell HealEffect which is counted nowhere) - harm mode still records damage_dealt/damage_taken, pinned by new tests 2026-09-19 16:52:04 +02:00
ctan 24e208ea41 fix(rules): load_map rejects any square_size_ft other than 5 (SRD Measuring Distance: one grid square is 5 feet; the engine's 5-10-5 geometry, step costs and ranges are hard-coded to that scale) - a map declaring 10-ft squares was loaded silently and mis-scaled; now a fail-fast MapValidationError, with reject/accept tests 2026-09-19 16:37:30 +02:00
ctan 1e2de7f89d fix(rules): a cone/line template excludes the caster's whole footprint, not just the anchor cell (SRD aiming-a-spell.md: 'It starts from any corner of your square' - every occupied square is origin space and never inside its own area) - cone_cells/line_cells take caster_cells; combat passes caster.occupied_cells(); 1x1 casters bit-identical; documented simplification: a grazed caster cell still consumes a length slot 2026-09-19 16:34:22 +02:00
ctan 2e468ba619 fix(rules): point-cast cones/lines no longer demand line of effect to the aim point (SRD aiming-a-spell.md: a cone/line 'shoots away from you in the direction you designate' and 'starts from any corner of your square' - the LoE-to-the-space rule binds targets and burst origins only) - _point_target_rejection now requires in-bounds for every area and caster->point LoE only for bursts (and area-less spells, conservatively); template LoE stays enforced by cone_cells/line_cells 2026-09-19 16:29:38 +02:00
ctan 778d6a8620 fix(rules): a blinded creature cannot charge, run, or withdraw (SRD charge.md: 'If you don't have line of sight to the opponent at the start of your turn, you can't charge that opponent'; full-round-actions.md: 'You can't run ... if you can't see where you're going' and 'you can't withdraw from combat if you're blinded') - can_charge gates blinded with no_line_of_effect, _execute_run joins the cant_charge no-op, _withdraw rejects blinded 2026-09-19 16:24:31 +02:00
ctan 792cec140b fix(rules): dying-but-alive creatures still occupy their squares (SRD cover.md: soft cover from 'a square occupied by a creature'; SRD aiming-a-spell.md: a burst 'affects whatever it catches in its area, including creatures that you can't see') - soft-cover interposer sets and area templates filtered on active, so a dying creature's square was ignored; new CombatantState.occupies_space (not dead) gates both; corpses stay excluded (documented); channel energy keeps its active-only filter (pinned) 2026-09-19 16:19:23 +02:00
ctan aa424fa385 docs(rules): conformance journal rounds 5-6 (31 fix commits) and final residual inventory
Rounds 5-6 covered the spell subsystem (metamagic DC, holding the charge,
summon duration, Extend, Combat Casting/entangled, ASF/bonus-spells/dismissal
docs), the 24 feat/racial residuals, the deferred spell effects (mirror image,
bleed, bane, acid arrow, ice slick, owl's wisdom, bear's endurance, aid...)
and the map/LoS area-of-effect findings (line truncation at barriers, cone/
burst line of effect, cover-to-Reflex, adjacent melee cover, charge/run
corner rule, charge footprint terrain). Records the 31 commits
(d0db208..9086038), the deferred/lower-severity residuals, and the audit
coverage gap (oracle quota exhausted).
2026-09-19 15:58:53 +02:00
ctan 9086038bfb fix(rules): charge clear-path tests the whole footprint for difficult terrain (SRD movement-position-and-distance-2.md: 'You can't run or charge across difficult terrain' + 'you can move only as fast as the most difficult terrain you occupy') - _charge_path_clear priced only the anchor cell, so a 2x2 creature with one trailing square in rubble charged freely; the move-descent footprint pricing is deferred (would rewrite the anchor-based Dijkstra cost field) 2026-09-19 15:51:15 +02:00
ctan 5e478bfd27 fix(rules): charge and run walks enforce the diagonal corner rule (SRD measuring-distance.md: 'You can't move diagonally past a corner') - _charge_path_clear and _run_line_path never checked diagonal_allowed, so a charge/run could cut diagonally through a wall corner; both walks now stop/reject a diagonal step whose flanks are not both passable, same predicate the Dijkstra descent uses 2026-09-19 15:44:12 +02:00
ctan 27ce33ba67 fix(rules): adjacent melee cover uses the SRD any-line rule (cover.md: 'any line from any corner of your square to the target's square goes through a wall') - the gate previously applied the ranged best-corner rule to distance-1 melee, systematically under-granting cover; non-adjacent melee (reach) keeps the ranged rule, bonus scoring aligned per branch, README 'pire coin' wording updated 2026-09-19 15:35:56 +02:00
ctan 9f8655da76 fix(rules): README sync for the area-of-effect line-of-effect fixes - line truncation at barriers, cone/burst LoE from the origin, terrain cover Reflex half-bonus (SRD aiming-a-spell.md, cover.md); areas.py architecture entry updated 2026-09-19 14:35:17 +02:00
ctan fb6f58acc1 fix(rules): document the diagonal corner rule as stricter-than-RAW (SRD measuring-distance.md: 'You can't move diagonally past a corner' vs 'You can also move diagonally past other impassable obstacles, such as pits' - RAW blocks only both-flank corners; the engine requires both flanks passable for every diagonal, an accepted conservative approximation because relaxing it would rewrite Dijkstra paths and golden positions) 2026-09-19 14:32:44 +02:00
ctan beb8421600 fix(rules): pin the high-ground +1 as melee-only with the SRD proof (Table 8-5 'Attack Roll Modifiers': 'On higher ground - Melee +1, Ranged +0') - engine verified conformant, no code change 2026-09-19 14:30:46 +02:00
ctan a2ca78fce3 fix(rules): document the blocks_los flag as LINE OF EFFECT semantics (SRD aiming-a-spell.md: 'A line of effect is canceled by a solid barrier. It's like line of sight for ranged weapons, except that it's not blocked by fog, darkness, and other factors that limit normal sight') - vision-only concealment terrain is not modeled; concealment rides conditions (miss_chance), not the map 2026-09-19 14:26:26 +02:00
ctan 8774e51a12 fix(rules): cover grants a Reflex-save bonus against area effects (SRD cover.md 'Cover and Reflex Saves': +2 vs standard cover - half the AC value, so +1 partial/+2 standard/+4 improved; soft cover grants none) - _area_save adds terrain cover//2 from the effect's point of origin (burst center, caster square for cone/line) to Reflex rolls; resolve_save gains an explicit flat_bonus 2026-09-19 14:16:26 +02:00
ctan 948edc1815 fix(rules): bursts cannot affect creatures in total cover from the point of origin (SRD aiming-a-spell.md: a burst "can't affect creatures with total cover from its point of origin (in other words, its effects don't extend around corners)"; Line of Effect: a burst affects only what it has LoE to from its origin) - _area_creatures skips creatures whose occupied cells have no line of effect from the burst center 2026-09-19 14:08:24 +02:00
ctan 389e160627 fix(rules): cones require line of effect from their origin (SRD aiming-a-spell.md: a cone 'affects only an area, creature, or object to which it has line of effect from its origin') - cone_cells drops geometrically-inside cells without corner-to-corner LoE from the caster square; line/cone barrier-cell semantics aligned (both templates include the barrier square the effect strikes, exclude everything behind it) 2026-09-19 14:04:34 +02:00
ctan 5250763c35 fix(rules): line spells stop at a line-of-effect barrier (SRD aiming-a-spell.md: a line 'extends to the limit of its range or until it strikes a barrier that blocks line of effect') - line_cells truncates at the first blocks_los cell, creatures behind a solid wall are never caught 2026-09-19 13:54:25 +02:00
ctan b95533d49f fix(rules): bear's endurance +2 hp per Hit Die via split flat scaling, aid morale scoped to fear-descriptor saves (SRD spells/aid.md), charm person range close per SRD spells/charm-person.md, README synced (29 conditions, acid arrow total cap, mirror image instance-level miss chance) 2026-09-19 13:34:30 +02:00
ctan 35ce41e5f9 fix(rules): owl's wisdom +2 Will enhancement buff (SRD spells/owl-s-wisdom.md); correct and sharpen the deferred-utility docs for guidance, resistance, virtue, stabilize, saving finale, spiritual weapon (verified SRD texts, engine-track reasons, and the cantrip-buff policy degradation that blocks encoding resistance) 2026-09-19 13:27:55 +02:00
ctan 56310b191a fix(rules): mirror image rolled image count with dynamic images/(images+1) concealment miss chance, bleed cantrip gated on the SRD dying-target eligibility, bane -1 fear-scoped penalty via a dedicated bane condition (SRD spells/mirror-image.md, spells/bleed.md, spells/bane.md) 2026-09-19 13:22:19 +02:00
ctan d9b590ebce fix(rules): acid arrow recurring total cap (1+6=7 rounds at 18th, the SRD max counts additional rounds) and ice slick flat scaling 1d6+1/level max +10 (SRD spells/acid-arrow.md, spells/ice-slick.md) 2026-09-19 13:12:36 +02:00
ctan 13e4a2774e fix(rules): cover the feat/trait no-op batch in test_feats (assert the eight documented inert specs, the two traced-noop names, and misty's resistances/bite data encoding) 2026-09-19 12:47:40 +02:00
ctan 2df9dd778c fix(rules): misty Scaled Skin electricity resist 5 and Maw or Claw bite 1d6 (SRD ARG daemon-spawn traits: resistance encoded in resistances, bite upgraded from the sheet's estimated 1d4 to the Medium 1d6 with the discrepancy recorded in notes) 2026-09-19 12:43:38 +02:00
ctan 7262568959 fix(rules): Esha sylph traits and invented names to documented traced no-ops (Whispering Wind/Like the Wind/Breeze-Kissed are real sylph racial traits whose SRD effects the engine cannot resolve - the coded bonuses were fabricated; Fey-touched and Pingre are not PF1e features and move to _NOOP_FEATS; descriptor gating stays covered via local probe specs) 2026-09-19 12:37:32 +02:00
ctan 2dd3fefa1d fix(rules): Larger Than Life / Ambitious / Focused Study to documented traced no-ops (SRD effects gate on Intimidate size, HD-differential Diplomacy, and chosen-skill bonuses the engine never resolves; the flat +1 Intimidate was the wrong effect) 2026-09-19 12:27:07 +02:00
ctan 17c06e18cc fix(rules): document dismissal of (D)-duration spells as out of scope (SRD Cast a Spell: Dismiss a Spell - no dismiss action nor active-spell registry exists in the engine) 2026-09-19 12:06:24 +02:00
ctan 1902877cfe fix(rules): document the spell_slots data convention - JSON slots are final per-encounter budgets, engine computes no bonus spells (evidence: PC sheets carry the base class table without bonus, lich slots match no RAW daily table) 2026-09-19 12:04:30 +02:00
ctan b2739c5747 fix(rules): document arcane spell failure as out of scope (SRD Arcane Spells and Armor - the model has no armor field nor spell components, so ASF has neither source nor filter) 2026-09-19 12:01:40 +02:00
ctan 22724f583f fix(rules): Extend Spell doubles SummonEffect duration (SRD extend-spell: lasts twice as long; summon's 1 round/level is neither instantaneous, concentration nor permanent) 2026-09-19 11:55:30 +02:00
ctan 65a5e24609 fix(rules): Combat Casting's +4 no longer applies to the entangled concentration check (SRD combat-casting: the feat covers only casting defensively or while grappled) 2026-09-19 11:50:53 +02:00
ctan cec3c5f459 fix(rules): summon duration scales with caster level (SRD summon-monster: 1 round/level (D)) - SummonEffect gains duration_rounds_per_level resolved at cast time, summon_monster_i/ii JSON updated 2026-09-19 11:47:34 +02:00
ctan 418fdc826b fix(rules): a missed melee touch attack holds the charge instead of expending the spell (SRD Holding the Charge: re-attempted at the start of later turns, dissipated by casting another spell; ranged touch still expended on a miss) 2026-09-19 11:40:02 +02:00
ctan 81a09c88f0 fix(rules): metamagic no longer raises the save DC - only Heighten Spell does (SRD empower-spell: saving throws not affected; heighten-spell: DCs calculated at the heightened level, modeled at its minimum +1 slot adjustment) 2026-09-19 11:33:10 +02:00
ctan b74baf1aa9 fix(rules): Lonely and Bitter drawbacks replace bogus +1 Will bonuses (SRD: Lonely -2 Sense Motive and -2 saves vs charm with mind_affecting standing in; Bitter reduces ally-sourced healing by 1 via engine hook) 2026-09-19 10:50:50 +02:00
ctan a5ebbecad8 fix(rules): Friendly Face and Extremely Fashionable become documented no-ops (SRD: animal attitude +2 steps and +1 Bluff/Diplomacy/Intimidate in fine clothing - neither attitude nor clothing state is modeled; old +1/+4 Diplomacy bonuses were wrong) 2026-09-19 10:44:25 +02:00
ctan 6d0ee1bfeb fix(rules): Born Damned and Axe to Grind become documented no-ops (SRD: +2 saves vs curses has no curse descriptor; +1 damage vs foes threatened only by you is not expressible - old Fort-vs-poison and axe-damage bonuses were wrong) 2026-09-19 10:41:21 +02:00
ctan d0db208610 fix(rules): Exalted of the Society grants +1 channel energy/day via resource_grants; Fortified Drinker becomes documented no-op (SRD: +2 saves vs mind-affecting after drinking, alcohol state not modeled) 2026-09-19 10:37:23 +02:00
260 changed files with 25381 additions and 2667 deletions
+41 -1738
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -1,5 +1,6 @@
{
"name": "Celestial Wolf",
"alignment": "NG",
"level": 2,
"size": "Medium",
"abilities": {
@@ -24,6 +25,8 @@
"ref": 5,
"will": 1
},
"arcane_caster": false,
"armor_spell_failure": 0,
"attacks": [
{
"id": "celestial_wolf:bite",
@@ -56,7 +59,7 @@
"electricity": 5
},
"spell_resistance": 6,
"notes": "SRD Bestiary wolf (2d8+4 hp 13, AC 14/touch 12/flat 12, Fort +5, Will +1, bite +2 1d6+1 plus trip, Str 13 Dex 15 Con 15) + celestial template. Encoded from the template: DR 5/magic, energy resistance acid/cold/electricity 5, SR 6 (CR 1 + 5). Not encodable in this model: smite evil 2/day (+2 damage vs evil), the bite's free trip on hit (the engine has trip only as a standard-action maneuver, no auto-trip attack qualifier), and the weapon model has no magic property so no equipment bypasses the DR 5/magic (correct for mundane weapons). Summoning scaling (energy resistance +2 and SR +5 per summon spell level above 2nd) is not modeled; this sheet is the summon monster II baseline.",
"notes": "SRD Bestiary wolf (2d8+4 hp 13, AC 14/touch 12/flat 12, Fort +5, Will +1, bite +2 1d6+1 plus trip, Str 13 Dex 15 Con 15) + celestial template. Encoded from the template: DR 5/magic, energy resistance acid/cold/electricity 5, SR 6 (CR 1 + 5). Not encodable in this model: smite evil 2/day (+2 damage vs evil), the bite's free trip on hit (the engine has trip only as a standard-action maneuver, no auto-trip attack qualifier), and the weapon model has no magic property so no equipment bypasses the DR 5/magic (correct for mundane weapons). Summoning scaling (energy resistance +2 and SR +5 per summon spell level above 2nd) is not modeled; this sheet is the summon monster II baseline. Alignment inferred: SRD celestial template = any good; NG chosen for the good axis with neutral law axis",
"source": "summon monster II",
"features": [],
"spells": [],
+3
View File
@@ -1,5 +1,6 @@
{
"name": "Giant Belostomatid",
"alignment": "N",
"level": 3,
"size": "Large",
"cr": "3",
@@ -28,6 +29,8 @@
"ref": 2,
"will": 1
},
"arcane_caster": false,
"armor_spell_failure": 0,
"attacks": [
{
"name": "bite",
+3
View File
@@ -1,5 +1,6 @@
{
"name": "Goblin",
"alignment": "NE",
"level": 1,
"size": "Small",
"cr": "1/3",
@@ -19,6 +20,8 @@
"initiative_mod": 6,
"speed_land_ft": 30,
"saves": { "fort": 3, "ref": 2, "will": -1 },
"arcane_caster": false,
"armor_spell_failure": 0,
"attacks": [
{
"name": "short sword",
+8 -4
View File
@@ -1,24 +1,25 @@
{
"name": "Lich",
"alignment": "NE",
"level": 11,
"size": "Medium",
"creature_type": "undead",
"abilities": {
"str_score": 12,
"str_score": 10,
"dex_score": 14,
"con_score": 10,
"int_score": 20,
"int_score": 22,
"wis_score": 14,
"cha_score": 16
},
"hp_max": 100,
"hp_max": 111,
"ac": {
"total": 23,
"touch": 14,
"flat_footed": 21
},
"bab": 5,
"initiative_mod": 6,
"initiative_mod": 2,
"speed_land_ft": 30,
"saves": {
"fort": 6,
@@ -26,6 +27,8 @@
"will": 11
},
"casting_ability": "int",
"arcane_caster": true,
"armor_spell_failure": 0,
"spell_slots": {
"1": 4,
"2": 4,
@@ -59,6 +62,7 @@
"bypass": ["bludgeoning", "magic"]
},
"source": "bestiary",
"notes": "Stat block aligned to the KB lich entry (bestiary/lich.md): hp 111 (11d6+55 plus 15 false life), Str 10, Int 22, Init +2, fear aura 60 ft / DC 18. The spell list is the sim's own encounter-authored list (the KB's prepared list includes spells outside the data/spells registry); only the stat block was aligned.",
"features": [
"Fear Aura",
"Paralyzing Touch",
+3
View File
@@ -1,5 +1,6 @@
{
"name": "Orc",
"alignment": "CE",
"level": 1,
"size": "Medium",
"cr": "1/3",
@@ -20,6 +21,8 @@
"initiative_mod": 0,
"speed_land_ft": 30,
"saves": { "fort": 3, "ref": 0, "will": -1 },
"arcane_caster": false,
"armor_spell_failure": 0,
"attacks": [
{
"name": "falchion",
+3
View File
@@ -1,5 +1,6 @@
{
"name": "Thrune Agent",
"alignment": "LE",
"level": 8,
"size": "Medium",
"creature_type": "living",
@@ -30,6 +31,8 @@
"will": 6
},
"casting_ability": "int",
"arcane_caster": true,
"armor_spell_failure": 0,
"spell_slots": {
"1": 5,
"2": 5,
+19
View File
@@ -1,5 +1,6 @@
{
"name": "Esha Randu",
"alignment": "LG",
"level": 4,
"size": "Medium",
"abilities": {
@@ -25,6 +26,8 @@
"will": 5
},
"casting_ability": "int",
"arcane_caster": false,
"armor_spell_failure": 0,
"spell_slots": {},
"resources": {},
"attacks": [
@@ -101,5 +104,21 @@
],
"spells": [
"Unseen Servant"
],
"carried_items": [
"Arrow",
"Blunt Arrow",
"Dye Arrow",
"Smoke Arrow",
"Tangleshot Arrow",
"Whistling Arrow",
"Thieves' Tools, common",
"Clefs de la loge",
"Badge d'éclaireur",
"Rope",
"Pomme",
"La pomme de Notch",
"Pierre de communication"
]
}
+6 -2
View File
@@ -1,5 +1,6 @@
{
"name": "Esha's Mount",
"alignment": "N",
"level": 4,
"size": "Large",
"abilities": {
@@ -44,7 +45,7 @@
"natural": true
}
],
"notes": "Light-horse mount shared with Esha's sheet: hooves are 1d4 bludgeoning (SRD Bestiary horse, light - 2 hooves 1d4), natural attacks. No bite encoded: the SRD light horse has no bite attack and the Foundry sheet lists no mount bite (unlike Jeanne's sheet, which does).",
"notes": "Light-horse mount shared with Esha's sheet: hooves are 1d4 bludgeoning (SRD Bestiary horse, light - 2 hooves 1d4), natural attacks. No bite encoded: the SRD light horse has no bite attack and the Foundry sheet lists no mount bite (unlike Jeanne's sheet, which does). Alignment inferred: SRD animal (horse) is typically neutral; no alignment on the sheet",
"source": "fiches_personnages/esha_sheet.json (mount)",
"features": [
"Combat Reflexes",
@@ -56,7 +57,10 @@
],
"spells": [],
"casting_ability": "wis",
"arcane_caster": false,
"armor_spell_failure": 0,
"resources": {},
"metamagic_feats": [],
"skills": {}
"skills": {},
"carried_items": []
}
+6 -1
View File
@@ -1,5 +1,6 @@
{
"name": "Harvie",
"alignment": "N",
"level": 2,
"size": "Small",
"abilities": {
@@ -69,6 +70,7 @@
}
],
"source": "fiches_personnages/harvie_sheet.json",
"notes": "Feat : 'Friendly Face' est un no-op documenté (attitude initiale des animaux +2 pas, système d'attitude non modélisé).",
"features": [
"Friendly Face",
"Fey-touched",
@@ -76,8 +78,11 @@
],
"spells": [],
"casting_ability": "wis",
"arcane_caster": false,
"armor_spell_failure": 0,
"spell_slots": {},
"resources": {},
"metamagic_feats": [],
"skills": {}
"skills": {},
"carried_items": []
}
+5 -1
View File
@@ -1,5 +1,6 @@
{
"name": "Ierlieth",
"alignment": "CN",
"level": 4,
"size": "Tiny",
"abilities": {
@@ -76,8 +77,11 @@
],
"spells": [],
"casting_ability": "wis",
"arcane_caster": false,
"armor_spell_failure": 0,
"spell_slots": {},
"resources": {},
"metamagic_feats": [],
"skills": {}
"skills": {},
"carried_items": []
}
+5 -1
View File
@@ -1,5 +1,6 @@
{
"name": "Jeanne",
"alignment": "N",
"level": 4,
"size": "Large",
"abilities": {
@@ -74,8 +75,11 @@
],
"spells": [],
"casting_ability": "wis",
"arcane_caster": false,
"armor_spell_failure": 0,
"spell_slots": {},
"resources": {},
"metamagic_feats": [],
"skills": {}
"skills": {},
"carried_items": ["Saddle (riding)", "Feed", "Trail Rations"]
}
+23 -4
View File
@@ -1,5 +1,6 @@
{
"name": "Misty",
"alignment": "NE",
"level": 4,
"size": "Medium",
"abilities": {
@@ -25,6 +26,8 @@
"will": 3
},
"casting_ability": "cha",
"arcane_caster": true,
"armor_spell_failure": 0,
"spell_slots": {
"1": 4,
"2": 3
@@ -95,7 +98,7 @@
"attack_bonus": 4,
"damage": [
{
"formula": "1d4",
"formula": "1d6",
"types": [
"piercing"
]
@@ -128,6 +131,7 @@
}
],
"source": "fiches_personnages/misty_sheet.json",
"notes": "Traits : 'Born Damned' est un no-op documenté (+2 sauvegardes vs malédictions, descripteur 'curse' absent du moteur) ; 'Axe to Grind' est un no-op documenté (+1 dégâts vs ennemi menacé seulement par soi, condition non exprimable) ; 'Scaled Skin (electricity)' est encodé dans resistances (électricité 5, SRD : remplace Fiendish Resistance, donc pas de résistance froid/feu). 'Maw or Claw' : la fiche Foundry note la morsure « 1d4 piercing estimé » (item sans stats, estimation de l'auteur, non délibérée) ; le SRD (ARG, trait tieffelin daemon-spawn) accorde une morsure 1d6 à une créature Medium - l'encodage suit le SRD (1d6) et consigne l'écart avec la fiche.",
"features": [
"Harmonic Spell",
"Weapon Finesse",
@@ -137,8 +141,8 @@
"Gifted Adept (dispel magic; magic)",
"Prehensile Tail (tiefling daemon-spawn)",
"Scaled Skin (electricity) (tiefling daemon-spawn)",
"Maw or Claw (Bite) (tiefling daemon-spawn)",
"Plot Armor"
"Maw or Claw (Bite) (tiefling daemon-spawn)",
"Plot Armor"
],
"spells": [
"Prestidigitation",
@@ -165,5 +169,20 @@
"Bladed Dash",
"Mirror Image",
"Heroism"
]
],
"resistances": {
"electricity": 5
},
"carried_items": [
"Pouch, Spell Component",
"Tools, Thieves'",
"Thieves' Tools, masterwork",
"Clef de la Loge de Sauerton",
"+1 Mithral Arrow",
"Bouteille de ??? à moitié vide",
"Scroll of Detect Secret Doors",
"Scroll of Cure Light Wounds",
"Wand of Infernal Healing"
]
}
+13
View File
@@ -1,5 +1,6 @@
{
"name": "Nairda Guisenda",
"alignment": "CN",
"level": 4,
"size": "Medium",
"abilities": {
@@ -25,6 +26,8 @@
"will": 7
},
"casting_ability": "int",
"arcane_caster": true,
"armor_spell_failure": 0,
"spell_slots": {
"1": 4,
"2": 3
@@ -114,5 +117,15 @@
"Ice Slick",
"Darkness",
"Flurry of Snowballs"
],
"carried_items": [
"Spell Component Pouch",
"Holy symbol (Wood)",
"Arrow",
"Lettres dans une chambre de bandits",
"Clefs de la loge",
"Badge des éclaireurs",
"Potion de soin de Nairda"
]
}
+19 -3
View File
@@ -1,5 +1,6 @@
{
"name": "Oni Triumvir",
"alignment": "NG",
"level": 4,
"size": "Medium",
"abilities": {
@@ -25,6 +26,8 @@
"will": 9
},
"casting_ability": "wis",
"arcane_caster": false,
"armor_spell_failure": 0,
"spell_slots": {
"1": 4,
"2": 3
@@ -69,7 +72,11 @@
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"range_increment_ft": 10
"range_increment_ft": 10,
"splash": {
"damage": 1,
"type": "fire"
}
},
{
"name": "Attaques naturelles (Shifter Claws / formes fées)",
@@ -92,7 +99,7 @@
}
],
"source": "fiches_personnages/oni_sheet.json",
"notes": "Armes placeholder Foundry sans dégâts encodés omises : 'une Pioche' (non maîtrisée) et 'Weapon' (à remplacer par la vraie arme).",
"notes": "Armes placeholder Foundry sans dégâts encodés omises : 'une Pioche' (non maîtrisée) et 'Weapon' (à remplacer par la vraie arme). Traits : 'Exalted of the Society' accorde +1 canalisation d'énergie/jour (channel_energy 6 -> 7, fusionné par le chargeur) ; 'Extremely Fashionable' est un no-op documenté (+1 Bluff/Diplomacy/Intimidate en habits fins, état vestimentaire non modélisé) ; 'Bitter' (drawback) réduit de 1 les soins reçus d'un allié (comportement moteur).",
"features": [
"Summon Good Monster",
"Sacred Summons",
@@ -123,5 +130,14 @@
"Delay Poison",
"Ironskin",
"Restoration, Lesser"
]
],
"carried_items": [
"Arrow",
"Holy symbol (Wood)",
"Clefs de la loge",
"Eh je suis Éclaireuse maintenant",
"Kit, Fishing",
"Alchemist's Fire"
]
}
+32 -1
View File
@@ -1,5 +1,6 @@
{
"name": "Tammara Cailean",
"alignment": "CG",
"level": 4,
"size": "Medium",
"abilities": {
@@ -25,6 +26,8 @@
"will": 8
},
"casting_ability": "wis",
"arcane_caster": false,
"armor_spell_failure": 0,
"spell_slots": {
"1": 4,
"2": 3
@@ -108,7 +111,11 @@
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"range_increment_ft": 10
"range_increment_ft": 10,
"splash": {
"damage": 1,
"type": "fire"
}
},
{
"name": "Unarmed Strike",
@@ -130,6 +137,7 @@
}
],
"source": "fiches_personnages/tammara_sheet.json",
"notes": "Traits : 'Fortified Drinker' est un no-op documenté (+2 sauvegardes vs effets mentaux après alcool, état d'ébriété non modélisé) ; 'Lonely' (drawback) applique -2 Sense Motive et -2 sauvegardes vs charme (mind_affecting remplace charme).",
"features": [
"Weapon Focus (Pistol)",
"Rapid Reload (Pistol)",
@@ -202,5 +210,28 @@
"Delay Poison",
"Aid",
"Drunkard's Breath"
],
"carried_items": [
"Mess Kit",
"Gunsmith's Kit",
"Holy symbol (Wood)",
"Spell component pouch",
"Powder horn",
"Firearm Bullet",
"Alchemical Cartridge (Paper, Bullet Or Pellet)",
"Black Powder (Dose)",
"Matériaux pour munitions",
"Rope",
"Bouteille d'alcool (poste des bandits)",
"Clefs de la loge (4)",
"Black Powder (Dose) en stock",
"Firearm Bullet en stock",
"Alchemical Cartridge (Paper, Bullet Or Pellet) en stock",
"Alcool de poison d'araignée ogre",
"Badge des éclaireurs",
"Bouteille d'alcool fort (genre plus que du désinfectant)",
"Une médaille de Caiden Cailean",
"Potion of Cure Light Wounds"
]
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Abundant Ammunition",
"level": 1,
"school": "conjuration",
"components": "V, S, M/DF",
"range": "close",
"save": "none",
"base_dc": 10,
+3 -2
View File
@@ -2,6 +2,7 @@
"name": "Acid Arrow",
"level": 2,
"school": "evocation",
"components": "V, S, M, F",
"range": "long",
"save": "none",
"base_dc": 10,
@@ -19,9 +20,9 @@
"types": ["acid"],
"base_rounds": 1,
"per_levels": 3,
"max": 6
"max": 7
}
}
],
"description": "SRD spells/acid-arrow.md: ranged touch attack dealing 2d4 acid damage, plus another 2d4 acid damage at the start of each of the target's turns for 1 round + 1 round per three caster levels (maximum 6 additional rounds). Recurring damage is reduced by energy resistance/immunity, not by DR. No save."
"description": "SRD spells/acid-arrow.md: ranged touch attack dealing 2d4 acid damage, plus another 2d4 acid damage at the start of each of the target's turns for 1 round + 1 round per three caster levels (maximum 6 ADDITIONAL rounds at 18th level, i.e. 7 recurring rounds total). Recurring damage is reduced by energy resistance/immunity, not by DR. No save."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Acid Splash",
"level": 0,
"school": "conjuration",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+5 -2
View File
@@ -2,6 +2,7 @@
"name": "Aid",
"level": 2,
"school": "enchantment",
"components": "V, S, DF",
"range": "touch",
"save": "none",
"base_dc": 10,
@@ -11,10 +12,12 @@
"type": "buff",
"modifiers": [
{ "target": "attack", "value": 1, "bonus_type": "morale" },
{ "target": "will", "value": 1, "bonus_type": "morale" }
{ "target": "fort", "value": 1, "bonus_type": "morale", "condition": "fear" },
{ "target": "ref", "value": 1, "bonus_type": "morale", "condition": "fear" },
{ "target": "will", "value": 1, "bonus_type": "morale", "condition": "fear" }
],
"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."
"description": "SRD spells/aid.md: the subject gains a +1 morale bonus on attack rolls and on saves against fear effects, plus temporary hit points equal to 1d8 + caster level (maximum 1d8+10). The save bonus is scoped to fear effects via the descriptor-conditioned modifiers (all three saves, replacing the previous unconditional Will-only approximation). The temporary hit points are deferred: CombatantState has no temp-HP track (same gap as the virtue cantrip). Duration 1 min/level approximated as 10 rounds."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Air Bubble",
"level": 1,
"school": "conjuration",
"components": "S, M/DF",
"range": "touch",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Alarm",
"level": 1,
"school": "abjuration",
"components": "V, S, F/DF",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Arcane Mark",
"level": 0,
"school": "universal",
"components": "V, S",
"range": "touch",
"save": "none",
"base_dc": 10,
+4 -2
View File
@@ -2,6 +2,7 @@
"name": "Bane",
"level": 1,
"school": "enchantment",
"components": "V, S, DF",
"range": "medium",
"save": "will",
"base_dc": 10,
@@ -11,16 +12,17 @@
"type": "area",
"radius_ft": 50,
"targets": "enemies",
"centered_on_caster": true,
"save_for_half": false,
"effects": [
{
"type": "condition",
"condition_name": "shaken",
"condition_name": "bane",
"duration_rounds": 10,
"save_negates": true
}
]
}
],
"description": "SRD spells/bane.md: enemies within a 50-ft burst centered on the caster take a -1 penalty on attack rolls and a -1 penalty on saving throws against fear effects; Will negates. The -1 penalty is approximated with the shaken condition (-2 on attacks and saves), matching the existing Doom approximation. Duration 1 min/level approximated as 10 rounds."
"description": "SRD spells/bane.md: enemies within a 50-ft burst centered on the caster take a -1 penalty on attack rolls and a -1 penalty on saving throws against fear effects; Will negates. Modeled with the dedicated bane condition (untyped -1 on attacks, -1 on saves conditioned on the fear descriptor) - the previous shaken (-2) approximation overcounted the penalty. Duration 1 min/level approximated as 10 rounds."
}
+12 -3
View File
@@ -2,19 +2,28 @@
"name": "Bear's Endurance",
"level": 2,
"school": "transmutation",
"components": "V, S, M/DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "fort", "value": 2, "bonus_type": "enhancement" },
{ "target": "hp", "value": 2, "bonus_type": "enhancement" }
{ "target": "fort", "value": 2, "bonus_type": "enhancement" }
],
"duration_rounds": 60
},
{
"type": "buff",
"modifiers": [
{ "target": "hp", "value": 0, "bonus_type": "enhancement" }
],
"duration_rounds": 60,
"scaling_flat": { "amount": 2, "per_levels": 1, "max": 40 }
}
],
"description": "The subject gains a +4 enhancement bonus to Constitution, granting +2 to Fortitude saves and +2 hit points per Hit Die (SRD bear's endurance; the hp modifier is informational — the engine has no per-HD hp pool). Will negates (harmless). Duration 10 min/level approximated as 60 rounds."
"description": "SRD spells/bear-s-endurance.md: the subject gains a +4 enhancement bonus to Constitution - +2 to Fortitude saves and +2 hit points per Hit Die - for 10 min./level. The engine has no ability-score track, so the +4 Con is encoded as its combat consequences: an unscaled +2 Fortitude enhancement, and a separate hp modifier carrying the per-HD bonus via flat scaling (+2 per level, capped at +40 for level 20; caster level stands in for the target's Hit Dice, and the hp target is informational - the engine has no per-HD hp pool). Will negates (harmless). Duration 10 min/level approximated as 60 rounds."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Bladed Dash",
"level": 2,
"school": "transmutation",
"components": "V",
"range": "personal",
"save": "none",
"base_dc": 10,
+4 -2
View File
@@ -2,6 +2,7 @@
"name": "Bleed",
"level": 0,
"school": "necromancy",
"components": "V, S",
"range": "close",
"save": "will",
"base_dc": 10,
@@ -11,8 +12,9 @@
"type": "condition",
"condition_name": "bleeding",
"duration_rounds": 1,
"save_negates": true
"save_negates": true,
"requires_dying": true
}
],
"description": "SRD spells/bleed.md: a living creature at -1 or fewer hit points begins dying, taking 1 point of damage per round; Will negates. Modeled as the bleeding condition for 1 round with bleed_damage = 1 (the engine's bleed tick deals it at the start of the target's turn)."
"description": "SRD spells/bleed.md: you target one living creature that has -1 or fewer hit points; it begins dying, taking 1 point of damage per round; Will negates. Gated on the target's hit points (requires_dying): a creature at 0 hp or above is not a valid target and the spell has no effect. Modeled as the bleeding condition for 1 round with bleed_damage = 1 (the engine's bleed tick deals it at the start of the target's turn)."
}
+2
View File
@@ -2,6 +2,7 @@
"name": "Bless",
"level": 1,
"school": "enchantment",
"components": "V, S, DF",
"range": "medium",
"save": "none",
"base_dc": 10,
@@ -11,6 +12,7 @@
"type": "area",
"radius_ft": 50,
"targets": "allies",
"centered_on_caster": true,
"save_for_half": false,
"effects": [
{
+1
View File
@@ -2,6 +2,7 @@
"name": "Blindness/Deafness",
"level": 2,
"school": "necromancy",
"components": "V",
"range": "medium",
"save": "fort",
"base_dc": 10,
+2
View File
@@ -2,8 +2,10 @@
"name": "Bull's Strength",
"level": 2,
"school": "transmutation",
"components": "V, S, M/DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [
+1
View File
@@ -2,6 +2,7 @@
"name": "Burning Hands",
"level": 1,
"school": "evocation",
"components": "V, S",
"range": "close",
"save": "ref",
"base_dc": 10,
+3 -2
View File
@@ -2,7 +2,8 @@
"name": "Charm Person",
"level": 1,
"school": "enchantment",
"range": "medium",
"components": "V, S",
"range": "close",
"save": "will",
"base_dc": 10,
"cast_time": "standard",
@@ -14,5 +15,5 @@
"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."
"description": "SRD spells/charm-person.md: range close (25 ft. + 5 ft./2 levels) - corrected from the previous medium approximation. The charmed person regards the caster as a trusted friend; the engine has no charmed condition, so it is modeled as fascinated (the subject takes no actions). Duration 1 hour/level approximated as 100 rounds. Will negates."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Color Spray",
"level": 1,
"school": "illusion",
"components": "V, S, M",
"range": "close",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Comprehend Languages",
"level": 1,
"school": "divination",
"components": "V, S, M/DF",
"range": "personal",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Create Water",
"level": 0,
"school": "conjuration",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+2
View File
@@ -2,8 +2,10 @@
"name": "Cure Light Wounds",
"level": 1,
"school": "conjuration",
"components": "V, S",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [
+2
View File
@@ -2,8 +2,10 @@
"name": "Cure Moderate Wounds",
"level": 2,
"school": "conjuration",
"components": "V, S",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [
+1
View File
@@ -2,6 +2,7 @@
"name": "Dancing Lights",
"level": 0,
"school": "evocation",
"components": "V, S",
"range": "medium",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Darkness",
"level": 2,
"school": "evocation",
"components": "V, M/DF",
"range": "touch",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Daze",
"level": 0,
"school": "enchantment",
"components": "V, S, M",
"range": "close",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Deja Vu",
"level": 1,
"school": "enchantment",
"components": "V",
"range": "medium",
"save": "none",
"base_dc": 10,
+2
View File
@@ -2,8 +2,10 @@
"name": "Delay Disease",
"level": 2,
"school": "conjuration",
"components": "V, S, DF",
"range": "touch",
"save": "fort",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [],
+2
View File
@@ -2,8 +2,10 @@
"name": "Delay Poison",
"level": 2,
"school": "conjuration",
"components": "V, S, DF",
"range": "touch",
"save": "fort",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [],
@@ -2,6 +2,7 @@
"name": "Detect Fiendish Presence",
"level": 0,
"school": "divination",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Detect Magic",
"level": 0,
"school": "divination",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Detect Poison",
"level": 0,
"school": "divination",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Disrupt Undead",
"level": 0,
"school": "necromancy",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Divine Favor",
"level": 1,
"school": "evocation",
"components": "V, S, DF",
"range": "personal",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Doom",
"level": 1,
"school": "necromancy",
"components": "V, S, DF",
"range": "medium",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Drunkard's Breath",
"level": 2,
"school": "conjuration",
"components": "V, S, M",
"range": "close",
"save": "fort",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Endure Elements",
"level": 1,
"school": "abjuration",
"components": "V, S",
"range": "touch",
"save": "none",
"base_dc": 10,
+2
View File
@@ -2,8 +2,10 @@
"name": "Enhance Water",
"level": 1,
"school": "transmutation",
"components": "V, S",
"range": "touch",
"save": "fort",
"harmless": true,
"base_dc": 10,
"cast_time": "one_round",
"effects": [],
+2
View File
@@ -2,8 +2,10 @@
"name": "Enhanced Diplomacy",
"level": 0,
"school": "divination",
"components": "V, S",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [],
+2
View File
@@ -2,8 +2,10 @@
"name": "Face of the Devourer",
"level": 1,
"school": "transmutation",
"components": "V, S",
"range": "touch",
"save": "fort",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [],
+1
View File
@@ -2,6 +2,7 @@
"name": "Fear",
"level": 4,
"school": "necromancy",
"components": "V, S, M",
"range": "close",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Fireball",
"level": 3,
"school": "evocation",
"components": "V, S, M",
"range": "long",
"save": "ref",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Flurry of Snowballs",
"level": 2,
"school": "evocation",
"components": "V, S",
"range": "close",
"save": "ref",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Forbid Action",
"level": 1,
"school": "enchantment",
"components": "V",
"range": "close",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Grasp",
"level": 0,
"school": "divination",
"components": "V",
"range": "personal",
"save": "none",
"base_dc": 10,
+12 -2
View File
@@ -2,10 +2,20 @@
"name": "Guidance",
"level": 0,
"school": "divination",
"components": "V, S",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"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."
"effects": [
{
"type": "one_shot",
"track": "attack",
"value": 1,
"bonus_type": "competence",
"duration_rounds": 10
}
],
"description": "SRD spells/guidance.md: a cantrip - the subject gets a +1 competence bonus on a SINGLE attack roll, saving throw, or skill check, declared before the roll (duration 1 minute or until discharged). Modeled as a one-shot +1 competence modifier on the ATTACK track: the subject's next weapon attack roll (resolve_attack) discharges it and the bonus rides that roll only, not the crit-confirm roll; spell touch-delivery attacks roll on a separate path and do not consume it. The SRD 'attack OR save OR skill' choice has no declaration channel in the engine, so the attack-roll variant is encoded (the mechanism also carries save and skill tracks). 1 minute = 10 rounds, expiring at the start of the bearer's turns like any duration-tracked effect. The bonus folds into the resolve_modifiers pool, so it stacks with other types but not with a higher competence bonus on the same roll. default_policy never casts it (cantrips have no spell slot, and the one-shot type is not a buff/heal/offensive pick)."
}
+2
View File
@@ -2,8 +2,10 @@
"name": "Heroism",
"level": 2,
"school": "enchantment",
"components": "V, S",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [
+1
View File
@@ -2,6 +2,7 @@
"name": "Hex Vulnerability",
"level": 1,
"school": "necromancy",
"components": "V, S, M",
"range": "close",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Hidden Diplomacy",
"level": 1,
"school": "enchantment",
"components": "V, S, M",
"range": "close",
"save": "will",
"base_dc": 10,
+2
View File
@@ -2,8 +2,10 @@
"name": "Hide from Undead",
"level": 1,
"school": "abjuration",
"components": "V, S, DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"effects": [],
+1
View File
@@ -2,6 +2,7 @@
"name": "Hold Person",
"level": 3,
"school": "enchantment",
"components": "V, S, F/DF",
"range": "medium",
"save": "will",
"base_dc": 10,
+4 -2
View File
@@ -2,6 +2,7 @@
"name": "Ice Slick",
"level": 2,
"school": "evocation",
"components": "V, S",
"range": "close",
"save": "ref",
"base_dc": 10,
@@ -16,7 +17,8 @@
"type": "damage",
"formula": "1d6",
"types": ["cold"],
"half_on_save": true
"half_on_save": true,
"scaling_flat": { "amount": 1, "per_levels": 1, "max": 10 }
},
{
"type": "condition",
@@ -27,5 +29,5 @@
]
}
],
"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."
"description": "SRD spells/ice-slick.md: a blast of intense cold coats a 5-ft-radius burst in ice: creatures in the area take 1d6 points of cold damage + 1 point per caster level (maximum +10) and fall prone; a successful Reflex save halves the damage and avoids falling prone. The lingering slippery surface (half speed, Acrobatics checks) is not modeled."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Ill Omen",
"level": 1,
"school": "enchantment",
"components": "V, S, M",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Inflict Light Wounds",
"level": 1,
"school": "necromancy",
"components": "V, S",
"range": "touch",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Ironskin",
"level": 2,
"school": "transmutation",
"components": "V, S, DF/M",
"range": "personal",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Light",
"level": 0,
"school": "evocation",
"components": "V, M/DF",
"range": "touch",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Lightning Bolt",
"level": 3,
"school": "evocation",
"components": "V, S, M",
"range": "medium",
"save": "ref",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Mage Armor",
"level": 1,
"school": "conjuration",
"components": "V, S, F",
"range": "touch",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Mage Hand",
"level": 0,
"school": "transmutation",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Magic Missile",
"level": 1,
"school": "evocation",
"components": "V, S",
"range": "medium",
"save": "none",
"base_dc": 10,
+2
View File
@@ -2,8 +2,10 @@
"name": "Make Whole",
"level": 2,
"school": "transmutation",
"components": "V, S",
"range": "close",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "long",
"effects": [],
@@ -2,6 +2,7 @@
"name": "Masterwork Transformation",
"level": 2,
"school": "transmutation",
"components": "V, S, M",
"range": "touch",
"save": "none",
"base_dc": 10,
+2
View File
@@ -2,8 +2,10 @@
"name": "Mending",
"level": 0,
"school": "transmutation",
"components": "V, S",
"range": "close",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "long",
"effects": [],
+1
View File
@@ -2,6 +2,7 @@
"name": "Message",
"level": 0,
"school": "transmutation",
"components": "V, S, F",
"range": "medium",
"save": "none",
"base_dc": 10,
+9 -2
View File
@@ -2,6 +2,7 @@
"name": "Mirror Image",
"level": 2,
"school": "illusion",
"components": "V, S",
"range": "personal",
"save": "none",
"base_dc": 10,
@@ -11,8 +12,14 @@
"type": "condition",
"condition_name": "blurred",
"duration_rounds": 10,
"save_negates": false
"duration_rounds_per_level": 10,
"save_negates": false,
"images": {
"dice": "1d4",
"per_levels": 3,
"max": 8
}
}
],
"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."
"description": "SRD spells/mirror-image.md: 1d4 illusory doubles plus one per three caster levels (maximum eight total) inhabit your space. The image count is rolled at cast time and folded into the engine's concealment machinery as a dynamic miss chance of images/(images+1) on the blurred carrier condition (an attack that would hit strikes a figment instead). Duration 1 min./level scaled as 10 rounds per caster level. Simplification: the figment-destruction track (a hit or a near miss by 5 or less destroys one image; area and no-attack-roll effects ignore them) is not modeled - the miss chance persists for the whole duration."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Oath of Anonymity",
"level": 0,
"school": "illusion",
"components": "V",
"range": "personal",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Obscuring Mist",
"level": 1,
"school": "conjuration",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Open/Close",
"level": 0,
"school": "transmutation",
"components": "V, S, F",
"range": "close",
"save": "will",
"base_dc": 10,
+12 -2
View File
@@ -2,10 +2,20 @@
"name": "Owl's Wisdom",
"level": 2,
"school": "transmutation",
"components": "V, S, M/DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"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."
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "will", "value": 2, "bonus_type": "enhancement" }
],
"duration_rounds": 10
}
],
"description": "SRD spells/owl-s-wisdom.md: the subject gains a +4 enhancement bonus to Wisdom for 1 min./level. The engine has no ability-score track, so the +4 Wis is encoded as its combat-visible consequence: a +2 enhancement bonus on Will saves (half of +4, as a score modifier feeds saves at half). Wisdom-based skills and spell DCs are not modeled. Will negates (harmless). Duration 1 min/level approximated as 10 rounds."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Prestidigitation",
"level": 0,
"school": "universal",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+15 -2
View File
@@ -2,11 +2,24 @@
"name": "Protection from Evil",
"level": 1,
"school": "abjuration",
"components": "V, S, M/DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"spell_resistance": false,
"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."
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "ac", "value": 2, "bonus_type": "deflection", "condition": "attacker_is_evil" },
{ "target": "fort", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_evil" },
{ "target": "ref", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_evil" },
{ "target": "will", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_evil" }
],
"duration_rounds": 10
}
],
"description": "SRD spells/protection-from-evil.md: a barrier wards the subject from evil for 1 min./level. The subject gains a +2 deflection bonus to AC and a +2 resistance bonus on saves - the exact SRD wording: \"+2 deflection bonus to AC and a +2 resistance bonus on saves against attacks or effects created by evil creatures\" - so both bonuses are gated on the attacker/caster alignment (condition attacker_is_evil) and never apply against a creature that is not evil. Will negates (harmless), spell resistance no. Duration 1 min/level approximated as 10 rounds (engine convention for 1-minute durations). Not modeled: the SRD's second effect (immediate renewed saving throw with a +2 morale bonus against mental-control spells and effects, suppression for the duration, immunity to new attempts) - the engine has no mental-control tracking to suppress; and the third effect (evil summoned creatures cannot touch the warded creature) - the engine has no summoned-subtype contact restriction."
}
+15 -2
View File
@@ -2,11 +2,24 @@
"name": "Protection from Evil, Communal",
"level": 2,
"school": "abjuration",
"components": "V, S, M/DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"spell_resistance": false,
"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."
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "ac", "value": 2, "bonus_type": "deflection", "condition": "attacker_is_evil" },
{ "target": "fort", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_evil" },
{ "target": "ref", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_evil" },
{ "target": "will", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_evil" }
],
"duration_rounds": 10
}
],
"description": "SRD spells/protection-from-evil-communal.md: functions like protection from evil - each touched creature gains the +2 deflection bonus to AC and +2 resistance bonus on saves against attacks or effects created by evil creatures (gated on the attacker/caster alignment via condition attacker_is_evil, never applying against a non-evil source). Will negates (harmless), spell resistance no. Not modeled: the SRD duration split - \"you divide the duration in 1-minute intervals among the creatures touched\" - the engine gives every touched creature the same fixed duration as protection from evil (1 min/level approximated as 10 rounds each) instead of draining one shared pool; and the mental-control suppression clause of the base spell (see protection_from_evil.json)."
}
+15 -2
View File
@@ -2,11 +2,24 @@
"name": "Protection from Law",
"level": 1,
"school": "abjuration",
"components": "V, S, M/DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"spell_resistance": false,
"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."
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "ac", "value": 2, "bonus_type": "deflection", "condition": "attacker_is_lawful" },
{ "target": "fort", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_lawful" },
{ "target": "ref", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_lawful" },
{ "target": "will", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_lawful" }
],
"duration_rounds": 10
}
],
"description": "SRD spells/protection-from-law.md: functions like protection from evil, except the deflection and resistance bonuses apply against lawful creatures - \"+2 deflection bonus to AC and a +2 resistance bonus on saves against attacks or effects created by lawful creatures\" - so both bonuses are gated on the attacker/caster alignment (condition attacker_is_lawful) and never apply against a creature that is not lawful. Will negates (harmless), spell resistance no. Duration 1 min/level approximated as 10 rounds (engine convention for 1-minute durations). Not modeled: the renewed saving throw against mental control by lawful creatures and the lawful summoned creature contact restriction (same gaps as protection from evil)."
}
+15 -2
View File
@@ -2,11 +2,24 @@
"name": "Protection from Law, Communal",
"level": 2,
"school": "abjuration",
"components": "V, S, M/DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"spell_resistance": false,
"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."
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "ac", "value": 2, "bonus_type": "deflection", "condition": "attacker_is_lawful" },
{ "target": "fort", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_lawful" },
{ "target": "ref", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_lawful" },
{ "target": "will", "value": 2, "bonus_type": "resistance", "condition": "attacker_is_lawful" }
],
"duration_rounds": 10
}
],
"description": "SRD spells/protection-from-law-communal.md: functions like protection from law - each touched creature gains the +2 deflection bonus to AC and +2 resistance bonus on saves against attacks or effects created by lawful creatures (gated on the attacker/caster alignment via condition attacker_is_lawful, never applying against a non-lawful source). Will negates (harmless), spell resistance no. Not modeled: the SRD duration split - \"you divide the duration in 1-minute intervals among the creatures touched\" - the engine gives every touched creature the same fixed duration as protection from law (1 min/level approximated as 10 rounds each) instead of draining one shared pool; and the mental-control suppression clause of the base spell (see protection_from_law.json)."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Purify Food and Drink",
"level": 0,
"school": "transmutation",
"components": "V, S",
"range": "close",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Putrefy Food and Drink",
"level": 0,
"school": "transmutation",
"components": "V, S",
"range": "close",
"save": "will",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Ray of Enfeeblement",
"level": 1,
"school": "necromancy",
"components": "V, S",
"range": "close",
"save": "fort",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Ray of Frost",
"level": 0,
"school": "evocation",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+1
View File
@@ -2,6 +2,7 @@
"name": "Read Magic",
"level": 0,
"school": "divination",
"components": "V, S, F",
"range": "personal",
"save": "none",
"base_dc": 10,
+14 -2
View File
@@ -2,10 +2,22 @@
"name": "Resistance",
"level": 0,
"school": "abjuration",
"components": "V, S, M/DF",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "standard",
"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."
"effects": [
{
"type": "buff",
"modifiers": [
{ "target": "fort", "value": 1, "bonus_type": "resistance" },
{ "target": "ref", "value": 1, "bonus_type": "resistance" },
{ "target": "will", "value": 1, "bonus_type": "resistance" }
],
"duration_rounds": 10
}
],
"description": "SRD spells/resistance.md: a cantrip - the subject gains a +1 resistance bonus on saving throws for 1 minute. Will negates (harmless). Modeled as a BuffEffect granting +1 resistance to fort/ref/will for 10 rounds (1 minute); the resistance bonus type does not stack with itself, so a second Resistance (or a cloak of resistance) keeps the higher value. default_policy never casts it: cantrips have no spell slot, and _find_buff_spell skips spells the caster has no slot for, so Resistance cannot preempt a leveled buff (Divine Favor) in the known-spell order."
}
+2
View File
@@ -2,8 +2,10 @@
"name": "Restoration, Lesser",
"level": 2,
"school": "conjuration",
"components": "V, S",
"range": "touch",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "long",
"effects": [],
+1
View File
@@ -2,6 +2,7 @@
"name": "Sanctuary",
"level": 1,
"school": "abjuration",
"components": "V, S, DF",
"range": "touch",
"save": "will",
"base_dc": 10,
+3 -1
View File
@@ -2,10 +2,12 @@
"name": "Saving Finale",
"level": 1,
"school": "evocation",
"components": "V, S",
"range": "close",
"save": "will",
"harmless": true,
"base_dc": 10,
"cast_time": "immediate",
"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."
"description": "SRD spells/saving-finale.md: immediate action, requires an active bardic performance - when a creature affected by your bardic performance fails a saving throw, end the performance to let it immediately reroll the failed save and take the new result. Will negates (harmless). Deferred: rerolling an already-resolved save needs a post-save reaction hook plus a bardic-performance resource gate (the engine's saves are one-shot resolutions with no reroll window), a cross-cutting redesign. No combat effect encoded; the immediate casting time is kept for data fidelity."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Scorching Ray",
"level": 2,
"school": "evocation",
"components": "V, S",
"range": "close",
"save": "none",
"base_dc": 10,
+10 -2
View File
@@ -2,6 +2,7 @@
"name": "Shard of Chaos",
"level": 2,
"school": "evocation",
"components": "V, S, DF",
"range": "close",
"save": "will",
"base_dc": 10,
@@ -13,8 +14,15 @@
"formula": "0",
"types": ["force"],
"half_on_save": true,
"scaling": { "dice": "1d8", "per_levels": 2, "max_dice": 5 }
"scaling": { "dice": "1d8", "per_levels": 2, "max_dice": 5 },
"alignment_gate": {
"axis": "law_chaos",
"immune": "chaotic",
"full": "lawful",
"full_condition": "staggered",
"full_condition_rounds": 1
}
}
],
"description": "SRD spells/shard-of-chaos.md: evocation [chaotic], level 2 (cleric/oracle/warpriest), range close, dart-shaped projectile of chaotic energy delivered as a ranged touch attack, Will partial, spell resistance yes. A lawful creature struck takes 1d8 points of damage per two caster levels (maximum 5d8); a successful Will save halves the damage. Simplified: the engine has no alignment track, so the SRD alignment gate (no effect on chaotic creatures, half damage to neutrals, the lawful-outsider 1d6/level + slowed variant) cannot be represented - the base 1d8-per-two-levels damage (engine floor: caster_level // 2 dice) is modeled against every target as force damage (no energy type models chaotic energy), Will half."
"description": "SRD spells/shard-of-chaos.md: evocation [chaotic], level 2 (cleric/oracle/warpriest), range close, dart-shaped projectile of chaotic energy delivered as a ranged touch attack, Will partial, spell resistance yes. A lawful creature struck takes 1d8 points of damage per two caster levels (maximum 5d8) and, on a failed Will save, is slowed for 1 round; a successful Will save halves the damage and negates the slow. The bolt has no effect on chaotic creatures (alignment_gate immune) and deals only half damage to creatures neither lawful nor chaotic, without the slow (alignment_gate neutral halving). Simplifications: the SRD's slowed condition is mapped to the engine's staggered condition (the SRD slow entry states a slowed creature is staggered; the -1 penalty and half-speed parts of slow are not modeled); the lawful-outsider variant (1d6 per caster level, maximum 10d6) is not modeled (the engine has no outsider subtype); force damage stands in for chaotic energy (no energy type models it)."
}
+1
View File
@@ -2,6 +2,7 @@
"name": "Shield",
"level": 1,
"school": "abjuration",
"components": "V, S",
"range": "personal",
"save": "none",
"base_dc": 10,

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