feat(editor): add WSTM (Stat Modifier Curve) open catalog format

Open replacement for the gtChanceTo*.dbc / gtRegen*.dbc /
gtCombatRatings.dbc family of "1D level-keyed curve" tables.
Each entry defines a single linear curve mapping character level
to a stat value: melee crit chance per level, mana regen per
spirit per level, base armor per level, etc.

Curves are linear: value(level) = baseValue + perLevelDelta *
(level - 1), with the result optionally scaled by a global
multiplier and clamped to a level range. Most stock WoW curves
fit this shape — the few that don't (cubic Combat Ratings) live
in the dedicated WCRR catalog with spline support.

Distinct from WCRR (Combat Rating conversion, integer ratings ->
percentages) and WSPC (Spell Power Cost buckets, per-spell
costs). WSTM is for the generic engine-side stat curves that
aren't per-spell or per-rating.

Seven curveKind values classify the major stat families (Crit /
Hit / Power / Regen / Resist / Mitigation / Misc), and each
curve carries its own [minLevel, maxLevel] applicability range
plus a multiplier for global scaling without retuning each
curve's slope.

Three preset emitters: --gen-stm (5 crit-related curves with
canonical 3.3.5a base+per-level scaling — MeleeCrit 5%+0.05/lvl
resolves to 8.95% at lvl 80), --gen-stm-regen (4 regen curves
including ManaPerSpirit and the Vanilla-era 3 rage/sec OOC
decay), --gen-stm-armor (3 armor/mitigation/resistance curves).

The info renderer demos resolveAtLevel(curveId, 80) inline as
the @lvl80 column — server admins can sanity-check what each
curve resolves to at character cap without writing test code.

Validation enforces id+name presence, curveKind 0..6,
minLevel<=maxLevel, no duplicate ids; warns on:
  - maxLevel > 80 (unreachable at WotLK cap)
  - multiplier=0 (curve always evaluates to 0)
  - multiplier<0 (inverts the curve — possibly intentional)
  - perLevelDelta<0 (curve shrinks with level — unusual)

Wired through the cross-format table; WSTM appears in all 18
cross-format utilities. Format count 93 -> 94; CLI flag count
1076 -> 1081.
This commit is contained in:
Kelsi 2026-05-10 00:05:07 -07:00
parent 68c20bd152
commit 9a85cc029e
10 changed files with 646 additions and 0 deletions

View file

@ -2069,6 +2069,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wstc to a human-editable JSON sidecar (defaults to <base>.wstc.json)\n");
std::printf(" --import-wstc-json <json-path> [out-base]\n");
std::printf(" Import a .wstc.json sidecar back into binary .wstc (isPremium accepts bool OR int)\n");
std::printf(" --gen-stm <wstm-base> [name]\n");
std::printf(" Emit .wstm 5 crit curves (MeleeCrit / RangedCrit / SpellCrit / Parry / Dodge) with canonical 3.3.5a base+per-level scaling\n");
std::printf(" --gen-stm-regen <wstm-base> [name]\n");
std::printf(" Emit .wstm 4 regen curves (ManaPerSpirit / HpPerSpirit / EnergyPerSec / RageDecay) with stock formulas\n");
std::printf(" --gen-stm-armor <wstm-base> [name]\n");
std::printf(" Emit .wstm 3 armor/mitigation curves (BaseArmorPerLevel / ArmorMitigationPct / ResistancePerLevel)\n");
std::printf(" --info-wstm <wstm-base> [--json]\n");
std::printf(" Print WSTM entries (id / kind / minLvl / maxLvl / baseValue / perLevelDelta / multiplier / value@lvl80 sample / name)\n");
std::printf(" --validate-wstm <wstm-base> [--json]\n");
std::printf(" Static checks: id+name required, curveKind 0..6, minLevel<=maxLevel, no duplicate ids; warns on maxLevel>80, multiplier=0 (always 0), multiplier<0 (inverts), perLevelDelta<0 (shrinks)\n");
std::printf(" --gen-weather-temperate <wow-base> [zoneName]\n");
std::printf(" Emit .wow weather schedule: clear-dominant + occasional rain + fog (forest / grassland)\n");
std::printf(" --gen-weather-arctic <wow-base> [zoneName]\n");