feat(rules): SRD Mage Armor spell + non-stacking armor bonus type

SRD spells/mage-armor.md: 'An invisible but tangible field of force
surrounds the subject of a mage armor spell, providing a +4 armor bonus
to AC' - 'School conjuration (creation) [force]', level 1, 'Casting Time
1 standard action', 'Range touch', 'Duration 1 hour/level (D)',
'Spell Resistance no'.

- data/spells/mage_armor.json: buff +4 armor to AC, 600 rounds (1
  hour/level modeled fixed, the shield.json convention). Description
  documents the known simplification: the engine bakes worn armor into
  the AC total and cannot see it, so the armor bonus stacks additively
  with that baked value (two mage armors still do not stack).
- effects.py: 'armor' added to BonusType; absent from _STACKING_TYPES it
  behaves as a keep-the-highest type (verified by resolve_modifiers
  tests).
- Spell-count pins 107 -> 108 (test_runner_spells, batch3, test_spells)
  and the README references.
- Golden transcript untouched (buff branch only fires when no offensive
  spell is selected; the lich still opens with Magic Missile).
This commit is contained in:
2026-09-16 15:36:43 +02:00
parent 881f2ef9b2
commit 3e19d2974e
7 changed files with 161 additions and 14 deletions
+3 -2
View File
@@ -3,8 +3,8 @@
PF1e bonus types determine whether multiple bonuses to the same stat stack:
- Stacking types (dodge, racial, trait, untyped/"") all sum together.
- Non-stacking types (morale, sacred, profane, competence, insight, luck,
enhancement, deflection, natural, shield) keep only the highest value per
type.
enhancement, deflection, natural, shield, armor) keep only the highest
value per type.
- Penalties follow the same rules: same-type penalties don't stack (the
least severe — i.e. the max value — is kept).
@@ -38,6 +38,7 @@ BonusType = Literal[
"deflection", # no stack
"natural", # no stack
"shield", # no stack
"armor", # no stack (SRD mage armor: "+4 armor bonus to AC")
"circumstance", # no stack (simulator can't distinguish circumstances)
]