Adv 2.0 D&D Phase 10 SUB2a-ii: Battle Master + Assassin L5/L7

Battle Master:
  - New "superiority" resource pool (4/long-rest at L5), provisioned via
    initSubclassResources at !subclass selection.
  - Three armed maneuvers fueled by superiority dice:
    * Precision Attack — +d8 (≈+4) to first attack roll
    * Tripping Attack  — enemy skips first attack (reuses Phase 9
      SpellEnemySkipFirst)
    * Rally            — +(d8 + CHA) HealItem at <50% HP
  - L7 Know Your Enemy proxied as +1 AttackBonus passive.

Assassin:
  - L5 Assassinate: AssassinateAdvantage (re-roll first miss, take better
    of two d20s) + AssassinateBonusDmg = level (5 at L5) stacked on top
    of the Rogue's existing Sneak Attack auto-crit.
  - L7 Impostor bumps the bonus damage by +3.
  - L5 Infiltration Expertise → +5 Deception; L7 Impostor → +10.

Engine:
  - CombatModifiers gains FirstAttackBonus, AssassinateAdvantage,
    AssassinateBonusDmg.
  - resolvePlayerAttack consumes each on the first attack only via new
    combatState one-shot flags.

Tests added: 14 covering passive gating (BM L7+, Assassin L5/L7),
maneuver Apply flags, superiority pool init for BM only, !arm gating
across class/subclass for precision_attack, Deception bonus tiers,
statistical lift from FirstAttackBonus and AssassinateBonusDmg in
SimulateCombat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-08 10:15:57 -07:00
parent 437460c9b2
commit c8fac7ffd9
6 changed files with 464 additions and 0 deletions

View File

@@ -22,6 +22,27 @@ func applySubclassPassives(stats *CombatStats, mods *CombatModifiers, c *DnDChar
if c.Level >= 5 {
mods.CritThreshold = 19
}
case SubclassBattleMaster:
// L7 Know Your Enemy: 5e gives factual info about the target after
// 1 min observation. We don't model "observation time" — proxy the
// tactical edge as a flat +1 to attack rolls from L7 onward.
if c.Level >= 7 {
stats.AttackBonus++
}
case SubclassAssassin:
// L5 Assassinate: advantage on the opening strike + bonus damage
// stacked on top of the Rogue's existing Sneak Attack auto-crit
// (proxy for "crits vs. surprised targets"). Bonus scales with
// level; L7 Impostor — flavored as deeper study of the mark — adds
// a small surprise-damage bump.
if c.Level >= 5 {
mods.AssassinateAdvantage = true
bonus := c.Level
if c.Level >= 7 {
bonus += 3
}
mods.AssassinateBonusDmg = bonus
}
}
}
@@ -56,6 +77,52 @@ func init() {
mods.FrenzyDmgBonus = 0.5
},
}
// Phase 10 SUB2a-ii — Battle Master superiority-die maneuvers. Each
// consumes one die from the new "superiority" resource pool (4/long-rest
// at L5; long-rest refresh in our model — 5e refreshes on short rest).
// We don't model 5e's interactive "before/after the roll" choice; a die
// commits at !arm time and fires on the next combat.
//
// Three non-reaction maneuvers covered:
// - Precision Attack → +d8 (≈+4 flat) on first attack roll
// - Tripping Attack → enemy skips its first attack (reuses Phase 9
// SpellEnemySkipFirst)
// - Rally → temp-HP buffer at <50% (reuses HealItem; self
// buff since one-shot combat has no ally-target UI)
dndActiveAbilities["precision_attack"] = DnDAbility{
ID: "precision_attack",
Name: "Precision Attack",
Class: ClassFighter,
Subclass: SubclassBattleMaster,
Resource: "superiority",
Description: "Add +d8 (≈+4) to your first attack roll this combat (consumes 1 superiority die).",
Apply: func(c *DnDCharacter, mods *CombatModifiers) {
mods.FirstAttackBonus += 4
},
}
dndActiveAbilities["trip_attack"] = DnDAbility{
ID: "trip_attack",
Name: "Tripping Attack",
Class: ClassFighter,
Subclass: SubclassBattleMaster,
Resource: "superiority",
Description: "Trip the enemy on engagement: they skip their first attack (consumes 1 superiority die).",
Apply: func(c *DnDCharacter, mods *CombatModifiers) {
mods.SpellEnemySkipFirst = true
},
}
dndActiveAbilities["rally"] = DnDAbility{
ID: "rally",
Name: "Rally",
Class: ClassFighter,
Subclass: SubclassBattleMaster,
Resource: "superiority",
Description: "Steel yourself for the fight: heal at low HP for d8 + CHA mod (consumes 1 superiority die).",
Apply: func(c *DnDCharacter, mods *CombatModifiers) {
mods.HealItem += 4 + abilityModifier(c.CHA)
},
}
}
// persistDnDPostCombatSubclass handles end-of-combat subclass bookkeeping.