Files
gogobee/internal/plugin/dnd_spells_data.go
prosolis 2ef0b25266 Phase A1: jargon-free flavor on all reachable spell Descriptions
The audit plan flagged jargon leaks in the SRD spell prose surfaced by
!cast and !spellbook. Verifying against HEAD turned up two corrections
to the plan's framing:

- The overlay in dnd_spells_data.go wins on ID collision, so for the
  ~76 hand-authored spells the *overlay* Descriptions are what players
  actually see — and those were crunchier than the SRD ("Heal 1d8 +
  WIS mod HP. Touch.", "8d6 fire in 20 ft radius. DEX save half.",
  etc.). Touching only the generated SRD file would have missed the
  bigger leak.
- The Upcast field is never displayed at runtime (only the import
  generator reads it), so testing it against the regex would test
  dead data.

So this commit:
- rewrites every overlay Description into playful, jargon-free flavor
  in the charm_person voice ("Sweet-talk a humanoid…") — no dice, no
  saves, no AC math, no ability-mod references
- adds 31 overlay shims for SRD-only spells whose generated text was
  broken (placeholder gaps from the Open5e classifier — "no larger
  than a.", "up to to", "in a in range"), empty ("You touch a
  creature."), typo-laden ("magicou", "undeadou", "diseasesou"), or
  jargon-heavy (false_life's "1d4+4 temporary hit points")
- adds TestSpellDescriptionsAreJargonFree, a regex sweep over the
  merged registry that bans "saving throw", "spell slot of",
  "ability modifier", "hit points equal to", and \bNdN\b dice
  notation. Currently passing; will fail-loud on regressions or new
  SRD imports that need overlay shims.

The generated dnd_spells_srd_data.go is left untouched — overlay is
the regen-safe surface per its own header comment.
2026-05-15 07:04:37 -07:00

522 lines
40 KiB
Go

package plugin
// Hand-authored spell overlay. Loaded after the Open5e SRD dump in
// buildSRDSpellList() and merged on top — overlay entries replace the SRD
// struct entirely (Classes is unioned). The original ~76 entries are tuned
// for Effect/DamageDice/Upcast; the 2026-05 pass rewrote every Description
// into playful, jargon-free flavor (no dice, no saves, no ability-mod math)
// and added overlay shims for the SRD-only spells whose generated text was
// broken, empty, or jargon-laden.
//
// Voice template: charm_person — "Sweet-talk a humanoid into treating you
// as their new favourite acquaintance. Wears off; they may notice."
//
// Mechanics still live in Effect/DamageDice/SaveStat/Concentration fields;
// the engine reads those, not the prose. The prose is what the player sees
// in !cast confirmations and the !spellbook list.
func buildSpellList() []SpellDefinition {
mage := []DnDClass{ClassMage}
cleric := []DnDClass{ClassCleric}
ranger := []DnDClass{ClassRanger}
mageCleric := []DnDClass{ClassMage, ClassCleric}
clericRanger := []DnDClass{ClassCleric, ClassRanger}
rangerCleric := []DnDClass{ClassRanger, ClassCleric}
allCasters := []DnDClass{ClassMage, ClassCleric, ClassRanger}
return []SpellDefinition{
// ── Cantrips (level 0) ────────────────────────────────────────────────
{ID: "fire_bolt", Name: "Fire Bolt", Level: 0, School: "evocation",
Classes: mage, Effect: EffectDamageAttack, CastTime: CastAction,
AttackRoll: true, DamageDice: "1d10", DamageType: "fire",
Description: "Flick a mote of fire at a foe. Small, hot, surprisingly rude.",
Upcast: "2d10 at L5, 3d10 at L11"},
{ID: "toll_the_dead", Name: "Toll the Dead", Level: 0, School: "necromancy",
Classes: mageCleric, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "WIS", DamageDice: "1d8", DamageType: "necrotic",
Description: "A funeral bell rings only the target can hear — and the wounded hear it louder."},
{ID: "chill_touch", Name: "Chill Touch", Level: 0, School: "necromancy",
Classes: mageCleric, Effect: EffectDamageAttack, CastTime: CastAction,
AttackRoll: true, DamageDice: "1d8", DamageType: "necrotic",
Description: "A spectral hand clutches a foe — bandages refuse to stick for a moment."},
{ID: "poison_spray", Name: "Poison Spray", Level: 0, School: "conjuration",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "CON", DamageDice: "1d12", DamageType: "poison",
Description: "A green puff from your palm. Hold your breath; they probably won't."},
{ID: "shocking_grasp", Name: "Shocking Grasp", Level: 0, School: "evocation",
Classes: mage, Effect: EffectDamageAttack, CastTime: CastAction,
AttackRoll: true, DamageDice: "1d8", DamageType: "lightning",
Description: "Lightning leaps from your touch. They flinch hard; their reaction is gone."},
{ID: "sacred_flame", Name: "Sacred Flame", Level: 0, School: "evocation",
Classes: cleric, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "DEX", DamageDice: "1d8", DamageType: "radiant",
Description: "A column of holy light drops on a foe. Cover does not help."},
{ID: "guidance", Name: "Guidance", Level: 0, School: "divination",
Classes: clericRanger, Effect: EffectUtility, CastTime: CastAction,
Concentration: true,
Description: "A faint helpful hum settles on an ally. Their next tricky task feels a touch less tricky."},
{ID: "shillelagh", Name: "Shillelagh", Level: 0, School: "transmutation",
Classes: rangerCleric, Effect: EffectBuffSelf, CastTime: CastBonusAction,
Concentration: true,
Description: "Your club or staff wakes up grumpy. Hits harder and listens to your wisdom now."},
{ID: "thorn_whip", Name: "Thorn Whip", Level: 0, School: "transmutation",
Classes: ranger, Effect: EffectDamageAttack, CastTime: CastAction,
AttackRoll: true, DamageDice: "1d6", DamageType: "piercing",
Description: "A vine of thorns lashes out, snags, and yanks a foe closer. Undignified."},
{ID: "mending", Name: "Mending", Level: 0, School: "transmutation",
Classes: allCasters, Effect: EffectUtility, CastTime: CastAction,
Description: "Knit a small break back together — a snapped key, a torn cloak, a sulking wineskin. Out of combat only."},
{ID: "message", Name: "Message", Level: 0, School: "transmutation",
Classes: mage, Effect: EffectUtility, CastTime: CastAction,
Description: "Whisper to someone across the room. They can whisper back. Nobody else hears a thing."},
{ID: "minor_illusion", Name: "Minor Illusion", Level: 0, School: "illusion",
Classes: mage, Effect: EffectUtility, CastTime: CastAction,
Description: "Conjure a quick sound or harmless image. Looks real until someone tries to touch it."},
// ── 1st level — Mage ──────────────────────────────────────────────────
{ID: "magic_missile", Name: "Magic Missile", Level: 1, School: "evocation",
Classes: mage, Effect: EffectDamageAuto, CastTime: CastAction,
DamageDice: "3d4+3", DamageType: "force",
Description: "Three darts of pure magic streak from your fingers. They never miss; they never tire of it.",
Upcast: "+1 dart per slot above 1st"},
{ID: "thunderwave", Name: "Thunderwave", Level: 1, School: "evocation",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "CON", DamageDice: "2d8", DamageType: "thunder", AOE: true,
Description: "A short, loud BOOM rolls out from you. Nearby foes get the message and stumble back.",
Upcast: "louder per slot above 1st"},
{ID: "mage_armor", Name: "Mage Armor", Level: 1, School: "abjuration",
Classes: mage, Effect: EffectBuffSelf, CastTime: CastAction,
Description: "Wrap yourself in an invisible glove of force. Hits glance off; movement stays free."},
{ID: "burning_hands", Name: "Burning Hands", Level: 1, School: "evocation",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "DEX", DamageDice: "3d6", DamageType: "fire", AOE: true,
Description: "Splay your fingers; a short cone of flame washes over the front rank.",
Upcast: "hotter per slot above 1st"},
{ID: "detect_magic", Name: "Detect Magic", Level: 1, School: "divination",
Classes: mage, Effect: EffectUtility, CastTime: CastRitual,
Concentration: true,
Description: "Your senses sharpen — anything enchanted nearby softly glows in your mind's eye. Ritual; no cost."},
{ID: "fog_cloud", Name: "Fog Cloud", Level: 1, School: "conjuration",
Classes: []DnDClass{ClassMage, ClassRanger}, Effect: EffectBuffSelf, CastTime: CastAction,
Concentration: true,
Description: "A bank of fog rolls in. Aim becomes a guessing game; everyone misses more."},
{ID: "grease", Name: "Grease", Level: 1, School: "conjuration",
Classes: mage, Effect: EffectControl, CastTime: CastAction,
SaveStat: "DEX", AOE: true,
Description: "A patch of greasy floor blooms underfoot. Walking is a comedy; running, a tragedy."},
{ID: "shield", Name: "Shield", Level: 1, School: "abjuration",
Classes: mage, Effect: EffectReaction, CastTime: CastReaction,
Description: "Throw up a slab of force in an eyeblink — the blow you were about to take skids off harmlessly."},
{ID: "sleep", Name: "Sleep", Level: 1, School: "enchantment",
Classes: mage, Effect: EffectControl, CastTime: CastAction,
AOE: true,
Description: "A drowsy fog rolls over nearby foes. The weakest drop where they stand and snore.",
Upcast: "more snoring per slot above 1st"},
{ID: "chromatic_orb", Name: "Chromatic Orb", Level: 1, School: "evocation",
Classes: mage, Effect: EffectDamageAttack, CastTime: CastAction,
AttackRoll: true, DamageDice: "3d8", DamageType: "varies",
Description: "A floating orb in the colour of your chosen element. Pick a flavour of pain and hurl.",
Upcast: "+1d8 per slot above 1st"},
// ── 1st level — Cleric ────────────────────────────────────────────────
{ID: "cure_wounds", Name: "Cure Wounds", Level: 1, School: "evocation",
Classes: clericRanger, Effect: EffectSpellHeal, CastTime: CastAction,
DamageDice: "1d8",
Description: "Lay a glowing hand on an ally; a stack of fresh wounds quietly close up. Bruises survive.",
Upcast: "stronger per slot above 1st"},
{ID: "bless", Name: "Bless", Level: 1, School: "enchantment",
Classes: cleric, Effect: EffectBuffAlly, CastTime: CastAction,
Concentration: true,
Description: "Drop a quiet blessing on up to three allies. They swing a little truer; their nerve holds."},
{ID: "inflict_wounds", Name: "Inflict Wounds", Level: 1, School: "necromancy",
Classes: cleric, Effect: EffectDamageAttack, CastTime: CastAction,
AttackRoll: true, DamageDice: "3d10", DamageType: "necrotic",
Description: "Press a withered palm to a foe and a deep, ugly wound opens under your touch.",
Upcast: "uglier per slot above 1st"},
{ID: "guiding_bolt", Name: "Guiding Bolt", Level: 1, School: "evocation",
Classes: cleric, Effect: EffectDamageAttack, CastTime: CastAction,
AttackRoll: true, DamageDice: "4d6", DamageType: "radiant",
Description: "A blazing javelin of light streaks home — and leaves the target glowing, easy to hit next.",
Upcast: "brighter per slot above 1st"},
{ID: "shield_of_faith", Name: "Shield of Faith", Level: 1, School: "abjuration",
Classes: cleric, Effect: EffectBuffAlly, CastTime: CastBonusAction,
Concentration: true,
Description: "A shimmering halo settles around an ally. Blades skid off it. Looks holy; works."},
// `healing_word_spell` was a hand-authored alias for `healing_word`
// that collided on display name with the SRD entry. Removed to make
// parseSpell("healing word") deterministic; Cleric default now points
// at the canonical SRD `healing_word` (cleric/druid/bard tagged).
{ID: "command", Name: "Command", Level: 1, School: "enchantment",
Classes: cleric, Effect: EffectControl, CastTime: CastAction,
SaveStat: "WIS",
Description: "Snap a single word at a foe — Halt! Drop! Flee! — and watch them obey before they've thought about it.",
Upcast: "+1 target per slot above 1st"},
{ID: "protection_from_evil", Name: "Protection from Evil and Good", Level: 1, School: "abjuration",
Classes: cleric, Effect: EffectBuffAlly, CastTime: CastAction,
Concentration: true,
Description: "Ward an ally against things that aren't supposed to be here — fiends, fey, the undead. They flinch from the touch."},
// ── 1st level — Ranger ────────────────────────────────────────────────
{ID: "hunters_mark", Name: "Hunter's Mark", Level: 1, School: "divination",
Classes: ranger, Effect: EffectBuffSelf, CastTime: CastBonusAction,
Concentration: true,
Description: "Tag a quarry. You'll feel where they are, and your shots will know where to land."},
{ID: "ensnaring_strike", Name: "Ensnaring Strike", Level: 1, School: "conjuration",
Classes: ranger, Effect: EffectControl, CastTime: CastBonusAction,
Concentration: true, SaveStat: "STR",
Description: "Your next hit sprouts thorny vines that wrap a foe in place. They writhe; the thorns bite."},
{ID: "speak_with_animals", Name: "Speak with Animals", Level: 1, School: "divination",
Classes: ranger, Effect: EffectUtility, CastTime: CastRitual,
Description: "For a while, animals make sense and you make sense to them. Most of what they say is about food."},
{ID: "absorb_elements", Name: "Absorb Elements", Level: 1, School: "abjuration",
Classes: ranger, Effect: EffectReaction, CastTime: CastReaction,
Description: "Catch the worst of an incoming blast of fire, frost, or lightning on your sleeve and shrug it off."},
// ── 2nd level — Mage ──────────────────────────────────────────────────
{ID: "scorching_ray", Name: "Scorching Ray", Level: 2, School: "evocation",
Classes: mage, Effect: EffectDamageAuto, CastTime: CastAction,
DamageDice: "6d6", DamageType: "fire",
Description: "Three darting jets of fire spit from your hand. Each one finds skin.",
Upcast: "+1 ray per slot above 2nd"},
{ID: "mirror_image", Name: "Mirror Image", Level: 2, School: "illusion",
Classes: mage, Effect: EffectBuffSelf, CastTime: CastAction,
Description: "Three flickering duplicates of you appear. Foes pick the wrong one; you grin from the right one."},
{ID: "misty_step", Name: "Misty Step", Level: 2, School: "conjuration",
Classes: mage, Effect: EffectBuffSelf, CastTime: CastBonusAction,
Description: "Vanish in a curl of silver mist and reappear a short hop away. Very dramatic; very useful."},
{ID: "hold_person", Name: "Hold Person", Level: 2, School: "enchantment",
Classes: mageCleric, Effect: EffectControl, CastTime: CastAction,
Concentration: true, SaveStat: "WIS",
Description: "A foe locks rigid mid-step. Anything you hit them with next lands devastatingly well.",
Upcast: "+1 target per slot above 2nd"},
{ID: "shatter", Name: "Shatter", Level: 2, School: "evocation",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "CON", DamageDice: "3d8", DamageType: "thunder", AOE: true,
Description: "An invisible chord rings, then snaps — everything brittle nearby cracks at once.",
Upcast: "louder per slot above 2nd"},
{ID: "blur", Name: "Blur", Level: 2, School: "illusion",
Classes: mage, Effect: EffectBuffSelf, CastTime: CastAction,
Concentration: true,
Description: "Your outline smears. Foes swing at where you almost are; they almost hit."},
{ID: "web", Name: "Web", Level: 2, School: "conjuration",
Classes: mage, Effect: EffectControl, CastTime: CastAction,
Concentration: true, SaveStat: "STR", AOE: true,
Description: "Sticky cobwebs bloom across the floor. Foes go in fast and come out slowly, swearing."},
{ID: "levitate", Name: "Levitate", Level: 2, School: "transmutation",
Classes: mage, Effect: EffectUtility, CastTime: CastAction,
Concentration: true, SaveStat: "CON",
Description: "Lift a target gently off the floor. They flail like a kicked cat; the ground rolls past."},
{ID: "knock", Name: "Knock", Level: 2, School: "transmutation",
Classes: mage, Effect: EffectUtility, CastTime: CastAction,
Description: "Any mundane lock — and a few proud magical ones — clicks open at a sharp word. Loudly. Sorry."},
// ── 2nd level — Cleric ────────────────────────────────────────────────
{ID: "spiritual_weapon", Name: "Spiritual Weapon", Level: 2, School: "evocation",
Classes: cleric, Effect: EffectBuffSelf, CastTime: CastBonusAction,
DamageDice: "1d8",
Description: "A glowing spectral mace floats up beside you and swings at foes whenever you fancy.",
Upcast: "+1d8 per 2 slots above 2nd"},
{ID: "lesser_restoration", Name: "Lesser Restoration", Level: 2, School: "abjuration",
Classes: clericRanger, Effect: EffectUtility, CastTime: CastAction,
Description: "Lift a single nasty condition from an ally — blinded, poisoned, paralysed, deaf. They breathe out."},
{ID: "prayer_of_healing", Name: "Prayer of Healing", Level: 2, School: "evocation",
Classes: cleric, Effect: EffectSpellHeal, CastTime: CastAction,
DamageDice: "2d8",
Description: "Murmur a prayer over a clustered party. Bruises fade; spirits lift. Out of combat only."},
{ID: "silence", Name: "Silence", Level: 2, School: "illusion",
Classes: clericRanger, Effect: EffectUtility, CastTime: CastAction,
Concentration: true,
Description: "A bubble of dead quiet drops over an area. No shouting, no chanting, no spells with words."},
{ID: "aid", Name: "Aid", Level: 2, School: "abjuration",
Classes: cleric, Effect: EffectBuffAlly, CastTime: CastAction,
Description: "Three allies stand a little taller, feel a little tougher, last a little longer. All day.",
Upcast: "more vigour per slot above 2nd"},
{ID: "augury", Name: "Augury", Level: 2, School: "divination",
Classes: cleric, Effect: EffectUtility, CastTime: CastRitual,
Description: "Ask the next half-hour a yes-or-no question. TwinBee relays the omen, mostly straight."},
// ── 2nd level — Ranger ────────────────────────────────────────────────
{ID: "pass_without_trace", Name: "Pass Without Trace", Level: 2, School: "abjuration",
Classes: ranger, Effect: EffectBuffSelf, CastTime: CastAction,
Concentration: true,
Description: "A veil of hush settles over the party. Footprints fade; you slip past sentries like a rumour."},
{ID: "spike_growth", Name: "Spike Growth", Level: 2, School: "transmutation",
Classes: ranger, Effect: EffectDamageAuto, CastTime: CastAction,
Concentration: true, DamageDice: "2d4", DamageType: "piercing", AOE: true,
Description: "The ground sprouts thorny barbs across a wide patch. Anyone crossing leaves a blood trail."},
{ID: "beast_sense", Name: "Beast Sense", Level: 2, School: "divination",
Classes: ranger, Effect: EffectUtility, CastTime: CastRitual,
Concentration: true,
Description: "Borrow a beast's eyes and ears for a while. You smell things you'd rather not have smelled."},
{ID: "cordon_of_arrows", Name: "Cordon of Arrows", Level: 2, School: "transmutation",
Classes: ranger, Effect: EffectBuffSelf, CastTime: CastAction,
Description: "Plant four arrows in the dirt; they wait. Anyone uninvited steps near and the arrows leap."},
{ID: "find_traps", Name: "Find Traps", Level: 2, School: "divination",
Classes: ranger, Effect: EffectUtility, CastTime: CastAction,
Description: "The traps in your sight start to look like traps. They've always been there; now you see them."},
// ── 3rd level — Mage ──────────────────────────────────────────────────
{ID: "fireball", Name: "Fireball", Level: 3, School: "evocation",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "DEX", DamageDice: "8d6", DamageType: "fire", AOE: true,
Description: "A bead of flame streaks out and blossoms into a roaring bonfire. Foes inside have a brief, terrible moment.",
Upcast: "hotter per slot above 3rd"},
{ID: "lightning_bolt", Name: "Lightning Bolt", Level: 3, School: "evocation",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "DEX", DamageDice: "8d6", DamageType: "lightning", AOE: true,
Description: "A line of furious lightning blasts straight through everything stupid enough to be standing in it.",
Upcast: "louder per slot above 3rd"},
{ID: "counterspell", Name: "Counterspell", Level: 3, School: "abjuration",
Classes: mage, Effect: EffectReaction, CastTime: CastReaction,
Description: "Catch another caster mid-incantation and pluck the spell out of the air. Polite smile optional."},
{ID: "fly", Name: "Fly", Level: 3, School: "transmutation",
Classes: mage, Effect: EffectBuffAlly, CastTime: CastAction,
Concentration: true,
Description: "An ally lifts off and zips around like an angry hummingbird. Try not to lose concentration up there."},
{ID: "hypnotic_pattern", Name: "Hypnotic Pattern", Level: 3, School: "illusion",
Classes: mage, Effect: EffectControl, CastTime: CastAction,
Concentration: true, SaveStat: "WIS", AOE: true,
Description: "A swirling kaleidoscope hangs in the air. Foes who look stop fighting and stare, mouths agape."},
{ID: "dispel_magic", Name: "Dispel Magic", Level: 3, School: "abjuration",
Classes: mageCleric, Effect: EffectUtility, CastTime: CastAction,
Description: "Snip the threads of one active spell on a target. The magic frays and falls away."},
{ID: "slow", Name: "Slow", Level: 3, School: "transmutation",
Classes: mage, Effect: EffectControl, CastTime: CastAction,
Concentration: true, SaveStat: "WIS", AOE: true,
Description: "Time thickens around your foes. They wade. They paddle. They are very late to the fight."},
{ID: "animate_dead", Name: "Animate Dead", Level: 3, School: "necromancy",
Classes: mageCleric, Effect: EffectUtility, CastTime: CastAction,
Description: "Three nearby corpses sit up and look at you for orders. Frowned upon at parties; useful in dungeons."},
// ── 3rd level — Cleric ────────────────────────────────────────────────
{ID: "spirit_guardians", Name: "Spirit Guardians", Level: 3, School: "conjuration",
Classes: cleric, Effect: EffectDamageSave, CastTime: CastAction,
Concentration: true, SaveStat: "WIS", DamageDice: "3d8",
DamageType: "radiant", AOE: true,
Description: "Ghostly guardians swarm around you, slashing at anything hostile that comes close.",
Upcast: "more guardians per slot above 3rd"},
{ID: "revivify", Name: "Revivify", Level: 3, School: "necromancy",
Classes: cleric, Effect: EffectUtility, CastTime: CastAction,
MaterialCost: 300,
Description: "Coax a recently-dead ally back across the threshold. Burns up a small, expensive diamond doing it."},
{ID: "mass_healing_word", Name: "Mass Healing Word", Level: 3, School: "evocation",
Classes: cleric, Effect: EffectSpellHeal, CastTime: CastBonusAction,
DamageDice: "1d4",
Description: "Bark a healing word across the battlefield. Up to six allies find themselves slightly less dead."},
{ID: "beacon_of_hope", Name: "Beacon of Hope", Level: 3, School: "abjuration",
Classes: cleric, Effect: EffectBuffAlly, CastTime: CastAction,
Concentration: true,
Description: "A warm glow settles over your party. Fear loosens; the dying cling on; healing lands at its kindest."},
{ID: "remove_curse", Name: "Remove Curse", Level: 3, School: "abjuration",
Classes: cleric, Effect: EffectUtility, CastTime: CastAction,
Description: "Lift a curse from a creature or object. The bad thing it was making them do quietly stops."},
// ── 3rd level — Ranger ────────────────────────────────────────────────
{ID: "lightning_arrow", Name: "Lightning Arrow", Level: 3, School: "transmutation",
Classes: ranger, Effect: EffectDamageSave, CastTime: CastBonusAction,
SaveStat: "DEX", DamageDice: "4d8", DamageType: "lightning", AOE: true,
Description: "Your next arrow turns into a forking bolt. It strikes; nearby foes flinch from the splash."},
{ID: "conjure_barrage", Name: "Conjure Barrage", Level: 3, School: "conjuration",
Classes: ranger, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "DEX", DamageDice: "3d8", DamageType: "varies", AOE: true,
Description: "Hurl one weapon; a phantom volley follows, raining down the same kind of pain across a cone."},
{ID: "water_walk", Name: "Water Walk", Level: 3, School: "transmutation",
Classes: ranger, Effect: EffectUtility, CastTime: CastRitual,
Description: "Up to ten allies stride across water, mud, snow, or worse as if it were a polite pavement."},
{ID: "plant_growth", Name: "Plant Growth", Level: 3, School: "transmutation",
Classes: ranger, Effect: EffectUtility, CastTime: CastAction,
Description: "Brambles and undergrowth thrash to life around you. The forest objects to anyone hurrying through."},
{ID: "nondetection", Name: "Nondetection", Level: 3, School: "abjuration",
Classes: ranger, Effect: EffectUtility, CastTime: CastAction,
Description: "A target falls off every scrying mirror in the realm. Whoever's watching frowns at static."},
// ── 4th level (Mage / Cleric) ─────────────────────────────────────────
{ID: "banishment", Name: "Banishment", Level: 4, School: "abjuration",
Classes: cleric, Effect: EffectControl, CastTime: CastAction,
Concentration: true, SaveStat: "CHA",
Description: "Yank a creature back where they belong. Demons hate this; the rest just look confused and gone."},
{ID: "polymorph", Name: "Polymorph", Level: 4, School: "transmutation",
Classes: mage, Effect: EffectControl, CastTime: CastAction,
Concentration: true, SaveStat: "WIS",
Description: "Turn a foe into a small, harmless beast. They still know who they are. They are furious about it."},
{ID: "ice_storm", Name: "Ice Storm", Level: 4, School: "evocation",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "DEX", DamageDice: "6d6", DamageType: "cold", AOE: true,
Description: "Hail the size of bricks crashes down across a wide area. The footing afterwards is a problem too."},
{ID: "wall_of_fire", Name: "Wall of Fire", Level: 4, School: "evocation",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
Concentration: true, SaveStat: "DEX", DamageDice: "5d8",
DamageType: "fire", AOE: true,
Description: "A long curtain of flame roars up from the ground. Anyone trying to cross regrets it loudly."},
{ID: "greater_invisibility", Name: "Greater Invisibility", Level: 4, School: "illusion",
Classes: mage, Effect: EffectBuffSelf, CastTime: CastAction,
Concentration: true,
Description: "An ally goes properly invisible — they can swing, cast, and shout, and stay unseen the whole time."},
{ID: "guardian_of_faith", Name: "Guardian of Faith", Level: 4, School: "conjuration",
Classes: cleric, Effect: EffectBuffSelf, CastTime: CastAction,
Description: "A towering spectral knight appears and stands its ground. Anything hostile approaching gets a smiting."},
{ID: "death_ward", Name: "Death Ward", Level: 4, School: "abjuration",
Classes: cleric, Effect: EffectBuffAlly, CastTime: CastAction,
Description: "Lay a single saved-life on an ally. The next mortal blow ends in a stagger, not a funeral."},
{ID: "freedom_of_movement", Name: "Freedom of Movement", Level: 4, School: "abjuration",
Classes: clericRanger, Effect: EffectBuffAlly, CastTime: CastAction,
Description: "An ally shrugs off mud, vines, manacles, and most of the laws of friction for an hour."},
// ── 5th level (Mage / Cleric) ─────────────────────────────────────────
{ID: "cone_of_cold", Name: "Cone of Cold", Level: 5, School: "evocation",
Classes: mage, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "CON", DamageDice: "8d8", DamageType: "cold", AOE: true,
Description: "A blast of arctic air rolls out from your palms. Frost on eyelashes; foes very, very cold.",
Upcast: "colder per slot above 5th"},
{ID: "hold_monster", Name: "Hold Monster", Level: 5, School: "enchantment",
Classes: mage, Effect: EffectControl, CastTime: CastAction,
Concentration: true, SaveStat: "WIS",
Description: "Lock any creature — beast, fiend, dragon — rigid in place. Their eyes still glare; that's all."},
{ID: "wall_of_force", Name: "Wall of Force", Level: 5, School: "evocation",
Classes: mage, Effect: EffectBuffSelf, CastTime: CastAction,
Concentration: true,
Description: "Drop a sheet of invisible, unbreakable force exactly where you want it. Nothing gets through."},
{ID: "scrying", Name: "Scrying", Level: 5, School: "divination",
Classes: mageCleric, Effect: EffectUtility, CastTime: CastAction,
Concentration: true, SaveStat: "WIS",
Description: "Peer through a quiet pool or mirror and watch a chosen target wherever they are. Long-distance gossip."},
{ID: "mass_cure_wounds", Name: "Mass Cure Wounds", Level: 5, School: "evocation",
Classes: cleric, Effect: EffectSpellHeal, CastTime: CastAction,
DamageDice: "3d8",
Description: "A wave of healing rolls out and washes up to six bleeding allies clean of their worst injuries."},
{ID: "flame_strike", Name: "Flame Strike", Level: 5, School: "evocation",
Classes: cleric, Effect: EffectDamageSave, CastTime: CastAction,
SaveStat: "DEX", DamageDice: "8d6", DamageType: "fire", AOE: true,
Description: "A column of divine fire roars down from above. Half flame, half holy fury, all very loud."},
{ID: "divine_word", Name: "Divine Word", Level: 5, School: "evocation",
Classes: cleric, Effect: EffectControl, CastTime: CastBonusAction,
SaveStat: "WIS",
Description: "Speak a word from the language of creation. The weak fall; the strong stagger; the worst things go elsewhere."},
{ID: "raise_dead", Name: "Raise Dead", Level: 5, School: "necromancy",
Classes: cleric, Effect: EffectUtility, CastTime: CastAction,
MaterialCost: 500,
Description: "Coax an ally back from up to ten days dead. They wake up tired and missing a diamond."},
// ── SRD-only Description cleanups ─────────────────────────────────────
// Shims that overlay SRD entries whose generated Description was
// broken, empty, typo-laden ("magicou"), or jargon-heavy. Fields
// duplicated from buildSRDSpellList() so the overlay replacement
// preserves Effect/CastTime/etc. Classes are unioned at merge time.
{ID: "false_life", Name: "False Life", Level: 1, School: "necromancy",
Classes: []DnDClass{ClassMage, ClassSorcerer}, Effect: EffectBuffSelf, CastTime: CastAction,
DamageDice: "1d4+4",
Description: "Wrap yourself in a thin shell of necromantic vigour. Hits land on the shell first; you'll thank yourself later.",
Upcast: "tougher shell per slot above 1st"},
{ID: "druidcraft", Name: "Druidcraft", Level: 0, School: "transmutation",
Classes: []DnDClass{ClassDruid}, Effect: EffectUtility, CastTime: CastAction, AOE: true,
Description: "Whisper to the spirits and they whisper back — usually about tomorrow's weather, sometimes a tiny gust, sometimes a sapling. Mostly harmless theatre."},
{ID: "light", Name: "Light", Level: 0, School: "evocation",
Classes: []DnDClass{ClassMage, ClassCleric, ClassBard, ClassSorcerer}, Effect: EffectUtility, CastTime: CastAction, AOE: true,
Description: "Tap an object and it glows like a friendly torch. Easy to carry, harder to lose."},
{ID: "resistance", Name: "Resistance", Level: 0, School: "abjuration",
Classes: []DnDClass{ClassCleric, ClassDruid}, Effect: EffectUtility, CastTime: CastAction,
Concentration: true,
Description: "A brief shimmer of luck settles on an ally. The next hard knock lands a little softer."},
{ID: "spare_the_dying", Name: "Spare the Dying", Level: 0, School: "necromancy",
Classes: []DnDClass{ClassCleric}, Effect: EffectUtility, CastTime: CastAction,
Description: "Lay hands on someone who's just dropped. They're stable now — not better, but not getting worse."},
{ID: "thaumaturgy", Name: "Thaumaturgy", Level: 0, School: "transmutation",
Classes: []DnDClass{ClassCleric}, Effect: EffectUtility, CastTime: CastAction,
Description: "Bark, boom, flicker, tremor — manifest a small showy miracle on cue. Crowds appreciate it; cats less so."},
{ID: "true_strike", Name: "True Strike", Level: 0, School: "divination",
Classes: []DnDClass{ClassMage, ClassBard, ClassSorcerer, ClassWarlock}, Effect: EffectBuffSelf, CastTime: CastAction,
Concentration: true,
Description: "Point at a foe and read their tells. Your next swing knows exactly where they'll flinch."},
{ID: "mage_hand", Name: "Mage Hand", Level: 0, School: "conjuration",
Classes: []DnDClass{ClassMage, ClassBard, ClassSorcerer, ClassWarlock}, Effect: EffectBuffSelf, CastTime: CastAction,
Description: "A spectral hand pops into being and does small chores for you — fetch, lift, poke. Mute, helpful, slightly creepy."},
{ID: "prestidigitation", Name: "Prestidigitation", Level: 0, School: "transmutation",
Classes: []DnDClass{ClassMage, ClassBard, ClassSorcerer, ClassWarlock}, Effect: EffectUtility, CastTime: CastAction,
Description: "A grab-bag of party tricks — small sparks, faint scents, a chilled drink, a tidied cloak. Showy and useless and brilliant."},
{ID: "bane", Name: "Bane", Level: 1, School: "enchantment",
Classes: []DnDClass{ClassCleric, ClassBard, ClassPaladin}, Effect: EffectUtility, CastTime: CastAction,
Concentration: true,
Description: "Up to three foes feel suddenly cursed — their swings wobble, their nerve wavers. Petty and effective.",
Upcast: "+1 target per slot above 1st"},
{ID: "comprehend_languages", Name: "Comprehend Languages", Level: 1, School: "divination",
Classes: []DnDClass{ClassMage, ClassBard, ClassSorcerer, ClassWarlock}, Effect: EffectUtility, CastTime: CastRitual,
Description: "For a while, every spoken language sounds like your own. Idioms and sarcasm still your problem."},
{ID: "create_or_destroy_water", Name: "Create or Destroy Water", Level: 1, School: "transmutation",
Classes: []DnDClass{ClassCleric, ClassDruid}, Effect: EffectUtility, CastTime: CastAction, AOE: true,
Description: "Conjure a small flood out of nowhere, or politely uncreate one. Useful for drinks, fires, and floors.",
Upcast: "more water per slot above 1st"},
{ID: "detect_evil_and_good", Name: "Detect Evil and Good", Level: 1, School: "divination",
Classes: []DnDClass{ClassCleric, ClassPaladin}, Effect: EffectUtility, CastTime: CastAction,
Concentration: true,
Description: "Sweep the area for things that don't belong on the Material Plane — fiends, fey, the undead, the worse. Locations included."},
{ID: "detect_poison_and_disease", Name: "Detect Poison and Disease", Level: 1, School: "divination",
Classes: []DnDClass{ClassCleric, ClassRanger, ClassDruid, ClassPaladin}, Effect: EffectUtility, CastTime: CastRitual,
Concentration: true,
Description: "Sniff out anything that wants to make you sick — venoms, plagues, the cook's questionable stew."},
{ID: "floating_disk", Name: "Floating Disk", Level: 1, School: "conjuration",
Classes: []DnDClass{ClassMage}, Effect: EffectUtility, CastTime: CastRitual,
Description: "Summon a hovering disc of force that follows you around. Pack the loot on top; it never tires, never complains."},
{ID: "goodberry", Name: "Goodberry", Level: 1, School: "transmutation",
Classes: []DnDClass{ClassRanger, ClassDruid}, Effect: EffectUtility, CastTime: CastAction,
Description: "A handful of magic berries appears. Each is a small meal and a small heal in one bite. Tastes faintly of summer."},
{ID: "jump", Name: "Jump", Level: 1, School: "transmutation",
Classes: []DnDClass{ClassMage, ClassRanger, ClassDruid, ClassSorcerer}, Effect: EffectUtility, CastTime: CastAction,
Description: "Touch an ally and their next leap clears walls, fences, and most regrets."},
{ID: "longstrider", Name: "Longstrider", Level: 1, School: "transmutation",
Classes: []DnDClass{ClassMage, ClassRanger, ClassDruid, ClassBard}, Effect: EffectUtility, CastTime: CastAction,
Description: "An ally's stride lengthens. They cover more ground all day and barely notice.",
Upcast: "+1 target per slot above 1st"},
{ID: "purify_food_and_drink", Name: "Purify Food and Drink", Level: 1, School: "transmutation",
Classes: []DnDClass{ClassCleric, ClassDruid, ClassPaladin}, Effect: EffectUtility, CastTime: CastRitual,
Description: "Whatever's on the table is suddenly safe to eat. Poisons gone, plagues gone, flavour mostly intact."},
{ID: "sanctuary", Name: "Sanctuary", Level: 1, School: "abjuration",
Classes: []DnDClass{ClassCleric, ClassPaladin}, Effect: EffectUtility, CastTime: CastBonusAction,
Description: "Mark an ally with a quiet ward. Foes who try to swing at them think twice — and usually pick someone else."},
{ID: "silent_image", Name: "Silent Image", Level: 1, School: "illusion",
Classes: []DnDClass{ClassMage, ClassBard, ClassSorcerer}, Effect: EffectBuffSelf, CastTime: CastAction,
Concentration: true, AOE: true,
Description: "Project a silent illusion you can shift around at will. Looks real until someone tries to touch it."},
{ID: "unseen_servant", Name: "Unseen Servant", Level: 1, School: "conjuration",
Classes: []DnDClass{ClassMage, ClassBard, ClassWarlock}, Effect: EffectUtility, CastTime: CastRitual,
Description: "An invisible little helper appears at your elbow — fetches, carries, opens doors. Mute, tireless, suggestible."},
{ID: "barkskin", Name: "Barkskin", Level: 2, School: "transmutation",
Classes: []DnDClass{ClassCleric, ClassRanger, ClassDruid}, Effect: EffectUtility, CastTime: CastAction,
Concentration: true,
Description: "An ally's skin roughens into bark. Less expressive; much harder to dent."},
{ID: "enthrall", Name: "Enthrall", Level: 2, School: "enchantment",
Classes: []DnDClass{ClassBard, ClassWarlock}, Effect: EffectUtility, CastTime: CastAction,
Description: "Spin a rapid-fire patter so distracting that listeners lose track of everything else around them."},
{ID: "gentle_repose", Name: "Gentle Repose", Level: 2, School: "necromancy",
Classes: []DnDClass{ClassMage, ClassCleric}, Effect: EffectUtility, CastTime: CastRitual,
Description: "Hold time off a corpse for a while. Rot pauses; resurrection windows stay open."},
{ID: "protection_from_poison", Name: "Protection from Poison", Level: 2, School: "abjuration",
Classes: []DnDClass{ClassCleric, ClassRanger, ClassDruid, ClassPaladin}, Effect: EffectUtility, CastTime: CastAction,
Description: "Drain the poison from an ally and ward them against the next dose. Briefly venom-proof."},
{ID: "magic_weapon", Name: "Magic Weapon", Level: 2, School: "transmutation",
Classes: []DnDClass{ClassMage, ClassCleric, ClassPaladin}, Effect: EffectBuffAlly, CastTime: CastBonusAction,
Concentration: true,
Description: "A plain weapon hums with a faint magical edge. Hits land truer; ghosts stop being a problem."},
{ID: "major_image", Name: "Major Image", Level: 3, School: "illusion",
Classes: []DnDClass{ClassMage, ClassBard, ClassSorcerer, ClassWarlock}, Effect: EffectBuffSelf, CastTime: CastAction,
Concentration: true, DamageType: "thunder", AOE: true,
Description: "Spin up a full-sensory illusion — sound, smell, the lot. Move it around like a puppeteer."},
{ID: "tiny_hut", Name: "Tiny Hut", Level: 3, School: "evocation",
Classes: []DnDClass{ClassMage, ClassBard}, Effect: EffectUtility, CastTime: CastRitual, AOE: true,
Description: "Drop an invisible dome around the party for the night. Weather stays out; sleep stays in."},
{ID: "compulsion", Name: "Compulsion", Level: 4, School: "enchantment",
Classes: []DnDClass{ClassBard}, Effect: EffectControl, CastTime: CastAction,
Concentration: true, SaveStat: "WIS",
Description: "Foes who can hear you find themselves walking the direction you point. They're not happy about it."},
{ID: "hallucinatory_terrain", Name: "Hallucinatory Terrain", Level: 4, School: "illusion",
Classes: []DnDClass{ClassMage, ClassDruid, ClassBard, ClassWarlock}, Effect: EffectUtility, CastTime: CastAction, AOE: true,
Description: "Reskin a whole patch of landscape. A meadow becomes a swamp; a cliff becomes a road. Disorienting."},
{ID: "stone_shape", Name: "Stone Shape", Level: 4, School: "transmutation",
Classes: []DnDClass{ClassMage, ClassCleric, ClassDruid}, Effect: EffectUtility, CastTime: CastAction,
Description: "Mould a chunk of stone like wet clay. Doorways appear; walls grow handles; the dwarves disapprove."},
{ID: "antilife_shell", Name: "Antilife Shell", Level: 5, School: "abjuration",
Classes: []DnDClass{ClassDruid}, Effect: EffectUtility, CastTime: CastAction,
Concentration: true, AOE: true,
Description: "A shimmering bubble travels with you. Living things bounce off it; the undead saunter through."},
}
}