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.
This commit is contained in:
@@ -322,6 +322,16 @@ class Combatant(_FrozenModel):
|
||||
metamagic_feats: list[str] = Field(default_factory=list) # e.g. Empower/Maximize/Extend Spell
|
||||
spells: list[str] = Field(default_factory=list)
|
||||
spell_resistance: int = 0
|
||||
# PF1e (SRD, Magic - Arcane Spells and Armor): the armor/shield worn
|
||||
# imposes an arcane spell failure chance (percent, 0-100) on arcane
|
||||
# spells with a somatic component. The model has no equipment, so the
|
||||
# chance is authored directly on the combatant JSON.
|
||||
# rules/prpg-core-rulebook/arcane-spells-and-armor.md
|
||||
armor_spell_failure: int = Field(default=0, ge=0, le=100)
|
||||
# True when the combatant's spellcasting is arcane (sorcerer, wizard,
|
||||
# magus...) - only arcane casters incur armor spell failure; divine
|
||||
# casters (cleric, druid...) wear any armor freely.
|
||||
arcane_caster: bool = False
|
||||
resistances: dict[str, int] = Field(default_factory=dict)
|
||||
immunities: tuple[str, ...] = ()
|
||||
# PF1e (SRD "Surprise"): "If some but not all of the combatants are aware
|
||||
|
||||
Reference in New Issue
Block a user