feat(pipeline): WBND soulbind rules catalog (135th open format)

Novel replacement for the implicit item-binding policy vanilla
WoW carried in ItemTemplate.bondingType + per-item special-case
rules in the server's LootMgr (the 2-hour raid-loot trade
window was hard-coded; the account-bound-shared-across-faction
rule for heirlooms was a TBC+ addition with no formal data-
driven format). Each WBND entry binds one soulbind rule to its
bind kind (BoP / BoE / BoU / BoA / Soulbound / NoBind),
itemQualityFloor predicate (rule applies to items of this
quality and above unless overridden by a stricter rule),
tradable-window duration, raid-trade allowance, BoE-becomes-
BoP trigger, and cross-faction sharing flag.

Three presets capturing actual expansion-era policy evolution:
  --gen-bnd-vanilla  4 rules — Poor=NoBind, Common=BoE,
                     Uncommon+=BoP no-window, Epic+=Soulbound.
                     NO raid-trade window — the 1.12 master-
                     loot drama era
  --gen-bnd-tbc      5 rules adding the iconic 2-hour raid-
                     trade window for BoP items (Uncommon and
                     Rare quality)
  --gen-bnd-wotlk    6 rules adding Heirloom = BindOnAccount
                     + cross-faction (Alliance<->Horde via
                     account-mail) for the WotLK level-1-to-80
                     twink path

Validator catches: id+name required, bindKind 0..5,
itemQualityFloor 0..7, no duplicate ruleIds, no duplicate
(bindKind,qualityFloor) pairs (resolveForQuality lookup tie).
Hard error: tradableForRaidGroup=true with window=0 (window
expires instantly = no window at all). Warns on contradictions:
tradableForRaidGroup with non-BoP kind, window > 0 without
raid-trade flag, boeBecomesBoP without BoE kind,
accountBoundCrossFaction without BoA kind (all flag-ignored at
runtime).

Format count 134 -> 135. CLI flag count 1409 -> 1416.
This commit is contained in:
Kelsi 2026-05-10 04:44:42 -07:00
parent c21f36bd29
commit 19af564a27
10 changed files with 816 additions and 0 deletions

View file

@ -2643,6 +2643,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wloc to a human-editable JSON sidecar (defaults to <base>.wloc.json; emits both locKind and factionAccess as int + name string; floats preserved bit-for-bit)\n");
std::printf(" --import-wloc-json <json-path> [out-base]\n");
std::printf(" Import a .wloc.json sidecar back into binary .wloc (locKind int OR \"poi\"/\"rarespawn\"/\"herbnode\"/\"mineralvein\"/\"fishingspot\"/\"areatrigger\"/\"portallanding\"; factionAccess int OR \"both\"/\"alliance\"/\"horde\"/\"neutral\")\n");
std::printf(" --gen-bnd-vanilla <wbnd-base> [name]\n");
std::printf(" Emit .wbnd vanilla 1.12 soulbind policy: 4 rules (Poor=NoBind, Common=BoE, Uncommon+=BoP no-window, Epic+=Soulbound). NO raid-trade window in vanilla\n");
std::printf(" --gen-bnd-tbc <wbnd-base> [name]\n");
std::printf(" Emit .wbnd TBC 2.4.3 soulbind policy: 5 rules adding the 2-hour raid-trade window for BoP items (Uncommon and Rare quality)\n");
std::printf(" --gen-bnd-wotlk <wbnd-base> [name]\n");
std::printf(" Emit .wbnd WotLK 3.3.5a soulbind policy: 6 rules adding Heirloom = BindOnAccount + cross-faction (Alliance<->Horde via account-mail) for the level-1-to-80 twink path\n");
std::printf(" --info-wbnd <wbnd-base> [--json]\n");
std::printf(" Print WBND entries (id / bindKind / itemQualityFloor / raid-trade flag / boe-becomes-bop / xfac / window-sec / name)\n");
std::printf(" --validate-wbnd <wbnd-base> [--json]\n");
std::printf(" Static checks: id+name required, bindKind 0..5, itemQualityFloor 0..7, no duplicate ruleIds, no duplicate (bindKind,qualityFloor) pairs (resolveForQuality tie); tradableForRaidGroup=true with window=0 errors (instant expiry = no window). Warns on contradictions: tradableForRaidGroup with non-BoP kind, window > 0 without raid-trade flag, boeBecomesBoP without BoE kind, accountBoundCrossFaction without BoA kind (all flag-ignored at runtime)\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");