fix(rules): lich data conforms to the KB stat block
Verified against the KB (bestiary/lich.md, Bestiary Lich CR 12):
- bab 8 -> 5 ('Base Atk +5')
- Paralyzing Touch attack_bonus 10 -> 5 ('Melee touch +5 (1d8+5 plus
paralyzing touch)') and marked natural: true (template: 'A lich has a
touch attack that it can use once per round as a natural weapon [...]
treated as a primary natural weapon' - no BAB iteratives per e5691bd)
- damage_bonus 5 -> 0: the +5 (1 point per 2 HD) is already in the
1d8+5 formula and _resolve_swing adds damage_bonus on top (double
counting: the old data dealt 1d8+10)
- ac 25/14/23 -> 23/14/21 ('AC 23, touch 14, flat-footed 21'; touch was
already correct)
- saves 10/10/14 -> 6/7/11 ('Fort +6, Ref +7, Will +11')
- con_score 14 -> 10: 'Being undead, a lich has no Constitution score'
(Con '—'); the int-only schema encodes 'no score' as the neutral 10
(modifier +0), inert anyway since undead are destroyed at hp <= 0.
Left as intentional engine tuning (residual divergences, documented in
the final report): hp 100 vs 111, Str 12 vs 10, Int 20 vs 22,
initiative +6 vs +2, fear aura 30 ft/DC 22 vs 60 ft/DC 18.
Golden transcript untouched (the lich wins via Magic Missile before any
weapon swing; MM DC depends on Int, unchanged). Pinned a new
test_json_matches_srd_stat_block.
This commit is contained in:
+34
-6
@@ -36,23 +36,24 @@ def _make_lich() -> Combatant:
|
||||
size="Medium",
|
||||
creature_type="undead",
|
||||
abilities=AbilityScores(
|
||||
str_score=12, dex_score=14, con_score=14,
|
||||
str_score=12, dex_score=14, con_score=10,
|
||||
int_score=20, wis_score=14, cha_score=16,
|
||||
),
|
||||
hp_max=100,
|
||||
ac=ACProfile(total=25, touch=14, flat_footed=23),
|
||||
bab=8,
|
||||
ac=ACProfile(total=23, touch=14, flat_footed=21),
|
||||
bab=5,
|
||||
initiative_mod=6,
|
||||
speed_land_ft=30,
|
||||
saves=Saves(fort=10, ref=10, will=14),
|
||||
saves=Saves(fort=6, ref=7, will=11),
|
||||
attacks=[
|
||||
AttackSpec(
|
||||
id="lich:touch",
|
||||
name="Paralyzing Touch",
|
||||
kind="touch",
|
||||
attack_bonus=10,
|
||||
attack_bonus=5,
|
||||
natural=True,
|
||||
damage=[DamageComponent(formula=parse_dice("1d8+5"), types=["negative"])],
|
||||
damage_bonus=5,
|
||||
damage_bonus=0,
|
||||
crit_range=20,
|
||||
crit_mult=2,
|
||||
reach_ft=5,
|
||||
@@ -225,3 +226,30 @@ class TestLichJsonIntegrity:
|
||||
assert not any("spell_slots_" in k for k in data)
|
||||
assert len(data["casting_ability"]) > 0
|
||||
assert data["spell_slots"]["6"] == 3
|
||||
|
||||
def test_json_matches_srd_stat_block(self) -> None:
|
||||
"""Given: the lich bestiary JSON
|
||||
When: checked against the KB stat block (Bestiary "Lich":
|
||||
"Base Atk +5", "Melee touch +5 (1d8+5 plus paralyzing touch)",
|
||||
"AC 23, touch 14, flat-footed 21", "Fort +6, Ref +7, Will +11",
|
||||
and the template's "Being undead, a lich has no Constitution score")
|
||||
Then: BAB, the natural touch attack, AC, saves and CON all match."""
|
||||
data = json.loads(_DATA.read_text(encoding="utf-8"))
|
||||
assert data["bab"] == 5
|
||||
assert data["ac"] == {"total": 23, "touch": 14, "flat_footed": 21}
|
||||
assert data["saves"] == {"fort": 6, "ref": 7, "will": 11}
|
||||
touch = data["attacks"][0]
|
||||
assert touch["attack_bonus"] == 5
|
||||
# SRD lich template: "A lich has a touch attack that it can use once
|
||||
# per round as a natural weapon [...] treated as a primary natural
|
||||
# weapon" - no BAB iteratives (CRB "Natural Attacks").
|
||||
assert touch["natural"] is True
|
||||
assert touch["damage"][0]["formula"] == "1d8+5"
|
||||
# The +5 (1 point per 2 HD, SRD "Damage") is already in the formula;
|
||||
# damage_bonus would be added on top (double counting).
|
||||
assert touch["damage_bonus"] == 0
|
||||
# Undead have no CON score: encoded as the neutral 10 (modifier +0);
|
||||
# the engine destroys undead at hp <= 0 (SRD Undead traits), so the
|
||||
# score is never used for the dying band.
|
||||
assert data["creature_type"] == "undead"
|
||||
assert data["abilities"]["con_score"] == 10
|
||||
|
||||
Reference in New Issue
Block a user