diff --git a/internal/plugin/bestiary_srd.go b/internal/plugin/bestiary_srd.go index 49bc7e9..055fd33 100644 --- a/internal/plugin/bestiary_srd.go +++ b/internal/plugin/bestiary_srd.go @@ -8,12 +8,26 @@ package plugin // rebalance is calibrated against it. Do NOT feed these profiles into // auto-resolve. // -// The turn-based path can afford canonical SRD multiattack: the player issues a -// command per round, so a creature making three attack rolls a turn is a -// tactical problem the player can answer (heal, control, flee) rather than an -// un-survivable spike. Each SRDAttack carries its own to-hit and average -// damage, derived from the creature's SRD attack profile — not the tuned -// auto-resolve Attack stat. +// The turn-based path can afford canonical SRD multiattack *structure*: the +// player issues a command per round, so a creature making three attack rolls a +// turn is a tactical problem the player can answer (heal, control, flee) rather +// than an un-survivable spike. +// +// Balance basis (2026-05-14 tuning pass): raw SRD damage is calibrated for a +// 4-PC party and one-shots gogobee's single player — slice 1 shipped it +// canonical and it was un-survivable across every tier (marilith ~91/round, +// even Grol ~20/round vs a level-1 mage). So each profile keeps its SRD +// *shape* — attack count, names, per-attack to-hit, and the damage split +// between attacks — but the per-attack Damage values are scaled so the +// profile's round total lands at roughly 1.3x the creature's tuned +// auto-resolve Attack stat (dndBestiary). That 1.3x is deliberate: the +// 2026-05-10 rebalance calibrated the single-attack Attack stat as the +// gameplay floor, and turn-based fights should hit a little harder than +// auto-resolve to make the player's per-round agency (heal / control / flee) +// matter — but multiattack already raises hit reliability vs high-AC builds, +// and monster abilities now fire as riders on top, so the headroom over +// auto-resolve stays modest. Damage flows through the same calcDamage penalty +// formula as the Attack stat, so the two are directly comparable in scale. // // File is intentionally NOT dnd_-prefixed (see memory: no new dnd_* identifiers). @@ -34,124 +48,126 @@ type SRDProfile struct { // srdProfiles is the turn-based multiattack registry, keyed by bestiary ID. // Only elites and bosses that reach the turn-based engine need an entry; every -// other creature falls through to the single-attack template path. Damage -// values are SRD attack-profile averages, rounded. +// other creature falls through to the single-attack template path. Per-attack +// Damage is tuned (not raw SRD) — see the balance-basis note above; the +// // -comment after each profile records the tuned auto-resolve Attack stat the +// total was scaled against. var srdProfiles = map[string]SRDProfile{ // ── Bosses ─────────────────────────────────────────────────────────── - "boss_grol_unbroken": {Attacks: []SRDAttack{ - {Name: "Morningstar", AttackBonus: 5, Damage: 11}, - {Name: "Javelin", AttackBonus: 5, Damage: 9}, + "boss_grol_unbroken": {Attacks: []SRDAttack{ // Attack 6 → ~8 + {Name: "Morningstar", AttackBonus: 5, Damage: 5}, + {Name: "Javelin", AttackBonus: 5, Damage: 3}, }}, - "boss_valdris_unburied": {Attacks: []SRDAttack{ - {Name: "Paralyzing Touch", AttackBonus: 7, Damage: 12}, - {Name: "Necrotic Bolt", AttackBonus: 7, Damage: 10}, + "boss_valdris_unburied": {Attacks: []SRDAttack{ // Attack 8 → ~10 + {Name: "Paralyzing Touch", AttackBonus: 7, Damage: 6}, + {Name: "Necrotic Bolt", AttackBonus: 7, Damage: 4}, }}, - "boss_hollow_king": {Attacks: []SRDAttack{ - {Name: "Root Slam", AttackBonus: 7, Damage: 13}, - {Name: "Root Slam", AttackBonus: 7, Damage: 13}, + "boss_hollow_king": {Attacks: []SRDAttack{ // Attack 10 → ~13 + {Name: "Root Slam", AttackBonus: 7, Damage: 7}, + {Name: "Root Slam", AttackBonus: 7, Damage: 6}, }}, - "boss_dreaming_aboleth": {Attacks: []SRDAttack{ - {Name: "Tentacle", AttackBonus: 9, Damage: 12}, - {Name: "Tentacle", AttackBonus: 9, Damage: 12}, - {Name: "Tentacle", AttackBonus: 9, Damage: 12}, + "boss_dreaming_aboleth": {Attacks: []SRDAttack{ // Attack 16 → ~21 + {Name: "Tentacle", AttackBonus: 9, Damage: 7}, + {Name: "Tentacle", AttackBonus: 9, Damage: 7}, + {Name: "Tentacle", AttackBonus: 9, Damage: 7}, }}, - "boss_aldric_blackspire": {Attacks: []SRDAttack{ - {Name: "Unarmed Strike", AttackBonus: 9, Damage: 10}, - {Name: "Bite", AttackBonus: 9, Damage: 9}, + "boss_aldric_blackspire": {Attacks: []SRDAttack{ // Attack 21 → ~27 + {Name: "Unarmed Strike", AttackBonus: 9, Damage: 14}, + {Name: "Bite", AttackBonus: 9, Damage: 13}, }}, - "boss_emberlord_thyrak": {Attacks: []SRDAttack{ - {Name: "Molten Maul", AttackBonus: 8, Damage: 15}, - {Name: "Molten Maul", AttackBonus: 8, Damage: 15}, + "boss_emberlord_thyrak": {Attacks: []SRDAttack{ // Attack 14 → ~18 + {Name: "Molten Maul", AttackBonus: 8, Damage: 9}, + {Name: "Molten Maul", AttackBonus: 8, Damage: 9}, }}, - "boss_ilvaras_xunyl": {Attacks: []SRDAttack{ - {Name: "Scourge", AttackBonus: 9, Damage: 14}, - {Name: "Scourge", AttackBonus: 9, Damage: 14}, + "boss_ilvaras_xunyl": {Attacks: []SRDAttack{ // Attack 19 → ~25 + {Name: "Scourge", AttackBonus: 9, Damage: 13}, + {Name: "Scourge", AttackBonus: 9, Damage: 12}, }}, - "boss_thornmother": {Attacks: []SRDAttack{ - {Name: "Thorned Lash", AttackBonus: 8, Damage: 16}, - {Name: "Thorned Lash", AttackBonus: 8, Damage: 16}, + "boss_thornmother": {Attacks: []SRDAttack{ // Attack 18 → ~23 + {Name: "Thorned Lash", AttackBonus: 8, Damage: 12}, + {Name: "Thorned Lash", AttackBonus: 8, Damage: 11}, }}, - "boss_infernax": {Attacks: []SRDAttack{ - {Name: "Bite", AttackBonus: 11, Damage: 21}, - {Name: "Claw", AttackBonus: 11, Damage: 17}, - {Name: "Claw", AttackBonus: 11, Damage: 17}, + "boss_infernax": {Attacks: []SRDAttack{ // Attack 38 → ~49 + {Name: "Bite", AttackBonus: 11, Damage: 19}, + {Name: "Claw", AttackBonus: 11, Damage: 15}, + {Name: "Claw", AttackBonus: 11, Damage: 15}, }}, - "boss_belaxath": {Attacks: []SRDAttack{ - {Name: "Longsword", AttackBonus: 11, Damage: 21}, - {Name: "Whip", AttackBonus: 11, Damage: 15}, + "boss_belaxath": {Attacks: []SRDAttack{ // Attack 31 → ~40 + {Name: "Longsword", AttackBonus: 11, Damage: 24}, + {Name: "Whip", AttackBonus: 11, Damage: 16}, }}, // ── Multiattack elites ─────────────────────────────────────────────── - "hobgoblin_warchief": {Attacks: []SRDAttack{ - {Name: "Longsword", AttackBonus: 5, Damage: 8}, - {Name: "Longsword", AttackBonus: 5, Damage: 8}, + "hobgoblin_warchief": {Attacks: []SRDAttack{ // Attack 4 → ~5 + {Name: "Longsword", AttackBonus: 5, Damage: 3}, + {Name: "Longsword", AttackBonus: 5, Damage: 2}, }}, - "bandit_captain": {Attacks: []SRDAttack{ - {Name: "Scimitar", AttackBonus: 5, Damage: 6}, - {Name: "Scimitar", AttackBonus: 5, Damage: 6}, - {Name: "Dagger", AttackBonus: 5, Damage: 5}, + "bandit_captain": {Attacks: []SRDAttack{ // Attack 4 → ~6 + {Name: "Scimitar", AttackBonus: 5, Damage: 2}, + {Name: "Scimitar", AttackBonus: 5, Damage: 2}, + {Name: "Dagger", AttackBonus: 5, Damage: 2}, }}, - "owlbear": {Attacks: []SRDAttack{ - {Name: "Beak", AttackBonus: 7, Damage: 10}, - {Name: "Claws", AttackBonus: 7, Damage: 14}, + "owlbear": {Attacks: []SRDAttack{ // Attack 6 → ~8 + {Name: "Beak", AttackBonus: 7, Damage: 3}, + {Name: "Claws", AttackBonus: 7, Damage: 5}, }}, - "merrow": {Attacks: []SRDAttack{ - {Name: "Bite", AttackBonus: 5, Damage: 8}, - {Name: "Claws", AttackBonus: 5, Damage: 9}, + "merrow": {Attacks: []SRDAttack{ // Attack 4 → ~5 + {Name: "Bite", AttackBonus: 5, Damage: 3}, + {Name: "Claws", AttackBonus: 5, Damage: 2}, }}, - "helmed_horror": {Attacks: []SRDAttack{ - {Name: "Longsword", AttackBonus: 6, Damage: 8}, - {Name: "Longsword", AttackBonus: 6, Damage: 8}, + "helmed_horror": {Attacks: []SRDAttack{ // Attack 7 → ~9 + {Name: "Longsword", AttackBonus: 6, Damage: 5}, + {Name: "Longsword", AttackBonus: 6, Damage: 4}, }}, - "drow_elite_warrior": {Attacks: []SRDAttack{ - {Name: "Shortsword", AttackBonus: 7, Damage: 7}, - {Name: "Shortsword", AttackBonus: 7, Damage: 7}, - {Name: "Hand Crossbow", AttackBonus: 7, Damage: 6}, + "drow_elite_warrior": {Attacks: []SRDAttack{ // Attack 8 → ~10 + {Name: "Shortsword", AttackBonus: 7, Damage: 4}, + {Name: "Shortsword", AttackBonus: 7, Damage: 3}, + {Name: "Hand Crossbow", AttackBonus: 7, Damage: 3}, }}, - "salamander": {Attacks: []SRDAttack{ - {Name: "Spear", AttackBonus: 7, Damage: 10}, - {Name: "Tail", AttackBonus: 7, Damage: 10}, + "salamander": {Attacks: []SRDAttack{ // Attack 8 → ~10 + {Name: "Spear", AttackBonus: 7, Damage: 5}, + {Name: "Tail", AttackBonus: 7, Damage: 5}, }}, - "guard_drake": {Attacks: []SRDAttack{ - {Name: "Bite", AttackBonus: 5, Damage: 5}, - {Name: "Claws", AttackBonus: 5, Damage: 5}, + "guard_drake": {Attacks: []SRDAttack{ // Attack 4 → ~5 + {Name: "Bite", AttackBonus: 5, Damage: 3}, + {Name: "Claws", AttackBonus: 5, Damage: 2}, }}, - "young_red_dragon": {Attacks: []SRDAttack{ - {Name: "Bite", AttackBonus: 10, Damage: 17}, - {Name: "Claw", AttackBonus: 10, Damage: 13}, - {Name: "Claw", AttackBonus: 10, Damage: 13}, + "young_red_dragon": {Attacks: []SRDAttack{ // Attack 16 → ~20 + {Name: "Bite", AttackBonus: 10, Damage: 8}, + {Name: "Claw", AttackBonus: 10, Damage: 6}, + {Name: "Claw", AttackBonus: 10, Damage: 6}, }}, - "quickling": {Attacks: []SRDAttack{ - {Name: "Dagger", AttackBonus: 5, Damage: 7}, - {Name: "Dagger", AttackBonus: 5, Damage: 7}, - {Name: "Dagger", AttackBonus: 5, Damage: 7}, + "quickling": {Attacks: []SRDAttack{ // Attack 2 → ~3 + {Name: "Dagger", AttackBonus: 5, Damage: 1}, + {Name: "Dagger", AttackBonus: 5, Damage: 1}, + {Name: "Dagger", AttackBonus: 5, Damage: 1}, }}, - "vrock": {Attacks: []SRDAttack{ - {Name: "Beak", AttackBonus: 6, Damage: 10}, - {Name: "Talons", AttackBonus: 6, Damage: 14}, + "vrock": {Attacks: []SRDAttack{ // Attack 10 → ~13 + {Name: "Beak", AttackBonus: 6, Damage: 5}, + {Name: "Talons", AttackBonus: 6, Damage: 8}, }}, - "hezrou": {Attacks: []SRDAttack{ - {Name: "Bite", AttackBonus: 7, Damage: 15}, - {Name: "Claw", AttackBonus: 7, Damage: 11}, - {Name: "Claw", AttackBonus: 7, Damage: 11}, + "hezrou": {Attacks: []SRDAttack{ // Attack 13 → ~17 + {Name: "Bite", AttackBonus: 7, Damage: 7}, + {Name: "Claw", AttackBonus: 7, Damage: 5}, + {Name: "Claw", AttackBonus: 7, Damage: 5}, }}, - "nalfeshnee": {Attacks: []SRDAttack{ - {Name: "Bite", AttackBonus: 10, Damage: 32}, - {Name: "Claw", AttackBonus: 10, Damage: 15}, - {Name: "Claw", AttackBonus: 10, Damage: 15}, + "nalfeshnee": {Attacks: []SRDAttack{ // Attack 21 → ~28 + {Name: "Bite", AttackBonus: 10, Damage: 14}, + {Name: "Claw", AttackBonus: 10, Damage: 7}, + {Name: "Claw", AttackBonus: 10, Damage: 7}, }}, - "marilith": {Attacks: []SRDAttack{ - {Name: "Longsword", AttackBonus: 11, Damage: 13}, - {Name: "Longsword", AttackBonus: 11, Damage: 13}, - {Name: "Longsword", AttackBonus: 11, Damage: 13}, - {Name: "Longsword", AttackBonus: 11, Damage: 13}, - {Name: "Longsword", AttackBonus: 11, Damage: 13}, - {Name: "Longsword", AttackBonus: 11, Damage: 13}, - {Name: "Tail", AttackBonus: 11, Damage: 13}, + "marilith": {Attacks: []SRDAttack{ // Attack 26 → ~34 + {Name: "Longsword", AttackBonus: 11, Damage: 5}, + {Name: "Longsword", AttackBonus: 11, Damage: 5}, + {Name: "Longsword", AttackBonus: 11, Damage: 5}, + {Name: "Longsword", AttackBonus: 11, Damage: 5}, + {Name: "Longsword", AttackBonus: 11, Damage: 5}, + {Name: "Longsword", AttackBonus: 11, Damage: 5}, + {Name: "Tail", AttackBonus: 11, Damage: 4}, }}, - "vampire_spawn": {Attacks: []SRDAttack{ - {Name: "Unarmed Strike", AttackBonus: 6, Damage: 8}, - {Name: "Bite", AttackBonus: 6, Damage: 6}, + "vampire_spawn": {Attacks: []SRDAttack{ // Attack 8 → ~10 + {Name: "Unarmed Strike", AttackBonus: 6, Damage: 6}, + {Name: "Bite", AttackBonus: 6, Damage: 4}, }}, }