feat(editor): add WSPV (Spell Variant) — 113th open format

Novel replacement for the implicit context-conditional
spell substitution rules vanilla WoW encoded across
SpellSpecificType, SpellEffect.EffectMechanic override
fields, and the proc-modified spell tables in
SpellProcEvent. Each entry binds one base spell to a
variant spell that activates when a runtime condition
is met (player in a specific stance, talent talented,
racial buff active, weapon equipped, aura present).

Six conditionKind values cover the full substitution
surface: Stance / Form / Talent / Race / EquippedWeapon
/ AuraActive. The conditionValue field is polymorphic —
its semantics depend on conditionKind (a stance spellId,
a talentId, a race bit, etc.). The spell-cast pipeline
iterates findByBaseSpell at cast time and picks the
highest-priority variant whose condition is satisfied,
falling through to the base spell if none matches.

Three preset emitters demonstrating the pattern:
makeWarriorStance (4 stance-conditional Warrior
variants — Heroic Strike Berserker damage bonus,
Battle baseline, Mocking Blow Defensive AoE taunt,
Pummel Berserker-only gate), makeTalentMod (4 talent-
modified variants — Frostbolt + Brain Freeze instant,
Lava Burst + Flame Shock auto-crit, Earth Shield +
Improved bonus heal, Ferocious Bite + Berserk),
makeRacial (4 race-gated racials — Stoneform Dwarf,
War Stomp Tauren, Berserking Troll, Will of the
Forsaken).

Validator's most novel check is the (baseSpell,
conditionKind, conditionValue, priority) 4-tuple
uniqueness — two variants with all four matching
would tie at runtime and resolve non-deterministically
(the spell-cast pipeline's std::sort by priority is
stable but the underlying iteration order is undefined
when priorities tie). Packs the tuple into 64 bits
(base 32 | value 16 | kind 8 | prio 8) for set lookup.

Format count 112 -> 113. CLI flag count 1213 -> 1218.
This commit is contained in:
Kelsi 2026-05-10 02:20:19 -07:00
parent 81eb854709
commit 6403d84a28
10 changed files with 725 additions and 0 deletions

View file

@ -2335,6 +2335,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wmvc to a human-editable JSON sidecar (defaults to <base>.wmvc.json; emits category as both int AND name string; lines[] as JSON string array)\n");
std::printf(" --import-wmvc-json <json-path> [out-base]\n");
std::printf(" Import a .wmvc.json sidecar back into binary .wmvc (category int OR \"production\"/\"music\"/\"audio\"/\"engineering\"/\"art\"/\"voice\"/\"special\"; lines[] is a JSON array of strings — directly editable to add/remove credit lines without binary tooling)\n");
std::printf(" --gen-spv <wspv-base> [name]\n");
std::printf(" Emit .wspv 4 stance-conditional Warrior spell variants (Heroic Strike Berserker / Heroic Strike Battle / Mocking Blow Defensive / Pummel Berserker)\n");
std::printf(" --gen-spv-talent <wspv-base> [name]\n");
std::printf(" Emit .wspv 4 talent-modified spell variants (Frostbolt + Brain Freeze instant / Lava Burst + Flame Shock auto-crit / Earth Shield + Improved bonus heal / Ferocious Bite + Berserk)\n");
std::printf(" --gen-spv-racial <wspv-base> [name]\n");
std::printf(" Emit .wspv 4 racial spell variants (Stoneform Dwarf / War Stomp Tauren / Berserking Troll / Will of the Forsaken)\n");
std::printf(" --info-wspv <wspv-base> [--json]\n");
std::printf(" Print WSPV entries (id / baseSpellId / variantSpellId / conditionKind / conditionValue / priority / name)\n");
std::printf(" --validate-wspv <wspv-base> [--json]\n");
std::printf(" Static checks: id+name+baseSpellId+variantSpellId required, conditionKind 0..5, no duplicate variantIds, no two variants binding the same (baseSpell, conditionKind, conditionValue, priority) tuple (would tie at runtime and resolve non-deterministically); warns on conditionValue=0 (always-zero default match — gate becomes no-op)\n");
std::printf(" --catalog-pluck <wXXX-file> <id> [--json]\n");
std::printf(" Extract one entry by id from any registered catalog format. Auto-detects magic, dispatches to the per-format --info-* handler internally, then prints just the matching entry. Primary-key field is auto-detected (first *Id field, or first numeric)\n");
std::printf(" --catalog-find <directory> <id> [--magic <WXXX>] [--json]\n");