diff --git a/data/monsters/celestial_wolf.json b/data/monsters/celestial_wolf.json index 0f7b5c0..6e5f102 100644 --- a/data/monsters/celestial_wolf.json +++ b/data/monsters/celestial_wolf.json @@ -25,6 +25,8 @@ "ref": 5, "will": 1 }, + "arcane_caster": false, + "armor_spell_failure": 0, "attacks": [ { "id": "celestial_wolf:bite", diff --git a/data/monsters/giant_belostomatid.json b/data/monsters/giant_belostomatid.json index 4c57d98..85d2fe2 100644 --- a/data/monsters/giant_belostomatid.json +++ b/data/monsters/giant_belostomatid.json @@ -29,6 +29,8 @@ "ref": 2, "will": 1 }, + "arcane_caster": false, + "armor_spell_failure": 0, "attacks": [ { "name": "bite", diff --git a/data/monsters/goblin.json b/data/monsters/goblin.json index 977ed83..61978d2 100644 --- a/data/monsters/goblin.json +++ b/data/monsters/goblin.json @@ -20,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", diff --git a/data/monsters/orc.json b/data/monsters/orc.json index 9aae290..db962fb 100644 --- a/data/monsters/orc.json +++ b/data/monsters/orc.json @@ -21,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", diff --git a/data/monsters/thrune_agent.json b/data/monsters/thrune_agent.json index b0159bd..ed4e6c2 100644 --- a/data/monsters/thrune_agent.json +++ b/data/monsters/thrune_agent.json @@ -31,6 +31,8 @@ "will": 6 }, "casting_ability": "int", + "arcane_caster": true, + "armor_spell_failure": 0, "spell_slots": { "1": 5, "2": 5, diff --git a/data/pcs/esha.json b/data/pcs/esha.json index c44574e..1abe6b3 100644 --- a/data/pcs/esha.json +++ b/data/pcs/esha.json @@ -26,6 +26,8 @@ "will": 5 }, "casting_ability": "int", + "arcane_caster": false, + "armor_spell_failure": 0, "spell_slots": {}, "resources": {}, "attacks": [ diff --git a/data/pcs/esha_mount.json b/data/pcs/esha_mount.json index 6859e2b..d3f8786 100644 --- a/data/pcs/esha_mount.json +++ b/data/pcs/esha_mount.json @@ -57,6 +57,8 @@ ], "spells": [], "casting_ability": "wis", + "arcane_caster": false, + "armor_spell_failure": 0, "resources": {}, "metamagic_feats": [], "skills": {} diff --git a/data/pcs/harvie.json b/data/pcs/harvie.json index f5ea52f..0158a44 100644 --- a/data/pcs/harvie.json +++ b/data/pcs/harvie.json @@ -78,6 +78,8 @@ ], "spells": [], "casting_ability": "wis", + "arcane_caster": false, + "armor_spell_failure": 0, "spell_slots": {}, "resources": {}, "metamagic_feats": [], diff --git a/data/pcs/ierlieth.json b/data/pcs/ierlieth.json index 431be76..e3cd40f 100644 --- a/data/pcs/ierlieth.json +++ b/data/pcs/ierlieth.json @@ -77,6 +77,8 @@ ], "spells": [], "casting_ability": "wis", + "arcane_caster": false, + "armor_spell_failure": 0, "spell_slots": {}, "resources": {}, "metamagic_feats": [], diff --git a/data/pcs/jeanne.json b/data/pcs/jeanne.json index d29fa7b..7e9be21 100644 --- a/data/pcs/jeanne.json +++ b/data/pcs/jeanne.json @@ -75,6 +75,8 @@ ], "spells": [], "casting_ability": "wis", + "arcane_caster": false, + "armor_spell_failure": 0, "spell_slots": {}, "resources": {}, "metamagic_feats": [], diff --git a/data/pcs/misty.json b/data/pcs/misty.json index 39830a8..983f93d 100644 --- a/data/pcs/misty.json +++ b/data/pcs/misty.json @@ -26,6 +26,8 @@ "will": 3 }, "casting_ability": "cha", + "arcane_caster": true, + "armor_spell_failure": 0, "spell_slots": { "1": 4, "2": 3 diff --git a/data/pcs/nairda.json b/data/pcs/nairda.json index d5543f1..baa23b9 100644 --- a/data/pcs/nairda.json +++ b/data/pcs/nairda.json @@ -26,6 +26,8 @@ "will": 7 }, "casting_ability": "int", + "arcane_caster": true, + "armor_spell_failure": 0, "spell_slots": { "1": 4, "2": 3 diff --git a/data/pcs/tammara.json b/data/pcs/tammara.json index 2272058..7f274b8 100644 --- a/data/pcs/tammara.json +++ b/data/pcs/tammara.json @@ -26,6 +26,8 @@ "will": 8 }, "casting_ability": "wis", + "arcane_caster": false, + "armor_spell_failure": 0, "spell_slots": { "1": 4, "2": 3 diff --git a/tests/test_spell_failure.py b/tests/test_spell_failure.py index ce5a83d..e5b3874 100644 --- a/tests/test_spell_failure.py +++ b/tests/test_spell_failure.py @@ -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: