fix(rules): natural weapons get no BAB iteratives, secondary attacks at -5

PF1e (CRB "Natural Attacks"):
- Natural weapons (natural=true) get exactly one swing per full attack,
  regardless of BAB (no BAB iteratives for natural attacks).
- Secondary natural attacks (secondary=true) attack at BAB -5; the
  Multiattack feat reduces this to -2.
- Manifested in combat.py (_full_attack_weapon, _resolve_swing),
  models.py (AttackSpec.natural/secondary), 6 data JSONs, 4 new tests,
  and README documentation.

1461 passed, ruff clean, basedpyright 0 errors.
This commit is contained in:
2026-09-16 12:25:30 +02:00
parent 59caf67909
commit e5691bdb6f
10 changed files with 739 additions and 508 deletions
+78 -72
View File
@@ -1,76 +1,82 @@
{
"name": "Harvie",
"level": 2,
"size": "Small",
"abilities": {
"str_score": 10,
"dex_score": 15,
"con_score": 12,
"int_score": 2,
"wis_score": 12,
"cha_score": 6
},
"hp_max": 15,
"ac": {
"total": 14,
"touch": 13,
"flat_footed": 12
},
"bab": 1,
"initiative_mod": 2,
"speed_land_ft": 10,
"speed_fly_ft": 80,
"saves": {
"fort": 4,
"ref": 5,
"will": 2
},
"attacks": [
"name": "Harvie",
"level": 2,
"size": "Small",
"abilities": {
"str_score": 10,
"dex_score": 15,
"con_score": 12,
"int_score": 2,
"wis_score": 12,
"cha_score": 6
},
"hp_max": 15,
"ac": {
"total": 14,
"touch": 13,
"flat_footed": 12
},
"bab": 1,
"initiative_mod": 2,
"speed_land_ft": 10,
"speed_fly_ft": 80,
"saves": {
"fort": 4,
"ref": 5,
"will": 2
},
"attacks": [
{
"id": "harvie:bite",
"name": "Bite",
"kind": "melee",
"attack_bonus": 3,
"damage": [
{
"id": "harvie:bite",
"name": "Bite",
"kind": "melee",
"attack_bonus": 3,
"damage": [
{
"formula": "1d4",
"types": ["piercing"]
}
],
"damage_bonus": 0,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
},
{
"id": "harvie:claw",
"name": "Claw",
"kind": "melee",
"attack_bonus": 3,
"damage": [
{
"formula": "1d3",
"types": ["slashing"]
}
],
"damage_bonus": 0,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
"formula": "1d4",
"types": [
"piercing"
]
}
],
"source": "fiches_personnages/harvie_sheet.json",
"features": [
"Friendly Face",
"Fey-touched",
"Animal (race)"
],
"spells": [],
"casting_ability": "wis",
"spell_slots": {},
"resources": {},
"metamagic_feats": [],
"skills": {}
],
"damage_bonus": 0,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"natural": true
},
{
"id": "harvie:claw",
"name": "Claw",
"kind": "melee",
"attack_bonus": 3,
"damage": [
{
"formula": "1d3",
"types": [
"slashing"
]
}
],
"damage_bonus": 0,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"natural": true
}
],
"source": "fiches_personnages/harvie_sheet.json",
"features": [
"Friendly Face",
"Fey-touched",
"Animal (race)"
],
"spells": [],
"casting_ability": "wis",
"spell_slots": {},
"resources": {},
"metamagic_feats": [],
"skills": {}
}
+58 -55
View File
@@ -1,59 +1,62 @@
{
"name": "Ierlieth",
"level": 4,
"size": "Tiny",
"abilities": {
"str_score": 8,
"dex_score": 17,
"con_score": 11,
"int_score": 10,
"wis_score": 10,
"cha_score": 9
},
"hp_max": 29,
"ac": {
"total": 17,
"touch": 15,
"flat_footed": 14
},
"bab": 3,
"initiative_mod": 3,
"speed_land_ft": 15,
"speed_fly_ft": 40,
"saves": {
"fort": 3,
"ref": 6,
"will": 3
},
"attacks": [
"name": "Ierlieth",
"level": 4,
"size": "Tiny",
"abilities": {
"str_score": 8,
"dex_score": 17,
"con_score": 11,
"int_score": 10,
"wis_score": 10,
"cha_score": 9
},
"hp_max": 29,
"ac": {
"total": 17,
"touch": 15,
"flat_footed": 14
},
"bab": 3,
"initiative_mod": 3,
"speed_land_ft": 15,
"speed_fly_ft": 40,
"saves": {
"fort": 3,
"ref": 6,
"will": 3
},
"attacks": [
{
"id": "ierlieth:bite",
"name": "Bite",
"kind": "melee",
"attack_bonus": 4,
"damage": [
{
"id": "ierlieth:bite",
"name": "Bite",
"kind": "melee",
"attack_bonus": 4,
"damage": [
{
"formula": "1d4",
"types": ["piercing"]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
"formula": "1d4",
"types": [
"piercing"
]
}
],
"source": "fiches_personnages/ierlieth_sheet.json",
"features": [
"Drake (race)",
"Toughness",
"Power Attack"
],
"spells": [],
"casting_ability": "wis",
"spell_slots": {},
"resources": {},
"metamagic_feats": [],
"skills": {}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"natural": true
}
],
"source": "fiches_personnages/ierlieth_sheet.json",
"features": [
"Drake (race)",
"Toughness",
"Power Attack"
],
"spells": [],
"casting_ability": "wis",
"spell_slots": {},
"resources": {},
"metamagic_feats": [],
"skills": {}
}
+164 -162
View File
@@ -1,166 +1,168 @@
{
"name": "Misty",
"level": 4,
"size": "Medium",
"abilities": {
"str_score": 12,
"dex_score": 19,
"con_score": 14,
"int_score": 16,
"wis_score": 7,
"cha_score": 18
},
"hp_max": 31,
"ac": {
"total": 20,
"touch": 14,
"flat_footed": 16
},
"bab": 3,
"initiative_mod": 4,
"speed_land_ft": 30,
"saves": {
"fort": 7,
"ref": 9,
"will": 3
},
"casting_ability": "cha",
"spell_slots": {
"1": 4,
"2": 3
},
"resources": {
"bardic_rounds": 12
},
"attacks": [
"name": "Misty",
"level": 4,
"size": "Medium",
"abilities": {
"str_score": 12,
"dex_score": 19,
"con_score": 14,
"int_score": 16,
"wis_score": 7,
"cha_score": 18
},
"hp_max": 31,
"ac": {
"total": 20,
"touch": 14,
"flat_footed": 16
},
"bab": 3,
"initiative_mod": 4,
"speed_land_ft": 30,
"saves": {
"fort": 7,
"ref": 9,
"will": 3
},
"casting_ability": "cha",
"spell_slots": {
"1": 4,
"2": 3
},
"resources": {
"bardic_rounds": 12
},
"attacks": [
{
"name": "MWK Rapier",
"kind": "melee",
"attack_bonus": 8,
"damage": [
{
"name": "MWK Rapier",
"kind": "melee",
"attack_bonus": 8,
"damage": [
{
"formula": "1d6",
"types": [
"piercing"
]
}
],
"damage_bonus": 1,
"crit_range": 18,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
},
{
"name": "Dagger",
"kind": "melee",
"attack_bonus": 7,
"damage": [
{
"formula": "1d4",
"types": [
"piercing"
]
}
],
"damage_bonus": 1,
"crit_range": 19,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"range_increment_ft": 10
},
{
"name": "Talons (attaque naturelle)",
"kind": "melee",
"attack_bonus": 4,
"damage": [
{
"formula": "1d4",
"types": [
"slashing"
]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
},
{
"name": "Bite (attaque naturelle)",
"kind": "melee",
"attack_bonus": 4,
"damage": [
{
"formula": "1d4",
"types": [
"piercing"
]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
},
{
"name": "Unarmed Strike",
"kind": "melee",
"attack_bonus": 4,
"damage": [
{
"formula": "1d3",
"types": [
"bludgeoning"
]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
"formula": "1d6",
"types": [
"piercing"
]
}
],
"source": "fiches_personnages/misty_sheet.json",
"features": [
"Harmonic Spell",
"Weapon Finesse",
"Axe to Grind (combat)",
"Spell Vulnerability (divination; drawback)",
"Born Damned (race: tiefling)",
"Gifted Adept (dispel magic; magic)",
"Prehensile Tail (tiefling daemon-spawn)",
"Scaled Skin (electricity) (tiefling daemon-spawn)",
"Maw or Claw (Bite) (tiefling daemon-spawn)"
],
"spells": [
"Prestidigitation",
"Oath of Anonymity",
"Detect Magic",
"Read Magic",
"Disrupt Undead",
"Ray of Frost",
"Summon Instrument",
"Mage Hand",
"Open/Close",
"Arcane Mark",
"Acid Splash",
"Grasp",
"Shield",
"Cure Light Wounds",
"Color Spray",
"Saving Finale",
"Vanish",
"Shocking Grasp",
"Burning Hands",
"Alarm",
"Scorching Ray",
"Bladed Dash",
"Mirror Image",
"Heroism"
]
],
"damage_bonus": 1,
"crit_range": 18,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
},
{
"name": "Dagger",
"kind": "melee",
"attack_bonus": 7,
"damage": [
{
"formula": "1d4",
"types": [
"piercing"
]
}
],
"damage_bonus": 1,
"crit_range": 19,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"range_increment_ft": 10
},
{
"name": "Talons (attaque naturelle)",
"kind": "melee",
"attack_bonus": 4,
"damage": [
{
"formula": "1d4",
"types": [
"slashing"
]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"natural": true
},
{
"name": "Bite (attaque naturelle)",
"kind": "melee",
"attack_bonus": 4,
"damage": [
{
"formula": "1d4",
"types": [
"piercing"
]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"natural": true
},
{
"name": "Unarmed Strike",
"kind": "melee",
"attack_bonus": 4,
"damage": [
{
"formula": "1d3",
"types": [
"bludgeoning"
]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
}
],
"source": "fiches_personnages/misty_sheet.json",
"features": [
"Harmonic Spell",
"Weapon Finesse",
"Axe to Grind (combat)",
"Spell Vulnerability (divination; drawback)",
"Born Damned (race: tiefling)",
"Gifted Adept (dispel magic; magic)",
"Prehensile Tail (tiefling daemon-spawn)",
"Scaled Skin (electricity) (tiefling daemon-spawn)",
"Maw or Claw (Bite) (tiefling daemon-spawn)"
],
"spells": [
"Prestidigitation",
"Oath of Anonymity",
"Detect Magic",
"Read Magic",
"Disrupt Undead",
"Ray of Frost",
"Summon Instrument",
"Mage Hand",
"Open/Close",
"Arcane Mark",
"Acid Splash",
"Grasp",
"Shield",
"Cure Light Wounds",
"Color Spray",
"Saving Finale",
"Vanish",
"Shocking Grasp",
"Burning Hands",
"Alarm",
"Scorching Ray",
"Bladed Dash",
"Mirror Image",
"Heroism"
]
}
+123 -122
View File
@@ -1,126 +1,127 @@
{
"name": "Oni Triumvir",
"level": 4,
"size": "Medium",
"abilities": {
"str_score": 12,
"dex_score": 14,
"con_score": 14,
"int_score": 12,
"wis_score": 18,
"cha_score": 16
},
"hp_max": 36,
"ac": {
"total": 19,
"touch": 18,
"flat_footed": 17
},
"bab": 4,
"initiative_mod": 2,
"speed_land_ft": 30,
"saves": {
"fort": 7,
"ref": 7,
"will": 9
},
"casting_ability": "wis",
"spell_slots": {
"1": 4,
"2": 3
},
"resources": {
"channel_energy": 6
},
"attacks": [
"name": "Oni Triumvir",
"level": 4,
"size": "Medium",
"abilities": {
"str_score": 12,
"dex_score": 14,
"con_score": 14,
"int_score": 12,
"wis_score": 18,
"cha_score": 16
},
"hp_max": 36,
"ac": {
"total": 19,
"touch": 18,
"flat_footed": 17
},
"bab": 4,
"initiative_mod": 2,
"speed_land_ft": 30,
"saves": {
"fort": 7,
"ref": 7,
"will": 9
},
"casting_ability": "wis",
"spell_slots": {
"1": 4,
"2": 3
},
"resources": {
"channel_energy": 6
},
"attacks": [
{
"name": "Longbow",
"kind": "ranged",
"attack_bonus": 6,
"damage": [
{
"name": "Longbow",
"kind": "ranged",
"attack_bonus": 6,
"damage": [
{
"formula": "1d8",
"types": [
"piercing"
]
}
],
"damage_bonus": 0,
"crit_range": 20,
"crit_mult": 3,
"reach_ft": 5,
"count": 1,
"range_increment_ft": 100
},
{
"name": "Alchemist's Fire",
"kind": "touch",
"attack_bonus": 6,
"damage": [
{
"formula": "1d6",
"types": [
"fire"
]
}
],
"damage_bonus": 0,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"range_increment_ft": 10
},
{
"name": "Attaques naturelles (Shifter Claws / formes fées)",
"kind": "melee",
"attack_bonus": 5,
"damage": [
{
"formula": "1d6",
"types": [
"slashing"
]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1
"formula": "1d8",
"types": [
"piercing"
]
}
],
"source": "fiches_personnages/oni_sheet.json",
"notes": "Armes placeholder Foundry sans dégâts encodés omises : 'une Pioche' (non maîtrisée) et 'Weapon' (à remplacer par la vraie arme).",
"features": [
"Summon Good Monster",
"Sacred Summons",
"Selective Channeling",
"Extremely Fashionable",
"Exalted of the Society (Cleric, Pathfinder Society)",
"Bitter",
"Ambitious"
],
"spells": [
"Guidance",
"Detect Magic",
"Stabilize",
"Read Magic",
"Detect Poison",
"Purify Food and Drink",
"Summon Monster I",
"Shield of Faith",
"Forbid Action",
"Cure Light Wounds",
"Hidden Diplomacy",
"Protection from Evil",
"Bless",
"Summon Monster II",
"Cure Moderate Wounds",
"Sound Burst",
"Suppress Charms and Compulsions",
"Delay Poison",
"Ironskin",
"Restoration, Lesser"
]
],
"damage_bonus": 0,
"crit_range": 20,
"crit_mult": 3,
"reach_ft": 5,
"count": 1,
"range_increment_ft": 100
},
{
"name": "Alchemist's Fire",
"kind": "touch",
"attack_bonus": 6,
"damage": [
{
"formula": "1d6",
"types": [
"fire"
]
}
],
"damage_bonus": 0,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"range_increment_ft": 10
},
{
"name": "Attaques naturelles (Shifter Claws / formes fées)",
"kind": "melee",
"attack_bonus": 5,
"damage": [
{
"formula": "1d6",
"types": [
"slashing"
]
}
],
"damage_bonus": 1,
"crit_range": 20,
"crit_mult": 2,
"reach_ft": 5,
"count": 1,
"natural": true
}
],
"source": "fiches_personnages/oni_sheet.json",
"notes": "Armes placeholder Foundry sans dégâts encodés omises : 'une Pioche' (non maîtrisée) et 'Weapon' (à remplacer par la vraie arme).",
"features": [
"Summon Good Monster",
"Sacred Summons",
"Selective Channeling",
"Extremely Fashionable",
"Exalted of the Society (Cleric, Pathfinder Society)",
"Bitter",
"Ambitious"
],
"spells": [
"Guidance",
"Detect Magic",
"Stabilize",
"Read Magic",
"Detect Poison",
"Purify Food and Drink",
"Summon Monster I",
"Shield of Faith",
"Forbid Action",
"Cure Light Wounds",
"Hidden Diplomacy",
"Protection from Evil",
"Bless",
"Summon Monster II",
"Cure Moderate Wounds",
"Sound Burst",
"Suppress Charms and Compulsions",
"Delay Poison",
"Ironskin",
"Restoration, Lesser"
]
}