Plumbed through the open5e importer so regen stays safe:
- New cmd/open5e-import/desc_overrides.go holds two per-ID override
maps (spellDescOverride, magicItemDescOverride) and a regex-driven
cleanDesc sanitizer. Override wins outright; otherwise the SRD
first-sentence runs through cleanDesc, which strips the phrases
the S3 acceptance criteria forbid (saving throw[s], spell slot,
within range, 5-foot, DC <n>, "(save DC X)" parentheticals,
"constitution score is N", "out to a range of N feet"). A small
post-pass repairs the orphan stubs the strippers leave behind
(" and." trailers, "must make." after the saving-throw object
is gone).
- gen.go (spells) + magicitems.go now call spellDescription /
magicItemDescription instead of raw firstSentence; same hand-
authored override pattern, same cleanDesc fallthrough.
- Override coverage: the 19 SRD-only spells that show up in
defaultKnownSpells (call_lightning, charm_person, vicious_mockery,
…) plus ~35 high-visibility magic items (Amulet of Health, every
Belt of Giant Strength variant, Cloak of Displacement, etc.).
Tone is outcome-first second-person with bite — these surface
in the spellbook and the curio shop, so they get to be funny.
- tuned.go (R21) + magicitems.go strip "(...)" from emitted Names
via stripNameParenthetical. Slug keeps the variant; only the
display text loses the qualifier. Two bestiary entries
(giant_rat_diseased, deep_gnome_svirfneblin) and stone_of_good_luck
affected.
- Regenerated all three data files. Acceptance grep is clean:
zero hits for any banned phrase in Description/Desc fields.
- New cmd/open5e-import/desc_overrides_test.go covers the
sanitizer regressions, orphan-repair, name-strip, and the
override-wins-but-fallthrough-still-sanitizes path.
Conflicts: none. S4 (magic-item UX) wanted this in first so the
new curio renderer consumes clean text — ready for it now.
The tuned bestiary previously left every generated entry with a nil
Ability. abilityFromTraits now classifies each creature's SRD trait
names against a priority-ordered rule table, mapping the most
combat-defining trait onto a MonsterAbility effect (death_aoe,
regenerate, spell_resist, evade, enrage, ...). Creatures whose traits
are all non-combat stay nil. 165 of 322 entries get an ability.
fetch/gen magicitems subcommands vendor data/open5e/magicitems.json (237
SRD items) and classify them into a generated registry. magic_items.go
holds the MagicItem struct + Kind enum + an init-time overlay merge where
a hand-authored entry wins on ID collision, mirroring the spell and
bestiary imports. Not yet wired into zone loot or the shop — that
integration is a deliberate follow-up.
Adds `gen tuned` to cmd/open5e-import: a deterministic formula that scales
every raw SRD stat block down to an engine-ready DnDMonsterTemplate. HP/AC/
AttackBonus are verbatim SRD (AC clamped to the engine min 10); the Attack
stat is interpolated from attackByCRPoints, a CR→Attack anchor table lifted
from the hand-tuned dndBestiary (CR is the calibration axis the 2026-05-10
rebalance used — raw SRD per-hit damage is ignored). Speed/BlockRate are
coarse baselines from SpeedWalk/AC.
bestiary_tuned.go merges the 322 generated templates into dndBestiary, but
hand-authored roster entries win — the merge only fills IDs the roster does
not already define, so playtested numbers and wired abilities are untouched.
Abilities are deliberately not wired: every generated entry has a nil
Ability, with the SRD multiattack/trait text parked in Notes as raw material
for the follow-up ability-wiring pass.
fetch|gen bestiary subcommands vendor data/open5e/monsters.json (322
SRD monsters) and generate bestiary_srd_data.go — all 322 as raw SRD
stat blocks (HP/AC/ability scores/CR + per-attack damage dice).
This is a balance-baseline reference, not an engine roster: raw SRD
damage one-shots the solo player, so nothing here feeds combat. It's
what the future tuning pass reads against when deriving dndBestiary /
srdProfiles entries. XP is derived from CR (Open5e has no XP field).
Rebuilds the cmd/open5e-import CLI (fetch/gen × spells) to vendor
data/open5e/spells.json (319 SRD spells) and generate
dnd_spells_srd_data.go (237 after the level>5 filter). mapClasses unions
the API's incomplete structured spell_lists field with the complete
free-text dnd_class field so all eight casters get spells.
dndSpellRegistry loads buildSRDSpellList() first; the hand-authored
buildSpellList() overlays it (hand wins on ID collision). Playable=true
flipped for Druid/Bard/Sorcerer/Warlock/Paladin, each with a
defaultKnownSpells case. TestDefaultKnownSpellsExistInRegistry now covers
all eight classes.
.gitignore: vendor data/open5e/ while keeping the rest of data/ ignored,
and anchor the open5e-import binary pattern so it stops swallowing the
cmd/open5e-import source dir. NOTICE adds CC-BY-4.0 / SRD attribution.