adventure: validate race/class + clamp level in admin char-migrate

Code-review fixes on the stuck-adventurer minting path:
- AdminBuildConfirmedCharacter now runs race/class through parseRace/
  parseClass (same as !setup), so a typo or non-playable class errors
  instead of silently minting a 1-HP/AC-10/no-spell sheet; inputs are
  normalized too.
- Clamp level to dndMaxLevel to match the L20 cap enforced elsewhere.
- CLI parses/validates every spec before db.Init, so a malformed spec
  mid-batch no longer leaves earlier specs already committed.
This commit is contained in:
prosolis
2026-07-16 06:42:54 -07:00
parent d08a20a114
commit 85e5ba5fce
2 changed files with 35 additions and 4 deletions

View File

@@ -31,9 +31,25 @@ import (
// SaveDnDCharacter upserts on user_id, so an existing pending draft is replaced.
// initResources / ensureSpellsForCharacter are idempotent.
func AdminBuildConfirmedCharacter(userID id.UserID, race DnDRace, class DnDClass, level int) (*DnDCharacter, error) {
// Validate (and normalize) race/class through the same parsers !setup uses,
// so a typo or a non-playable class fails loudly instead of silently minting
// a broken sheet — an unknown class falls through classInfo to 1 HP / AC 10
// / no spells, which looks "built" but isn't.
r, ok := parseRace(string(race))
if !ok {
return nil, fmt.Errorf("unknown race %q", race)
}
cl, ok := parseClass(string(class))
if !ok {
return nil, fmt.Errorf("unknown or non-playable class %q", class)
}
race, class = r, cl
if level < 1 {
level = 1
}
if level > dndMaxLevel {
level = dndMaxLevel
}
scores := applyRaceMods(race, classStatPriority(class))
c := &DnDCharacter{
UserID: userID,