1f8f068dd1
- 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.
88 lines
1.8 KiB
JSON
88 lines
1.8 KiB
JSON
{
|
|
"name": "Lich",
|
|
"alignment": "NE",
|
|
"level": 11,
|
|
"size": "Medium",
|
|
"creature_type": "undead",
|
|
"abilities": {
|
|
"str_score": 10,
|
|
"dex_score": 14,
|
|
"con_score": 10,
|
|
"int_score": 22,
|
|
"wis_score": 14,
|
|
"cha_score": 16
|
|
},
|
|
"hp_max": 111,
|
|
"ac": {
|
|
"total": 23,
|
|
"touch": 14,
|
|
"flat_footed": 21
|
|
},
|
|
"bab": 5,
|
|
"initiative_mod": 2,
|
|
"speed_land_ft": 30,
|
|
"saves": {
|
|
"fort": 6,
|
|
"ref": 7,
|
|
"will": 11
|
|
},
|
|
"casting_ability": "int",
|
|
"arcane_caster": true,
|
|
"armor_spell_failure": 0,
|
|
"spell_slots": {
|
|
"1": 4,
|
|
"2": 4,
|
|
"3": 4,
|
|
"4": 4,
|
|
"5": 4,
|
|
"6": 3
|
|
},
|
|
"attacks": [
|
|
{
|
|
"id": "lich:touch",
|
|
"name": "Paralyzing Touch",
|
|
"kind": "touch",
|
|
"attack_bonus": 5,
|
|
"natural": true,
|
|
"damage": [
|
|
{
|
|
"formula": "1d8+5",
|
|
"types": ["negative"]
|
|
}
|
|
],
|
|
"damage_bonus": 0,
|
|
"crit_range": 20,
|
|
"crit_mult": 2,
|
|
"reach_ft": 5,
|
|
"count": 1
|
|
}
|
|
],
|
|
"dr": {
|
|
"amount": 15,
|
|
"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",
|
|
"Undead Traits"
|
|
],
|
|
"spells": [
|
|
"Mage Armor",
|
|
"Shield",
|
|
"Magic Missile",
|
|
"Scorching Ray",
|
|
"Fireball",
|
|
"Lightning Bolt",
|
|
"Hold Person",
|
|
"Wall of Fire",
|
|
"Cloudkill",
|
|
"Disintegrate",
|
|
"Horrid Wilting",
|
|
"Power Word Kill"
|
|
],
|
|
"metamagic_feats": [],
|
|
"skills": {}
|
|
}
|