Adv 2.0 D&D Phase 10 SUB1: subclass selection system

Implements the identity layer of gogobee_subclass_system.md: 15 subclasses
(3 per class) selectable at L5 via !subclass.

- Schema: new subclass + last_subclass_respec_at columns on dnd_character.
- Registry (dnd_subclass.go): 15 entries with class, display, tagline, and
  TwinBee flavor line; helpers for prompt rendering and input resolution
  (number, id, or display-name; case/space/hyphen-insensitive).
- !subclass command: bare form prints prompt or current; !subclass <name>
  selects (free first time) or changes (500 euros + 30-day cooldown via
  separate timestamp from the full !respec wipe). Save-then-debit ordering
  mirrors !respec audit fix B.
- Level-up DM at L5+ now embeds the real selection prompt instead of the
  Phase 9 placeholder; suppressed once a subclass is set.
- Sheet shows the subclass below the class line, or nudges !subclass when
  unchosen at L5+.
- Existing !respec full-wipe also clears subclass + cooldown timestamp.

Mechanical effects of L5/7/10/15 subclass abilities deferred to SUB2/SUB3.
This commit is contained in:
prosolis
2026-05-08 09:18:52 -07:00
parent b53e516bf0
commit 1ee4276bcd
9 changed files with 758 additions and 9 deletions

View File

@@ -189,13 +189,18 @@ func buildLevelUpMessage(c *DnDCharacter, events []LevelUpEvent, flavor string)
}
}
// Subclass selection cue: design doc specs a prompt at L5. Mechanics
// arrive in a future phase; for now the level-up DM mentions it so the
// player isn't surprised when it lands.
for _, ev := range events {
if ev.NewLevel == 5 {
b.WriteString("\n🎯 _Subclass selection unlocks at L5 — coming in a future update._")
break
// Phase 10 — subclass selection cue. Fires when the player crosses L5
// without already having a subclass (rare path: a multi-level grant
// that crosses L5 keeps prompting until they pick). Existing-subclass
// cases stay quiet on later level-ups.
if c.Subclass == "" && c.Level >= dndSubclassMinLevel {
for _, ev := range events {
if ev.NewLevel >= dndSubclassMinLevel {
b.WriteString("\n🎯 ")
b.WriteString(renderSubclassPrompt(c.Class))
b.WriteString("\n")
break
}
}
}
return b.String()