feat(editor): add WTRD (Trade Window Rules) — 115th open format

Novel replacement for the implicit player-to-player
trade policy rules vanilla WoW hardcoded across the
trade-window message handlers (CMSG_INITIATE_TRADE,
CMSG_SET_TRADE_ITEM, CMSG_SET_TRADE_GOLD), the
soulbound-item check, the cross-faction-trade
rejection, and the GM-trade audit hooks. Each entry is
one trade-policy rule the trade-window state machine
consults at every state transition.

Seven ruleKind values (Allowed / Forbidden /
SoulboundException / CrossFactionAllowed / LevelGated /
GoldEscrowMax / AuditLogged) and five targetingFilter
values (AnyPlayer / SameRealmOnly / SameFactionOnly /
SameAccountOnly / GMOnly) cover the full trade-policy
surface. Priority field resolves rule conflicts —
higher priority wins (Allowed at 100 overrides
Forbidden at 10).

Three preset emitters cover real-world deployment
patterns: makeStandard (4 baseline rules — Soulbound
Forbidden globally, Quest items Forbidden, 2hr Soul-
boundException for raid trade-back, SameFactionOnly),
makeServerAdmin (3 server-custom overrides — GM-only
escrow at priority 100, AccountBound own-character
transfer, CrossFactionAllowed at level 80 for RP
servers), makeRMTPrevent (4 anti-RMT rules — 10g cap
for low-level trades, 500g cap for accounts < 30 days,
audit log for trades > 1000g, 24hr first-trade delay).

Validator's most novel check is the GoldEscrowMax /
goldEscrowMaxCopper consistency rule: a GoldEscrowMax-
kind rule MUST specify a non-zero gold cap (zero would
mean unlimited which contradicts the rule's purpose).
Also warns on GMOnly targeting with priority < 50 (GM-
mediated rules typically need high priority to override
player-initiated rules) and levelRequirement > 80
(exceeds current cap, rule never applies).

Format count 114 -> 115. CLI flag count 1227 -> 1232.
This commit is contained in:
Kelsi 2026-05-10 02:30:32 -07:00
parent 65c51a272f
commit 05bb96d23b
10 changed files with 744 additions and 0 deletions

View file

@ -2363,6 +2363,16 @@ void printUsage(const char* argv0) {
std::printf(" Export binary .wvox to a human-editable JSON sidecar (defaults to <base>.wvox.json; emits eventKind and genderHint as both int AND name string; transcript as plain string for accessibility editing)\n");
std::printf(" --import-wvox-json <json-path> [out-base]\n");
std::printf(" Import a .wvox.json sidecar back into binary .wvox (eventKind int OR \"greeting\"/\"aggro\"/\"death\"/\"queststart\"/\"questprogress\"/\"questcomplete\"/\"goodbye\"/\"special\"/\"phase\"; genderHint int OR \"male\"/\"female\"/\"both\"; volumeDb is signed int8)\n");
std::printf(" --gen-trd <wtrd-base> [name]\n");
std::printf(" Emit .wtrd 4 standard trade rules (Soulbound Forbidden / Quest items Forbidden / 2hr SoulboundException for raid drops / SameFactionOnly default)\n");
std::printf(" --gen-trd-admin <wtrd-base> [name]\n");
std::printf(" Emit .wtrd 3 server-admin trade rules (GM-only escrow / AccountBound own-character transfer / CrossFactionAllowed at level 80 for custom servers)\n");
std::printf(" --gen-trd-rmt <wtrd-base> [name]\n");
std::printf(" Emit .wtrd 4 anti-RMT trade rules (LowLevelGoldCap 10g / NewAccountValueCap 500g for accounts <30 days / HighValueAuditLog for trades >1000g / FirstTradeMandatoryDelay 24hr)\n");
std::printf(" --info-wtrd <wtrd-base> [--json]\n");
std::printf(" Print WTRD entries (id / ruleKind / targetingFilter / level requirement / priority / category bitmask / gold cap / name)\n");
std::printf(" --validate-wtrd <wtrd-base> [--json]\n");
std::printf(" Static checks: id+name required, ruleKind 0..6, targetingFilter 0..4, no duplicate ruleIds, GoldEscrowMax kind requires goldEscrowMaxCopper > 0 (else self-contradicting); warns on levelRequirement > 80 (exceeds cap), GMOnly targeting with priority < 50 (would be overridden by player rules)\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");