Combat: balance-tune SRD multiattack profiles for single-player

Slice 1 shipped raw SRD damage, which is calibrated for a 4-PC party
and one-shots gogobee's single player at every tier. Rescale each
profile's per-attack damage so the round-total lands at ~1.3x the
creature's tuned auto-resolve Attack stat, keeping the SRD structure
(attack count, to-hit, damage split) intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-14 07:44:01 -07:00
parent 96b291d831
commit fd28f31de5

View File

@@ -8,12 +8,26 @@ package plugin
// rebalance is calibrated against it. Do NOT feed these profiles into // rebalance is calibrated against it. Do NOT feed these profiles into
// auto-resolve. // auto-resolve.
// //
// The turn-based path can afford canonical SRD multiattack: the player issues a // The turn-based path can afford canonical SRD multiattack *structure*: the
// command per round, so a creature making three attack rolls a turn is a // player issues a command per round, so a creature making three attack rolls a
// tactical problem the player can answer (heal, control, flee) rather than an // turn is a tactical problem the player can answer (heal, control, flee) rather
// un-survivable spike. Each SRDAttack carries its own to-hit and average // than an un-survivable spike.
// damage, derived from the creature's SRD attack profile — not the tuned //
// auto-resolve Attack stat. // 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). // 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. // 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 // 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 // other creature falls through to the single-attack template path. Per-attack
// values are SRD attack-profile averages, rounded. // 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{ var srdProfiles = map[string]SRDProfile{
// ── Bosses ─────────────────────────────────────────────────────────── // ── Bosses ───────────────────────────────────────────────────────────
"boss_grol_unbroken": {Attacks: []SRDAttack{ "boss_grol_unbroken": {Attacks: []SRDAttack{ // Attack 6 → ~8
{Name: "Morningstar", AttackBonus: 5, Damage: 11}, {Name: "Morningstar", AttackBonus: 5, Damage: 5},
{Name: "Javelin", AttackBonus: 5, Damage: 9}, {Name: "Javelin", AttackBonus: 5, Damage: 3},
}}, }},
"boss_valdris_unburied": {Attacks: []SRDAttack{ "boss_valdris_unburied": {Attacks: []SRDAttack{ // Attack 8 → ~10
{Name: "Paralyzing Touch", AttackBonus: 7, Damage: 12}, {Name: "Paralyzing Touch", AttackBonus: 7, Damage: 6},
{Name: "Necrotic Bolt", AttackBonus: 7, Damage: 10}, {Name: "Necrotic Bolt", AttackBonus: 7, Damage: 4},
}}, }},
"boss_hollow_king": {Attacks: []SRDAttack{ "boss_hollow_king": {Attacks: []SRDAttack{ // Attack 10 → ~13
{Name: "Root Slam", AttackBonus: 7, Damage: 13}, {Name: "Root Slam", AttackBonus: 7, Damage: 7},
{Name: "Root Slam", AttackBonus: 7, Damage: 13}, {Name: "Root Slam", AttackBonus: 7, Damage: 6},
}}, }},
"boss_dreaming_aboleth": {Attacks: []SRDAttack{ "boss_dreaming_aboleth": {Attacks: []SRDAttack{ // Attack 16 → ~21
{Name: "Tentacle", AttackBonus: 9, Damage: 12}, {Name: "Tentacle", AttackBonus: 9, Damage: 7},
{Name: "Tentacle", AttackBonus: 9, Damage: 12}, {Name: "Tentacle", AttackBonus: 9, Damage: 7},
{Name: "Tentacle", AttackBonus: 9, Damage: 12}, {Name: "Tentacle", AttackBonus: 9, Damage: 7},
}}, }},
"boss_aldric_blackspire": {Attacks: []SRDAttack{ "boss_aldric_blackspire": {Attacks: []SRDAttack{ // Attack 21 → ~27
{Name: "Unarmed Strike", AttackBonus: 9, Damage: 10}, {Name: "Unarmed Strike", AttackBonus: 9, Damage: 14},
{Name: "Bite", AttackBonus: 9, Damage: 9}, {Name: "Bite", AttackBonus: 9, Damage: 13},
}}, }},
"boss_emberlord_thyrak": {Attacks: []SRDAttack{ "boss_emberlord_thyrak": {Attacks: []SRDAttack{ // Attack 14 → ~18
{Name: "Molten Maul", AttackBonus: 8, Damage: 15}, {Name: "Molten Maul", AttackBonus: 8, Damage: 9},
{Name: "Molten Maul", AttackBonus: 8, Damage: 15}, {Name: "Molten Maul", AttackBonus: 8, Damage: 9},
}}, }},
"boss_ilvaras_xunyl": {Attacks: []SRDAttack{ "boss_ilvaras_xunyl": {Attacks: []SRDAttack{ // Attack 19 → ~25
{Name: "Scourge", AttackBonus: 9, Damage: 14}, {Name: "Scourge", AttackBonus: 9, Damage: 13},
{Name: "Scourge", AttackBonus: 9, Damage: 14}, {Name: "Scourge", AttackBonus: 9, Damage: 12},
}}, }},
"boss_thornmother": {Attacks: []SRDAttack{ "boss_thornmother": {Attacks: []SRDAttack{ // Attack 18 → ~23
{Name: "Thorned Lash", AttackBonus: 8, Damage: 16}, {Name: "Thorned Lash", AttackBonus: 8, Damage: 12},
{Name: "Thorned Lash", AttackBonus: 8, Damage: 16}, {Name: "Thorned Lash", AttackBonus: 8, Damage: 11},
}}, }},
"boss_infernax": {Attacks: []SRDAttack{ "boss_infernax": {Attacks: []SRDAttack{ // Attack 38 → ~49
{Name: "Bite", AttackBonus: 11, Damage: 21}, {Name: "Bite", AttackBonus: 11, Damage: 19},
{Name: "Claw", AttackBonus: 11, Damage: 17}, {Name: "Claw", AttackBonus: 11, Damage: 15},
{Name: "Claw", AttackBonus: 11, Damage: 17}, {Name: "Claw", AttackBonus: 11, Damage: 15},
}}, }},
"boss_belaxath": {Attacks: []SRDAttack{ "boss_belaxath": {Attacks: []SRDAttack{ // Attack 31 → ~40
{Name: "Longsword", AttackBonus: 11, Damage: 21}, {Name: "Longsword", AttackBonus: 11, Damage: 24},
{Name: "Whip", AttackBonus: 11, Damage: 15}, {Name: "Whip", AttackBonus: 11, Damage: 16},
}}, }},
// ── Multiattack elites ─────────────────────────────────────────────── // ── Multiattack elites ───────────────────────────────────────────────
"hobgoblin_warchief": {Attacks: []SRDAttack{ "hobgoblin_warchief": {Attacks: []SRDAttack{ // Attack 4 → ~5
{Name: "Longsword", AttackBonus: 5, Damage: 8}, {Name: "Longsword", AttackBonus: 5, Damage: 3},
{Name: "Longsword", AttackBonus: 5, Damage: 8}, {Name: "Longsword", AttackBonus: 5, Damage: 2},
}}, }},
"bandit_captain": {Attacks: []SRDAttack{ "bandit_captain": {Attacks: []SRDAttack{ // Attack 4 → ~6
{Name: "Scimitar", AttackBonus: 5, Damage: 6}, {Name: "Scimitar", AttackBonus: 5, Damage: 2},
{Name: "Scimitar", AttackBonus: 5, Damage: 6}, {Name: "Scimitar", AttackBonus: 5, Damage: 2},
{Name: "Dagger", AttackBonus: 5, Damage: 5}, {Name: "Dagger", AttackBonus: 5, Damage: 2},
}}, }},
"owlbear": {Attacks: []SRDAttack{ "owlbear": {Attacks: []SRDAttack{ // Attack 6 → ~8
{Name: "Beak", AttackBonus: 7, Damage: 10}, {Name: "Beak", AttackBonus: 7, Damage: 3},
{Name: "Claws", AttackBonus: 7, Damage: 14}, {Name: "Claws", AttackBonus: 7, Damage: 5},
}}, }},
"merrow": {Attacks: []SRDAttack{ "merrow": {Attacks: []SRDAttack{ // Attack 4 → ~5
{Name: "Bite", AttackBonus: 5, Damage: 8}, {Name: "Bite", AttackBonus: 5, Damage: 3},
{Name: "Claws", AttackBonus: 5, Damage: 9}, {Name: "Claws", AttackBonus: 5, Damage: 2},
}}, }},
"helmed_horror": {Attacks: []SRDAttack{ "helmed_horror": {Attacks: []SRDAttack{ // Attack 7 → ~9
{Name: "Longsword", AttackBonus: 6, Damage: 8}, {Name: "Longsword", AttackBonus: 6, Damage: 5},
{Name: "Longsword", AttackBonus: 6, Damage: 8}, {Name: "Longsword", AttackBonus: 6, Damage: 4},
}}, }},
"drow_elite_warrior": {Attacks: []SRDAttack{ "drow_elite_warrior": {Attacks: []SRDAttack{ // Attack 8 → ~10
{Name: "Shortsword", AttackBonus: 7, Damage: 7}, {Name: "Shortsword", AttackBonus: 7, Damage: 4},
{Name: "Shortsword", AttackBonus: 7, Damage: 7}, {Name: "Shortsword", AttackBonus: 7, Damage: 3},
{Name: "Hand Crossbow", AttackBonus: 7, Damage: 6}, {Name: "Hand Crossbow", AttackBonus: 7, Damage: 3},
}}, }},
"salamander": {Attacks: []SRDAttack{ "salamander": {Attacks: []SRDAttack{ // Attack 8 → ~10
{Name: "Spear", AttackBonus: 7, Damage: 10}, {Name: "Spear", AttackBonus: 7, Damage: 5},
{Name: "Tail", AttackBonus: 7, Damage: 10}, {Name: "Tail", AttackBonus: 7, Damage: 5},
}}, }},
"guard_drake": {Attacks: []SRDAttack{ "guard_drake": {Attacks: []SRDAttack{ // Attack 4 → ~5
{Name: "Bite", AttackBonus: 5, Damage: 5}, {Name: "Bite", AttackBonus: 5, Damage: 3},
{Name: "Claws", AttackBonus: 5, Damage: 5}, {Name: "Claws", AttackBonus: 5, Damage: 2},
}}, }},
"young_red_dragon": {Attacks: []SRDAttack{ "young_red_dragon": {Attacks: []SRDAttack{ // Attack 16 → ~20
{Name: "Bite", AttackBonus: 10, Damage: 17}, {Name: "Bite", AttackBonus: 10, Damage: 8},
{Name: "Claw", AttackBonus: 10, Damage: 13}, {Name: "Claw", AttackBonus: 10, Damage: 6},
{Name: "Claw", AttackBonus: 10, Damage: 13}, {Name: "Claw", AttackBonus: 10, Damage: 6},
}}, }},
"quickling": {Attacks: []SRDAttack{ "quickling": {Attacks: []SRDAttack{ // Attack 2 → ~3
{Name: "Dagger", AttackBonus: 5, Damage: 7}, {Name: "Dagger", AttackBonus: 5, Damage: 1},
{Name: "Dagger", AttackBonus: 5, Damage: 7}, {Name: "Dagger", AttackBonus: 5, Damage: 1},
{Name: "Dagger", AttackBonus: 5, Damage: 7}, {Name: "Dagger", AttackBonus: 5, Damage: 1},
}}, }},
"vrock": {Attacks: []SRDAttack{ "vrock": {Attacks: []SRDAttack{ // Attack 10 → ~13
{Name: "Beak", AttackBonus: 6, Damage: 10}, {Name: "Beak", AttackBonus: 6, Damage: 5},
{Name: "Talons", AttackBonus: 6, Damage: 14}, {Name: "Talons", AttackBonus: 6, Damage: 8},
}}, }},
"hezrou": {Attacks: []SRDAttack{ "hezrou": {Attacks: []SRDAttack{ // Attack 13 → ~17
{Name: "Bite", AttackBonus: 7, Damage: 15}, {Name: "Bite", AttackBonus: 7, Damage: 7},
{Name: "Claw", AttackBonus: 7, Damage: 11}, {Name: "Claw", AttackBonus: 7, Damage: 5},
{Name: "Claw", AttackBonus: 7, Damage: 11}, {Name: "Claw", AttackBonus: 7, Damage: 5},
}}, }},
"nalfeshnee": {Attacks: []SRDAttack{ "nalfeshnee": {Attacks: []SRDAttack{ // Attack 21 → ~28
{Name: "Bite", AttackBonus: 10, Damage: 32}, {Name: "Bite", AttackBonus: 10, Damage: 14},
{Name: "Claw", AttackBonus: 10, Damage: 15}, {Name: "Claw", AttackBonus: 10, Damage: 7},
{Name: "Claw", AttackBonus: 10, Damage: 15}, {Name: "Claw", AttackBonus: 10, Damage: 7},
}}, }},
"marilith": {Attacks: []SRDAttack{ "marilith": {Attacks: []SRDAttack{ // Attack 26 → ~34
{Name: "Longsword", AttackBonus: 11, Damage: 13}, {Name: "Longsword", AttackBonus: 11, Damage: 5},
{Name: "Longsword", AttackBonus: 11, Damage: 13}, {Name: "Longsword", AttackBonus: 11, Damage: 5},
{Name: "Longsword", AttackBonus: 11, Damage: 13}, {Name: "Longsword", AttackBonus: 11, Damage: 5},
{Name: "Longsword", AttackBonus: 11, Damage: 13}, {Name: "Longsword", AttackBonus: 11, Damage: 5},
{Name: "Longsword", AttackBonus: 11, Damage: 13}, {Name: "Longsword", AttackBonus: 11, Damage: 5},
{Name: "Longsword", AttackBonus: 11, Damage: 13}, {Name: "Longsword", AttackBonus: 11, Damage: 5},
{Name: "Tail", AttackBonus: 11, Damage: 13}, {Name: "Tail", AttackBonus: 11, Damage: 4},
}}, }},
"vampire_spawn": {Attacks: []SRDAttack{ "vampire_spawn": {Attacks: []SRDAttack{ // Attack 8 → ~10
{Name: "Unarmed Strike", AttackBonus: 6, Damage: 8}, {Name: "Unarmed Strike", AttackBonus: 6, Damage: 6},
{Name: "Bite", AttackBonus: 6, Damage: 6}, {Name: "Bite", AttackBonus: 6, Damage: 4},
}}, }},
} }