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.
This commit is contained in:
2026-09-20 14:30:57 +02:00
parent 91666d84b9
commit f6292f8bdc
14 changed files with 63 additions and 9 deletions
+37 -9
View File
@@ -320,15 +320,43 @@ def test_observation_exposes_components_and_caster_fields() -> None:
json.dumps(obs)
def test_lich_is_arcane_oni_is_not() -> None:
"""Smoke test on the shipped data: the lich casts arcane spells with
no armor (ASF 0); Oni is a divine caster and never incurs ASF."""
lich = load_combatant(DATA_DIR / "monsters" / "lich.json")
oni = load_combatant(DATA_DIR / "pcs" / "oni.json")
assert lich.arcane_caster is True
assert lich.armor_spell_failure == 0
assert oni.arcane_caster is False
assert oni.armor_spell_failure == 0
def test_combatant_caster_flags_and_asf_match_sheets() -> None:
"""Smoke test on the shipped data, one entry per combatant JSON.
arcane_caster follows the sheet class (fiches_personnages identity.classes
/ bestiary KB): magus/bard/witch/sorcerer/wizard true, divine and
non-casters false. armor_spell_failure is the EFFECTIVE percent:
- lich: no armor (KB bestiary/lich.json, unarmored skeletal body).
- misty: mithral shirt (sheet combat.ac note "+6 mithral shirt",
light armor, RAW ASF 10%) but every spell she knows is a bard or
magus spell and both classes cast their own-list spells in light
armor with no ASF (classes/bard.md, classes/magus.md) -> 0.
- nairda: no armor (sheet combat.ac note "Pas d'armure") -> 0.
- thrune_agent: +1 studded leather (KB bestiary/thrune-agent.md gear,
light armor) but all its spells are magus spells and a magus casts
magus spells in light armor with no ASF (classes/magus.md) -> 0.
"""
expectations: dict[str, tuple[bool, int]] = {
"pcs/misty.json": (True, 0),
"pcs/nairda.json": (True, 0),
"pcs/oni.json": (False, 0),
"pcs/tammara.json": (False, 0),
"pcs/esha.json": (False, 0),
"pcs/esha_mount.json": (False, 0),
"pcs/harvie.json": (False, 0),
"pcs/ierlieth.json": (False, 0),
"pcs/jeanne.json": (False, 0),
"monsters/lich.json": (True, 0),
"monsters/thrune_agent.json": (True, 0),
"monsters/goblin.json": (False, 0),
"monsters/orc.json": (False, 0),
"monsters/celestial_wolf.json": (False, 0),
"monsters/giant_belostomatid.json": (False, 0),
}
for rel, (arcane, asf) in expectations.items():
loaded = load_combatant(DATA_DIR / rel)
assert loaded.arcane_caster is arcane, rel
assert loaded.armor_spell_failure == asf, rel
def test_registry_spells_declare_kb_components() -> None: