Adv 2.0 D&D Phase 10 SUB2-AT: Arcane Trickster spellcasting

Slots into the Phase 9 spell pipeline so an L5+ Rogue who picks Arcane
Trickster becomes a third-caster on the Mage list, INT-based.

  - isSpellcaster(c) gate: full casters by class plus AT Rogue at L5+.
    !cast / !spells now consult this; the cast class-gate also accepts
    Mage-tagged spells when the player is an AT Rogue.
  - subclassSpellSlots() / slotsForCharacter() / setSpellSlotsForCharacter()
    layer subclass slots on top of the class baseline. AT pool: 3 L1 at
    L5–6, 4 L1 + 2 L2 at L7+, +2 L3 at L13+ (third-caster table).
  - spellcastingMod returns INT for AT Rogue.
  - L7 Magical Ambush proxy: spellSaveDC bumps +2 once Rogue+AT hits L7,
    standing in for "targets have disadvantage when casting from hidden"
    since the one-shot combat layer doesn't model hidden state.
  - ensureSpellsForCharacter bootstraps a Mage-list starter spellbook
    (minor_illusion, shocking_grasp, magic_missile, shield, sleep at L5;
    mirror_image + misty_step at L7+; hypnotic_pattern at L13+) and
    refreshes the slot pool on level/subclass change.
  - applySubclassChoice runs ensureSpellsForCharacter for AT so the
    spellbook is provisioned at selection time (idempotent).
  - !cast clamps upcasts to highestAvailableSlotForChar when AT, since
    third-caster's slot ceiling lags behind the spell list.

Tests added: AT spellcaster gate (L5 cutoff, non-AT Rogue rejected), INT
mod, subclass slot pool by tier, L7 ambush DC bump, ensureSpells default
list (all on Mage list, slots correct), end-to-end !cast queues
magic_missile for AT Rogue, !cast rejects plain Thief Rogue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-08 10:32:34 -07:00
parent c8fac7ffd9
commit 4a5159ee14
4 changed files with 328 additions and 11 deletions

View File

@@ -124,6 +124,12 @@ func (p *AdventurePlugin) applySubclassChoice(
// the player still gets the subclass; resources can be re-initialized
// next long rest.
_ = initSubclassResources(ctx.Sender, chosen)
// Phase 10 SUB2-AT — Arcane Trickster bootstraps a Mage-list spellbook
// + third-caster slot pool on selection. Idempotent and non-fatal: the
// next `!cast`/`!spells` call also runs ensureSpellsForCharacter.
if chosen == SubclassArcaneTrickster {
_ = ensureSpellsForCharacter(c)
}
if charged {
// Debit last; race vs. balance change is rare and strictly safer
// to favor the player than risk taking euros without applying the