Commit graph

189 commits

Author SHA1 Message Date
Kelsi
019104536f feat(editor): add WTAL JSON round-trip authoring workflow
Closes the WTAL open-format loop with --export-wtal-json /
--import-wtal-json, mirroring the JSON pairs added for
every other novel binary format. All 19 binary formats
added since WOL now have full JSON round-trip authoring.

Each tree round-trips:
  • treeId, name, iconPath, requiredClassMask
  • talents[] with talentId / row / col / maxRank /
    prereqTalentId+Rank / rankSpellIds[5] (always emitted
    as a 5-element array, zero-padded for unused ranks)

The fixed-size rankSpellIds array round-trips exactly
even when most slots are zero — preserves binary layout
parity for downstream consumers expecting fixed-stride
talent records.

Verified byte-identical round-trip on the warrior preset
(3 trees, 11 talents with prereq chains and capstone WSPL
spell references intact).

Adds 2 flags (580 documented total now).
2026-05-09 16:41:37 -07:00
Kelsi
82a8c3559e feat(pipeline): add WMS (Wowee Map / Area) catalog format
Novel open replacement for Blizzard's Map.dbc + AreaTable.dbc
+ the AzerothCore-style world_zone SQL tables. The 26th open
format added to the editor.

Defines two related kinds of locator in one catalog:
  • Maps  — top-level worlds (continents / instances / raids /
            battlegrounds / arenas) with a friendly name,
            type, expansion tag, and player-count cap.
  • Areas — sub-zones within maps with friendly names, parent-
            area chain, recommended level range, faction-
            territory marker (alliance / horde / contested /
            both), exploration XP, and an ambient-sound
            cross-reference into WSND.

The runtime uses Areas for minimap labels, location strings
under the player frame, "Discover Sub-zone" XP gains, and
ambient-music selection on zone entry.

Cross-references with previously-added formats:
  WMS.area.ambienceSoundId    -> WSND.entry.soundId
  WMS.area.parentAreaId       -> WMS.area.areaId (intra-format
                                   sub-zone hierarchy)
  WSPN entries are tied to WMS.area boundaries by
  world position (no direct ID — the runtime resolves
  position -> area at lookup time)

Format:
  • magic "WMSX", version 1, little-endian
  • maps[] (each): mapId / name / shortName / mapType /
    expansionId / maxPlayers
  • areas[] (each): areaId / mapId / parentAreaId / name /
    minLevel..maxLevel / factionGroup / explorationXP /
    ambienceSoundId

Enums:
  • MapType (5):     Continent / Instance / Raid / Battleground / Arena
  • ExpansionId (5): Classic / Tbc / Wotlk / Cata / Mop
  • FactionGroup:    Both / Alliance / Horde / Contested
                      (PvP-flagging zone)

API: WoweeMapsLoader::save / load / exists +
WoweeMaps::findMap / findArea.

Three preset emitters showcase the catalog shape:
  • makeStarter — 1 continent + 3 areas with parent chain
                   (Goldshire is a sub-zone of Elwynn Forest)
  • makeClassic — 2 continents + Deadmines instance + 6
                   areas (Stormwind/Elwynn/Goldshire/Westfall/
                   Duskwood/Teldrassil/Deadmines) with WSND
                   ambient-sound refs
  • makeBgArena — Alterac Valley (40-player BG) + Nagrand
                   Arena (5v5 with maxPlayers=10)

CLI added (5 flags, 578 documented total now):
  --gen-maps / --gen-maps-classic / --gen-maps-bgarena
  --info-wms / --validate-wms

Validator catches: empty map name, unknown mapType / expansion,
BG/Arena with maxPlayers=0 (no participant cap), area ids=0
+ duplicates, empty area name, maxLevel < minLevel, areas
referencing non-existent maps, parentAreaId chains crossing
maps (sub-zones must be on the same world), self-parent.
2026-05-09 16:40:00 -07:00
Kelsi
cc4b9a6fad feat(editor): add WTAX JSON round-trip authoring workflow
Closes the WTAX open-format loop with --export-wtax-json /
--import-wtax-json, mirroring the JSON pairs added for
every other novel binary format. All 18 binary formats
added since WOL now have full JSON round-trip authoring.

Two top-level arrays mirror the binary layout:
  • nodes[] — nodeId / mapId / name / iconPath / position[3] /
    factionAlliance + factionHorde restrictions
  • paths[] — pathId / fromNodeId / toNodeId / moneyCostCopper /
    waypoints[] each with position[3] + delaySec

Vec3 fields become 3-element JSON arrays for natural
hand-edit. The intra-format graph (paths reference nodeIds)
round-trips exactly so the catalog's connectivity is
preserved.

Verified byte-identical round-trip on the continent preset
(6 nodes + 8 paths covering hub-and-spoke + 3 perimeter
shortcuts, 16 total waypoints).

Adds 2 flags (573 documented total now).
2026-05-09 16:35:01 -07:00
Kelsi
99baf2d0c4 feat(pipeline): add WTAL (Wowee Talent catalog) format
Novel open replacement for Blizzard's TalentTab.dbc +
Talent.dbc + the AzerothCore-style talent_progression SQL
tables. The 25th open format added to the editor.

Defines class talent specialization trees: per-class set
of named tabs (Arms / Fury / Protection for warrior, Fire
/ Frost / Arcane for mage), each with talents arranged in
a row/column grid, each talent having up to 5 ranks and
an optional prerequisite chain.

Cross-references with previously-added formats:
  WTAL.talent.prereqTalentId -> WTAL.talent.talentId
                                 (intra-format chain)
  WTAL.talent.rankSpellIds[] -> WSPL.entry.spellId
                                 (spell granted at each rank)

Format:
  • magic "WTAL", version 1, little-endian
  • per tree: treeId / name / iconPath / requiredClassMask /
    talents[] (row, col, maxRank, prereqTalentId+rank,
    rankSpellIds[5] zero-padded for unused ranks)

Enums:
  • ClassMask: bit positions match canonical CharClasses.dbc
    classIds — Warrior / Paladin / Hunter / Rogue / Priest /
    DK / Shaman / Mage / Warlock / Druid

API: WoweeTalentLoader::save / load / exists +
WoweeTalent::findTree / findTalent (global lookup across
all trees in the catalog).

Three preset emitters showcase tree shapes:
  • makeStarter — 1 small tree (3-talent vertical chain)
  • makeWarrior — 3 trees (Arms 4 / Fury 4 / Protection 3)
                   with WSPL cross-refs at capstones
                   (Mortal Strike -> WSPL 12294, Battle Shout
                   -> WSPL 6673, Thunder Clap -> WSPL 6343)
  • makeMage    — 3 trees (Arcane / Fire / Frost) with
                   capstones referencing Frostbolt 116 /
                   Fireball 133 / Blink 1953 from WSPL

CLI added (5 flags, 571 documented total now):
  --gen-talents / --gen-talents-warrior / --gen-talents-mage
  --info-wtal / --validate-wtal

Validator catches: tree+talent ids=0 or duplicates, empty
tree name, requiredClassMask=0 (every class would see this
tree — usually a typo), maxRank not in 1..5, talent listing
itself as prerequisite, prereqTalentId pointing at a
talent that doesn't exist in this catalog (intra-format
cross-reference resolution), prereqRank=0 or > the prereq
talent's maxRank (catches off-by-one references), gaps in
rankSpellIds progression (rank N has spell but rank N-1
doesn't — usually a typo).

The validator caught a real authoring bug in the makeMage /
makeWarrior presets during smoke testing — initial check
was comparing prereqRank against the WRONG talent's maxRank
(this talent's rather than the prereq's). Fixed in the same
commit by hoisting the check into the cross-reference
resolution pass where the prereq talent is in hand.
2026-05-09 16:33:45 -07:00
Kelsi
429460798f feat(editor): add WGSP JSON round-trip authoring workflow
Closes the WGSP open-format loop with --export-wgsp-json /
--import-wgsp-json, mirroring the JSON pairs added for
every other novel binary format. All 17 binary formats
added since WOL now have full JSON round-trip authoring.

Each menu round-trips:
  • menuId, titleText
  • options[] with optionId / text / kind (dual int + name) /
    actionTarget / requiredFlags (dual int + flag-string array) /
    moneyCostCopper

The kindName field makes it obvious that a hand-edited
"vendor" / "trainer" / "submenu" string maps to the right
internal value without needing to know that vendor=2 and
submenu=1.

Verified byte-identical round-trip on the innkeeper preset
(2 menus, 7 options including Submenu cross-references that
must stay byte-stable to preserve the inter-menu graph).

Adds 2 flags (566 documented total now).
2026-05-09 16:28:12 -07:00
Kelsi
3b107459b2 feat(pipeline): add WTAX (Wowee Taxi catalog) format
Novel open replacement for Blizzard's TaxiNodes.dbc +
TaxiPath.dbc + TaxiPathNode.dbc. The 24th open format
added to the editor.

Defines the flight-master network: a set of named nodes
(positions on the world map) plus the paths between them
(sequences of waypoints with per-segment delay and a
per-path gold cost). The same file holds both node and
path lists — flat arrays keyed by id, with intra-format
references from path.fromNodeId / toNodeId to node.nodeId.

Cross-references:
  WCRT.entry (with FlightMaster npcFlag) ~= WTAX.nodeId
                                            (matched by world
                                             position; flight
                                             master NPCs stand
                                             at their nodes)
  WTAX.path.fromNodeId / toNodeId -> WTAX.entry.nodeId
                                     (intra-format graph)

Format:
  • magic "WTAX", version 1, little-endian
  • nodes (each): nodeId / mapId / name / iconPath /
    position / faction restrictions
  • paths (each): pathId / from+toNodeId / moneyCostCopper /
    waypoints[] each with position + per-waypoint delaySec

API: WoweeTaxiLoader::save / load / exists +
WoweeTaxi::findNode / findPath / findPathBetween.

Three preset emitters showcase different graph shapes:
  • makeStarter  — 2 nodes + 2 paths (round-trip)
  • makeRegion   — 4 nodes at a 500m square + 4-path
                    directed ring (NW->NE->SE->SW->NW)
  • makeContinent — 6 nodes hub-spoke + 3 perimeter
                     shortcuts; intermediate waypoints
                     climb to altitude 120m for visual
                     arc effect

CLI added (5 flags, 564 documented total now):
  --gen-taxi / --gen-taxi-region / --gen-taxi-continent
  --info-wtax / --validate-wtax

Validator catches: nodeId/pathId=0 + duplicates, empty node
name, non-finite positions, fromNodeId == toNodeId
(self-loop path), path references to non-existent nodes
(intra-format cross-reference resolution), negative
waypoint delays.
2026-05-09 16:26:27 -07:00
Kelsi
efc27ba7d2 feat(editor): add --gen-mesh-rune-stone standing monolith
91st procedural mesh: a wide flat base block with a tall
narrow monolith standing on top. Reads as a small carved
standing-stone marker.

Distinct from existing ritual-prop primitives:
  • --gen-mesh-altar      — table-shaped, has flat top
  • --gen-mesh-shrine     — multi-tier with cap
  • --gen-mesh-tombstone  — narrower, curved-top silhouette
  • --gen-mesh-pillar     — round column, no base block
  • --gen-mesh-statue     — has figure on top

Useful for: druid groves (boundary markers), witch shrines,
ancient ruins (pre-civilization monuments), graveyard
boundary stones, faction territory markers, Stonehenge-style
ring formations (use 4-8 instances around a center point).

48 verts / 24 tris from two simple boxes — minimal vertex
budget, suitable for placing in dense clusters.
2026-05-09 16:21:35 -07:00
Kelsi
2de08a3fd0 feat(pipeline): add WGSP (Wowee Gossip Menu) format
Novel open replacement for AzerothCore-style gossip_menu +
gossip_menu_option + npc_text SQL tables PLUS the Blizzard
NpcText.dbc family. The 23rd open format added to the
editor.

An NPC's dialogue tree: a menu of options the player can
pick from when right-clicking the NPC. Each option may
bridge to another menu, trigger a vendor / trainer
interaction, offer a quest, etc. The simplified per-option
model (kind + actionTarget + flags + moneyCost) covers the
common cases without needing separate npc_text condition
tables.

Closes a major cross-format gap: WCRT.entry.gossipId has
existed since batch 116 (when WCRT was added) but pointed
to a format that didn't exist yet. The innkeeper preset's
menuId=4001 deliberately matches WCRT's Bartleby NPC so
the demo content stack can wire WCRT.gossipId = 4001 once
that field is plumbed through the runtime.

Cross-references:
  WCRT.entry.gossipId        -> WGSP.entry.menuId
  WGSP.option.actionTarget (Submenu) -> WGSP.entry.menuId
  WGSP.option.actionTarget (Vendor / Trainer)
                              -> WTRN.entry.npcId
  WGSP.option.actionTarget (Quest)  -> WQT.entry.questId

Format:
  • magic "WGSP", version 1, little-endian
  • per menu: menuId / titleText + options[]
  • per option: optionId / text / kind / actionTarget /
    requiredFlags / moneyCostCopper

Enums:
  • OptionKind (13): Close / Submenu / Vendor / Trainer /
                     Quest / Tabard / Banker / Innkeeper /
                     FlightMaster / TextOnly / Script /
                     Battlemaster / Auctioneer
  • OptionFlags:    AllianceOnly / HordeOnly / Coinpouch /
                     QuestGated / Closes

API: WoweeGossipLoader::save / load / exists / findById;
presets makeStarter (1 menu with vendor + trainer + close),
makeInnkeeper (2-menu tree: main menu 4001 with hearth /
vendor / flight / submenu options + lore submenu 4002 that
links back), makeQuestGiver (1 menu with 2 quest options
referencing WQT 1 and 100, plus a paid respec script
exercising the Coinpouch flag with a 10g cost).

CLI added (5 flags, 558 documented total now):
  --gen-gossip / --gen-gossip-innkeeper / --gen-gossip-questgiver
  --info-wgsp / --validate-wgsp

Validator catches: menuId=0 + duplicates, empty title /
options, unknown option kind, empty option text, Submenu
options pointing at non-existent menuIds (intra-format
cross-reference resolution), Coinpouch flag without
moneyCost (misleading UI), AllianceOnly+HordeOnly conflict.
2026-05-09 16:20:07 -07:00
Kelsi
c3f7286d4a feat(editor): add WTRN JSON round-trip authoring workflow
Closes the WTRN open-format loop with --export-wtrn-json /
--import-wtrn-json, mirroring the JSON pairs added for
every other novel binary format. All 16 binary formats
added since WOL now have full JSON round-trip authoring.

Each NPC round-trips:
  • npcId, kindMask (dual int + kindList string array),
    greeting
  • spells[]: spellId / cost / requiredSkill+rank / minLevel
  • items[]:  itemId / stockCount / restockSec /
              extendedCost / moneyCostCopper

The stockCount field has special handling — the sentinel
0xFFFFFFFF value emits as the string "unlimited" instead of
the raw integer, since 4294967295 reads as a magic-number
typo in hand-edit JSON. The importer accepts either form.

Verified byte-identical round-trip on the starter preset
(innkeeper 4001 with 1 spell + 3 items, exercising both
unlimited-stock and finite-stock-with-restock cases).

Adds 2 flags (553 documented total now).
2026-05-09 16:14:50 -07:00
Kelsi
d2ca3ea22b feat(pipeline): add WTRN (Wowee Trainer / Vendor catalog) format
Novel open replacement for AzerothCore-style npc_trainer +
npc_vendor SQL tables PLUS the Blizzard TrainerSpells.dbc
family. The 22nd open format added to the editor.

Unifies trainer spell lists and vendor item inventories
into one per-NPC entry. A creature flagged Trainer or
Vendor in WCRT references a WTRN entry that lists what they
teach / sell. The same NPC can be both — kindMask is a
bitmask covering the Trainer (0x01) and Vendor (0x02) kinds.

This format closes a major cross-format gap: WCRT.npcFlags
already had Vendor / Trainer bits, but until now there was
no format defining what a vendor sells or what a trainer
teaches. Now an NPC marked Vendor in WCRT has a real
inventory, and an NPC marked Trainer has a real spell list.

Cross-references — every WTRN field has a real format target:
  WTRN.entry.npcId           -> WCRT.entry.creatureId
  WTRN.spell.spellId         -> WSPL.entry.spellId
  WTRN.spell.requiredSkillId -> WSKL.entry.skillId
  WTRN.item.itemId           -> WIT.entry.itemId

Format:
  • magic "WTRN", version 1, little-endian
  • per NPC: npcId / kindMask / greeting + spells[] + items[]
  • per spell offer: spellId / moneyCostCopper /
    requiredSkillId / requiredSkillRank / requiredLevel
  • per item offer: itemId / stockCount (0xFFFFFFFF =
    unlimited) / restockSec / extendedCost / moneyCostCopper
    (0 = inherit from WIT.buyPrice)

API: WoweeTrainerLoader::save / load / exists / findByNpc;
presets makeStarter (innkeeper 4001 as both trainer +
vendor: teaches First Aid + sells starter items),
makeMageTrainer (NPC 4003 teaches the WSPL mage spells
at scaling cost), makeWeaponVendor (NPC 4002 sells WIT
weapons with mixed unlimited/finite stock + restock timers).

CLI added (5 flags, 551 documented total now):
  --gen-trainers / --gen-trainers-mage / --gen-trainers-weapons
  --info-wtrn / --validate-wtrn

Validator catches: npcId=0 + duplicates, kindMask=0 (NPC
offers nothing), Trainer flag without spells, Vendor flag
without items, spells/items present without the matching
kind bit (silently ignored at runtime), spellId=0 / itemId=0
in offers, finite stock with restockSec=0 (single-fill —
usually intentional but worth surfacing).

The 3 presets deliberately use npcIds matching WCRT village
merchants (4001/4002/4003) so the demo content stack is
self-consistent: WCRT 4001 has the Vendor + Trainer flag,
and WTRN 4001 actually defines what they sell and teach.
2026-05-09 16:12:58 -07:00
Kelsi
89871c171c feat(editor): add WACH JSON round-trip authoring workflow
Closes the WACH open-format loop with --export-wach-json /
--import-wach-json, mirroring the JSON pairs added for
every other novel binary format. All 15 binary formats
added since WOL now have full JSON round-trip authoring.

Each achievement round-trips:
  • 11 scalar fields (id, categoryId, name, description,
    icon, titleReward, points, minLevel, faction, flags)
  • criteria array with full per-criterion fields

Three enum-typed fields emit dual int + name forms so a
hand-author can use either:
  • criterion.kind (kill/quest/loot/level/rep/cast/skill/visit/meta)
  • faction        (both/alliance/horde)
  • flags          (hidden/server-first/realm-first/tracking/...)

Verified byte-identical round-trip on the meta preset (4
achievements, 6 criteria including the 3 CompleteAchievement
criteria that wire the meta-achievement to its prerequisites).

Adds 2 flags (546 documented total now).
2026-05-09 16:07:53 -07:00
Kelsi
08834ff498 feat(editor): add WSPL JSON round-trip authoring workflow
Closes the WSPL open-format loop with --export-wspl-json /
--import-wspl-json, mirroring the JSON pairs added for
every other novel binary format. All 14 binary formats
added since WOL now have full JSON round-trip authoring.

Each spell round-trips all 18 scalar fields. Four enum-typed
fields emit dual int + name forms so a hand-author can use
either:
  • school     (physical / holy / fire / nature / frost / shadow / arcane)
  • targetType (self / single / cone / aoe-self / line / ground)
  • effectKind (damage / heal / buff / debuff / teleport / summon / dispel)
  • flags      (passive / channeled / ranged / aoe / friendly / hostile / ...)

The flags bitset emits string-array form so a hand-author
can write ["hostile", "aoe"] instead of having to remember
that HostileOnly|AreaOfEffect = 0x110.

Verified byte-identical round-trip on the mage preset (4
spells covering frost / fire / arcane schools and damage /
buff / teleport effects, full flag and effect-value coverage).

Adds 2 flags (544 documented total now).
2026-05-09 16:06:29 -07:00
Kelsi
e5eaf13866 feat(pipeline): add WACH (Wowee Achievement Catalog) format
Novel open replacement for Blizzard's Achievement.dbc +
AchievementCriteria.dbc + AchievementCategory.dbc + the
AzerothCore-style character_achievement /
character_achievement_progress SQL tables. The 21st open
format added to the editor.

Each achievement carries display metadata (name, description,
icon, points, faction restriction) plus a list of criteria
the player must satisfy. Criteria mirror the WQT objective
model (kind + targetId + quantity), so the runtime can
reuse the same progress-tracking machinery for both quests
and achievements.

Cross-references with previously-added formats — every
criterion kind has a real format target:
  WACH.criteria.targetId (kind=KillCreature)    -> WCRT.creatureId
  WACH.criteria.targetId (kind=CompleteQuest)   -> WQT.questId
  WACH.criteria.targetId (kind=LootItem)        -> WIT.itemId
  WACH.criteria.targetId (kind=CastSpell)       -> WSPL.spellId
  WACH.criteria.targetId (kind=ReachSkillLevel) -> WSKL.skillId
  WACH.criteria.targetId (kind=EarnReputation)  -> WFAC.factionId
  WACH.criteria.targetId (kind=CompleteAchievement) -> WACH.achievementId
                                                       (meta-achievements)

Format:
  • magic "WACH", version 1, little-endian
  • per achievement: id / categoryId / name / description /
    iconPath / titleReward / points / minLevel / faction /
    flags / criteria[]
  • per criterion: criteriaId / kind / targetId / quantity /
    description

Enums:
  • CriteriaKind (9): KillCreature / CompleteQuest / LootItem /
                      ReachLevel / EarnReputation / CastSpell /
                      ReachSkillLevel / VisitArea /
                      CompleteAchievement
  • Faction:    Both / Alliance / Horde
  • Flags:      HiddenUntilEarned / ServerFirst / RealmFirst /
                 Tracking / Counter / Account

API: WoweeAchievementLoader::save / load / exists /
findById; presets makeStarter (3 simple kill/quest/level
demos), makeBandit (3 with WCRT/WGOT/WQT cross-refs),
makeMeta (3 base + 1 meta-achievement granting "the
Versatile" title, exercising CompleteAchievement criterion
kind that lets achievements depend on other achievements).

CLI added (5 flags, 542 documented total now):
  --gen-achievements / --gen-achievements-bandit / --gen-achievements-meta
  --info-wach / --validate-wach

Validator catches: achievementId=0 + duplicates, empty name,
faction out of range, no criteria (achievement can never
be earned), criterion quantity=0, unknown criterion kind,
targetId=0 on criterion kinds that need a real resource
reference (everything except ReachLevel which uses the
quantity field for the level number).

The bandit preset's cross-references close the gameplay
graph end-to-end: kill 50 creatureId=1000 (matches WCRT/
WSPN/WLOT bandit), loot objectId=2000 (matches WGOT bandit
strongbox), complete questId=1 (matches WQT Bandit Trouble).
The meta preset closes a separate loop: 3 sub-achievements
covering Mining (skillId=186), Lockpicking (skillId=633),
and Frostbolt cast count (spellId=116) — each pointing at
a real WSKL/WSPL entry that already exists in the demo
content stack.
2026-05-09 16:04:30 -07:00
Kelsi
f4f9804000 feat(editor): add WSKL JSON round-trip authoring workflow
Closes the WSKL open-format loop with --export-wskl-json /
--import-wskl-json, mirroring the JSON pairs added for
every other novel binary format. All 13 binary formats
added since WOL now have full JSON round-trip authoring.

Each skill round-trips all 8 scalar fields (skillId, name,
description, categoryId, canTrain, maxRank, rankPerLevel,
iconPath). The categoryId emits dual int + name forms so a
hand-author can write "profession" / "weapon" / "language"
instead of the int values.

Verified byte-identical round-trip on the professions
preset (12 entries: 9 primary + 3 secondary professions
with full canonical SkillLine IDs preserved).

Adds 2 flags (537 documented total now).
2026-05-09 15:59:20 -07:00
Kelsi
5ea1f7ee2a feat(pipeline): add WSPL (Wowee Spell Catalog) format
Novel open replacement for Blizzard's Spell.dbc +
SpellEffect.dbc + the AzerothCore-style spell_dbc /
spell_proc tables. The 20th open format added to the
editor — completes the canonical-data side of the gameplay
graph.

Each entry holds the metadata side of a spell: name,
description, school, range, mana / cast / cooldown times,
plus a single primary effect. The simplified effect model
(one effectKind + min/max value + misc field) covers the
common cases (damage / heal / buff / debuff / teleport /
summon / dispel) without needing to reproduce the full
multi-effect graph that classic Spell.dbc carries.

Cross-references with previously-added formats:
  WLCK.channel.targetId (kind=Spell)       -> WSPL.entry.spellId
  WQT.objective.targetId (kind=SpellCast)  -> WSPL.entry.spellId
  WCRT.equippedMain (item with on-use)     -> WIT -> WSPL

Format:
  • magic "WSPL", version 1, little-endian
  • per spell: spellId / name / description / iconPath /
    school / targetType / effectKind / cast & cooldown &
    GCD ms / manaCost / range min..max / minLevel /
    maxStacks / durationMs / effectValueMin..Max /
    effectMisc / flags

Enums:
  • School (7):     Physical / Holy / Fire / Nature / Frost /
                     Shadow / Arcane
  • TargetType (6): Self / Single / Cone / AoeFromSelf /
                     Line / Ground
  • EffectKind (7): Damage / Heal / Buff / Debuff / Teleport /
                     Summon / Dispel
  • Flags:          Passive / Hidden / Channeled / Ranged /
                     AreaOfEffect / Triggered / UnitTargetOnly /
                     FriendlyOnly / HostileOnly

API: WoweeSpellLoader::save / load / exists / findById;
presets makeStarter (Strike + Lesser Heal + Power Word:
Fortitude + Hearthstone, one per major effect kind),
makeMage (Frostbolt 116 + Fireball 133 + Arcane Intellect
1459 + Blink 1953, canonical Classic spellIds), makeWarrior
(Heroic Strike 78 + Thunder Clap 6343 + Battle Shout 6673 +
Mortal Strike 12294).

CLI added (5 flags, 535 documented total now):
  --gen-spells / --gen-spells-mage / --gen-spells-warrior
  --info-wspl / --validate-wspl

Validator catches: spellId=0 + duplicates, empty name,
school out of range, effectKind out of range, NaN range,
range/value min>max, FriendlyOnly+HostileOnly conflict
(incoherent), friendly-only with damage/debuff effect
(incoherent), hostile-only with heal/buff effect, buff/debuff
effect with durationMs=0 (instant fade — almost certainly
authoring oversight).

The validator caught a real preset-emitter authoring error
during initial smoke testing — buff spells were setting
effectValueMin without effectValueMax (validator's range
check immediately flagged it), prompting an in-batch fix
to set both fields. This is exactly the catch-the-typo
purpose validators serve.
2026-05-09 15:58:09 -07:00
Kelsi
929693405e feat(editor): add WLCK JSON round-trip authoring workflow
Closes the WLCK open-format loop with --export-wlck-json /
--import-wlck-json, mirroring the JSON pairs added for
every other novel binary format. All 12 binary formats
added since WOL now have full JSON round-trip authoring.

Each lock round-trips:
  • lockId, name, flags
  • all 5 channel slots (even unused ones, kind=None)
  • each channel: kind (dual int + name) + skillRequired +
    targetId

The flag bitset emits string-array form so a hand-author
can write ["destruct"] instead of having to remember that
DestructOnOpen = 0x01. Channel kindName makes the difference
between item / lockpick / spell / damage obvious without
needing to know the int values.

Verified byte-identical round-trip on the dungeon preset
(3 locks: light lockpick + steel chest with dual key/pick
channels + boss-key seal with destruct flag).

Adds 2 flags (530 documented total now).
2026-05-09 15:52:20 -07:00
Kelsi
95e593e59c feat(pipeline): add WSKL (Wowee Skill Catalog) format
Novel open replacement for Blizzard's SkillLine.dbc +
SkillLineCategory.dbc + the AzerothCore-style player skill
base tables. The 19th open format added to the editor.

Defines every player-trackable skill: weapon proficiencies
(Swords, Axes, Bows), professions (Mining, Alchemy,
Cooking), languages (Common, Dwarvish), class
specializations (Fire, Frost, Holy, Protection), armor
proficiencies (Mail, Plate), and secondary skills (First
Aid, Lockpicking, Riding).

Cross-references with previously-added formats:
  WLCK.channel.targetId (kind=Lockpick) -> WSKL.entry.skillId
  WGOT.entry.requiredSkill              -> WSKL.entry.skillId

The starter preset's skillIds 186 (Mining) and 633
(Lockpicking) deliberately match the canonical IDs already
referenced by WGOT.makeGather and WLCK.makeDungeon —
so the demo content stack now wires together end-to-end:
WGOT herb-node requires skill 186 -> WSKL Mining at rank 1+;
WLCK bandit-strongbox channel requires skill 633 -> WSKL
Lockpicking at rank 1+.

Format:
  • magic "WSKL", version 1, little-endian
  • per skill: skillId / name / description / categoryId /
    canTrain / maxRank / rankPerLevel / iconPath

Enums:
  • CategoryId (8): Weapon / Class / Profession /
    SecondaryProfession / Language / ArmorProficiency /
    Riding / WeaponSpec

API: WoweeSkillLoader::save / load / exists / findById;
presets makeStarter (5-skill demo with cross-referenced
canonical IDs), makeProfessions (12 classic professions:
9 primary + 3 secondary), makeWeapons (16 weapon skills
with canonical SkillLine IDs and rankPerLevel=5 auto-grow).

CLI added (5 flags, 528 documented total now):
  --gen-skills / --gen-skills-professions / --gen-skills-weapons
  --info-wskl / --validate-wskl

Validator catches: skillId=0 + duplicates, empty name,
maxRank=0, unknown categoryId, suspicious maxRank=1 on
non-Language skill (only languages cap at 1), weapon skill
with rankPerLevel=0 (won't auto-grow on use).
2026-05-09 15:50:25 -07:00
Kelsi
dda7933df9 feat(editor): add WFAC JSON round-trip authoring workflow
Closes the WFAC open-format loop with --export-wfac-json /
--import-wfac-json, mirroring the JSON pairs added for
every other novel binary format. All 11 binary formats
added since WOL now have full JSON round-trip authoring.

Each faction round-trips:
  • 13 scalar fields (id, parent, name, description, flags,
    7 ascending threshold values, baseReputation)
  • enemies[] and friends[] as JSON int arrays for trivial
    hand-edit
  • reputationFlags as dual int + string-array form
    (e.g. ["visible", "header"])

The 7 reputation thresholds round-trip exactly even though
they have non-default values in some presets — the
importer uses the canonical Hostile/Friendly/etc enum
values as defaults so a hand-author can omit the standard
thresholds and only specify custom ones.

Verified byte-identical round-trip on the alliance preset
(5 factions: Alliance header + 3 cities with reciprocal
friend lists + Defias enemy). Adds 2 flags (523 documented
total now).
2026-05-09 15:45:58 -07:00
Kelsi
81b1897a24 feat(pipeline): add WLCK (Wowee Lock Template) format
Novel open replacement for Blizzard's Lock.dbc. The 18th
open format added to the editor. Closes the cross-reference
gap from WGOT.entry.lockId — until now that field pointed
to a format that didn't exist yet.

A lock is a multi-channel security check. Each lock has up
to 5 independent channels; a player can open the lock by
satisfying ANY ONE channel:
  • Item     — requires a specific key item (WIT cross-ref)
  • Lockpick — requires the lockpicking skill at minimum rank
                (rogue / engineering profession)
  • Spell    — requires casting a specific spell
  • Damage   — can be forced open with attack damage

Cross-references with previously-added formats:
  WGOT.entry.lockId               -> WLCK.entry.lockId
  WLCK.channel.targetId (Item)    -> WIT.entry.itemId
  WLCK.channel.targetId (Lockpick) -> future WSKL skillId
  WLCK.channel.targetId (Spell)   -> future WSPL spellId

The starter and dungeon presets' lockIds (1 and 2)
deliberately match WGOT.makeDungeon's iron-door lockId=1
and bandit-strongbox lockId=2, so the demo content stack
already wires together: WSPN spawn -> WGOT object template
-> WLCK lock template -> WIT key items.

Format:
  • magic "WLCK", version 1, little-endian
  • per lock: lockId / name / flags / 5 fixed channel slots
  • per channel: kind / skillRequired / targetId
  • all 5 slots written even when unused (kind=None +
    zeroed fields), keeping the per-entry size constant for
    fast random access

Enums:
  • ChannelKind: None / Item / Lockpick / Spell / Damage
  • Flags:       DestructOnOpen / RespawnOnKey / TrapOnFail

API: WoweeLockLoader::save / load / exists / findById;
presets makeStarter (Iron Door + Wooden Chest), makeDungeon
(matches WGOT cross-references; light/heavy lockpicks +
boss-key-only seal), makeProfessions (4-tier rogue lockpick
progression at ranks 1/100/175/250).

CLI added (5 flags, 521 documented total now):
  --gen-locks / --gen-locks-dungeon / --gen-locks-professions
  --info-wlck / --validate-wlck

Validator catches: lockId=0 + duplicates, all-None channels
(lock can never open), Item/Spell/Lockpick channels with
targetId=0 (no resource referenced), unknown channel kind,
skillRequired set on non-Lockpick channel (silently ignored
at runtime — flag as warning).
2026-05-09 15:44:26 -07:00
Kelsi
062258de8a feat(editor): add WGOT JSON round-trip authoring workflow
Closes the WGOT open-format loop with --export-wgot-json /
--import-wgot-json, mirroring the JSON pairs added for
every other novel binary format. All 10 binary formats
added since WOL now have full JSON round-trip authoring.

Each game object round-trips all 13 scalar fields plus
dual int + name forms for typeId (16 enum values) and the
flags bitset.

The flag bitset emits string-array form so a hand-author
can write ["despawn"] instead of having to remember that
Despawn = 0x08. typeName makes "door/chest/herb-node/..."
obvious without needing to know typeId=11 means HerbNode.

Verified byte-identical round-trip on the dungeon preset
(5 objects: door + button + 2 chests + trap with full
WLOT cross-references and lockId fields preserved).

Adds 2 flags (516 documented total now).
2026-05-09 15:39:50 -07:00
Kelsi
4868f780cc feat(pipeline): add WFAC (Wowee Faction Catalog) format
Novel open replacement for Blizzard's Faction.dbc +
FactionTemplate.dbc + the AzerothCore-style
reputation_reward / reputation_spillover SQL tables. The
17th open format added to the editor.

Combines the "displayable Faction" (player-facing name +
reputation thresholds for friendly/honored/revered/exalted)
with the "FactionTemplate matrix" (which factions are
hostile to which) into one entry. The runtime walks the
catalog to answer two questions:
  • "Will faction A attack faction B on sight?" -> enemy list
  • "What rep tier is the player with X?"      -> thresholds

Cross-references with previously-added formats:
  WCRT.entry.factionId       -> WFAC.entry.factionId
  WFAC.entry.parentFactionId -> WFAC.entry.factionId
  WFAC.entry.enemies[]       -> WFAC.entry.factionId
  WFAC.entry.friends[]       -> WFAC.entry.factionId

The starter preset's factionId 35 (Friendly) and 14
(Hostile) deliberately match the WCRT preset defaults, so
the demo content stack is consistent: WCRT.makeBandit's
factionId=14 has a real entry in WFAC.makeStarter that
declares it hostile to friendly NPCs (35) and players (1).

Format:
  • magic "WFAC", version 1, little-endian
  • per faction: factionId / parentFactionId / name /
    description / reputationFlags / baseReputation /
    7 ascending tier thresholds (hostile..exalted) /
    enemies[] / friends[]

Enums:
  • ReputationFlags: VisibleOnTab / AtWarDefault / Hidden /
                      NoReputation / IsHeader (group label)
  • Tier (canonical): Hated / Hostile / Unfriendly /
                       Neutral / Friendly / Honored /
                       Revered / Exalted

API: WoweeFactionLoader::save / load / exists / findById +
WoweeFaction::isHostile(a, b); presets makeStarter (3-faction
demo matching WCRT defaults), makeAlliance (header +
Stormwind / Darnassus / Ironforge with reciprocal friend
lists + Defias enemy), makeWildlife (4 beast factions, each
hostile to player but ignoring other beasts).

CLI added (5 flags, 514 documented total now):
  --gen-factions / --gen-factions-alliance / --gen-factions-wildlife
  --info-wfac / --validate-wfac

Validator catches: factionId=0 + duplicates, empty name,
threshold ordering violations (hostile must be < unfriendly
< neutral < ... < exalted), self-listed as enemy or friend,
faction in both enemies and friends (incoherent).
2026-05-09 15:37:59 -07:00
Kelsi
62e370545f feat(editor): add WQT JSON round-trip authoring workflow
Closes the WQT open-format loop with --export-wqt-json /
--import-wqt-json, mirroring the WOL/WOW/WOMX/WSND/WSPN/
WIT/WLOT/WCRT JSON pairs. All 9 binary formats added since
WOL now have full JSON round-trip authoring.

Each quest round-trips:
  • 13 scalar fields (id, level range, masks, chain links,
    giver/turnin, xp + money reward, flags)
  • 3 string fields (title, objective, description)
  • objectives array with dual int + name kindName
  • rewardItems array with dual int + name pickFlagsList

The flag bitset emits string-array form so a hand-author can
write ["daily", "repeatable", "auto-accept"] instead of
having to remember the bit math. The objective kindName
makes "visit/collect/kill" obvious without needing to know
that kind=3 means VisitArea.

Verified byte-identical round-trip on the 3-quest chain
preset (full feature exercise: prev/next chain links,
mixed objective kinds, AutoComplete bridge quest, player-
choice rewards). Adds 2 flags (509 documented total now).
2026-05-09 15:33:21 -07:00
Kelsi
f8d7b6b6bd feat(pipeline): add WGOT (Wowee Game Object Template) format
Novel open replacement for AzerothCore-style
gameobject_template SQL tables PLUS the Blizzard
GameObjectDisplayInfo.dbc / GameObject types metadata. The
16th open format added to the editor.

Game objects are the non-creature interactable scenery:
chests (with loot), doors, buttons, mailboxes, herb / ore
gathering nodes, fishing pools, signposts, mounts. Each
has a displayId for the model, a typeId driving its
interaction logic, and optional cross-references to a lock
(future WLCK) and loot table (existing WLOT).

Cross-references with previously-added formats:
  WSPN.entry.entryId (kind=GameObject) -> WGOT.entry.objectId
  WGOT.entry.lootTableId               -> WLOT.entry.creatureId
                                          (loot tables are
                                           universal — chests
                                           and creatures both
                                           key by ID)

The dungeon preset's Bandit Strongbox uses lootTableId=2000
to match WLOT's bandit chest table id, so the demo content
stack already wires together: spawn (WSPN object kind 2000)
-> object template (WGOT 2000) -> loot table (WLOT 2000).

Format:
  • magic "WGOT", version 1, little-endian
  • per object: objectId / displayId / name / typeId /
    size / castBarCaption / requiredSkill +
    requiredSkillValue / lockId / lootTableId /
    minOpenTimeMs..maxOpenTimeMs / flags

Enums:
  • TypeId (16): Door / Button / Chest / Container /
    QuestGiver / Text / Trap / Goober / Transport /
    Mailbox / MineralNode / HerbNode / FishingNode /
    Mount / Sign / Bonfire
  • Flags: Disabled / ScriptOnly / UsableFromMount /
    Despawn / Frozen / QuestGated

API: WoweeGameObjectLoader::save / load / exists /
findById; presets makeStarter (chest + mailbox + sign),
makeDungeon (door + button + 2 chests + trap with proper
WLOT cross-references), makeGather (Peacebloom herb +
Tin Vein ore + fishing pool with skill requirements).

CLI added (5 flags, 507 documented total now):
  --gen-objects / --gen-objects-dungeon / --gen-objects-gather
  --info-wgot / --validate-wgot

Validator catches: objectId=0 + duplicates, size<=0,
minOpenTime>maxOpenTime, gathering node without skill
requirement (anyone can harvest — usually a typo), chest
without loot table (script must populate), requiredSkillValue
set without requiredSkill (incoherent).
2026-05-09 15:31:49 -07:00
Kelsi
2df4e75c71 feat(editor): add WCRT JSON round-trip authoring workflow
Closes the WCRT open-format loop with --export-wcrt-json /
--import-wcrt-json, mirroring the WOL/WOW/WOMX/WSND/WSPN/
WIT/WLOT JSON pairs. All 8 binary formats added since WOL
now have full JSON round-trip authoring.

Each creature template round-trips all 22 scalar fields.
Three enum-typed fields emit dual int + name forms so a
hand-author can use either:
  • typeId    (humanoid / beast / dragon / ...)
  • familyId  (wolf / cat / bear / ... — for beasts)
  • npcFlags  (vendor / quest / trainer / innkeeper / ...)
  • aiFlags   (passive / aggressive / flee / call-help / no-leash)

Both flag bitsets emit string-array forms so a hand-author
can write ["vendor", "innkeeper", "repair"] instead of
having to remember that those bits combine to 0x91.

Verified byte-identical round-trip on the merchants preset
(3 NPCs covering innkeeper / smith / alchemist with mixed
flag combinations). Adds 2 flags (502 documented total now).
2026-05-09 15:27:12 -07:00
Kelsi
02ae17740e feat(pipeline): add WQT (Wowee Quest Template) format
Novel open replacement for AzerothCore-style quest_template
SQL tables PLUS the Blizzard Quest.dbc / QuestObjective.dbc
trio. The 15th open format added to the editor — and the
last gameplay-graph piece the catalog needed.

Cross-references with previously-added formats:
  WQT.giverCreatureId    -> WCRT.entry.creatureId
  WQT.turninCreatureId   -> WCRT.entry.creatureId
  WQT.objective.targetId -> WCRT (kill) / WIT (collect) /
                             WOB (interact)
  WQT.rewardItem.itemId  -> WIT.entry.itemId
  WQT.prevQuestId        -> WQT.entry.questId (intra-format)
  WQT.nextQuestId        -> WQT.entry.questId

Together with WIT / WCRT / WLOT / WSPN / WOMX / WOL / WOW /
WSND, a content pack can now ship a complete RPG zone
(terrain + props + atmosphere + sounds + creatures + items
+ loot + spawns + quests) entirely in open formats with no
SQL or .dbc dependencies. 15 of 15 expected slots filled.

Format:
  • magic "WQTM", version 1, little-endian
  • per quest: questId / title / objective / description /
    minLevel..maxLevel + questLevel / requiredClass+RaceMask /
    prev+nextQuestId / giver+turninCreatureId /
    objectives[] / xpReward + moneyCopperReward /
    rewardItems[] / flags

Per-objective:
  kind (kill/collect/interact/visit/escort/cast),
  targetId, quantity

Per-reward:
  itemId, qty, pickFlags (AutoGiven / PlayerChoice)

Quest flags: Daily / Weekly / Raid / Group / AutoComplete /
              AutoAccept / Repeatable / ClassQuest / Pvp

API: WoweeQuestLoader::save / load / exists / findById;
presets makeStarter (1 simple kill quest, references the
bandit creatureId=1000), makeChain (3-quest chain with
prev/next links + AutoComplete bridge + player-choice
rewards), makeDaily (Daily+Repeatable+AutoAccept combo).

CLI added (5 flags, 500 documented total — round milestone):
  --gen-quests / --gen-quests-chain / --gen-quests-daily
  --info-wqt / --validate-wqt

Validator catches: questId=0+duplicates, level=0,
maxLevel<minLevel, empty title, no objectives without
AutoComplete (player can't finish), no rewards at all,
Daily without Repeatable (incoherent), targetId=0,
quantity=0, unknown objective kind, reward itemId=0 or qty=0.

The 3-quest chain demo exercises every major feature:
  • multiple objective kinds (visit / collect / kill)
  • prev/next chain links
  • AutoComplete dialogue-bridge quest
  • PlayerChoice reward (1 of 2 weapons)
2026-05-09 15:25:02 -07:00
Kelsi
24bc52ab11 feat(editor): add WLOT JSON round-trip authoring workflow
Closes the WLOT open-format loop with --export-wlot-json /
--import-wlot-json, mirroring the WOL/WOW/WOMX/WSND/WSPN/WIT
JSON pairs. All 7 binary formats added since WOL now have
full JSON round-trip authoring.

Each loot table round-trips:
  • table-level: creatureId, flags (int + flagsList strings),
                  dropCount, money min/max (copper)
  • per-drop:    itemId, chancePercent (float),
                  minQty / maxQty, flags (int + flagsList)

Both flag fields emit dual int + named string-array forms.
A hand-author can write ["quest", "always"] instead of
having to remember that QuestRequired|AlwaysDrop = 5.

Verified byte-identical round-trip on the boss preset
(6 drops including the QuestRequired+AlwaysDrop combo on
the guaranteed quest item, group-only epic at 5%, mass-loot
trade goods at 90%).

Adds 2 flags (495 documented total now).
2026-05-09 15:20:05 -07:00
Kelsi
b2b84139aa feat(pipeline): add WCRT (Wowee Creature Template) format
Novel open replacement for the AzerothCore-style
creature_template SQL table PLUS the Blizzard
CreatureTemplate / CreatureFamily / CreatureType.dbc trio.
The 14th open format added to the editor.

This is the canonical metadata side of creatures shared
across every spawn instance: HP, level range, faction,
behavior flags, NPC role bits (vendor / trainer /
quest-giver / innkeeper), base damage, equipped gear
references.

Cross-references with the previously-added formats:
  WSPN.entry.entryId    -> WCRT.entry.creatureId
  WLOT.entry.creatureId -> WCRT.entry.creatureId
  WCRT.entry.equipped*  -> WIT.entry.itemId

The 4-format set (WIT + WLOT + WSPN + WCRT) now lets a
content pack define a complete RPG zone's creature
ecosystem: what creatures are, where they spawn, what they
drop, and what gear they carry — entirely in open formats
with no SQL dependencies.

Format:
  • magic "WCRT", version 1, little-endian
  • per entry: creatureId / displayId / name / subname /
    minLevel..maxLevel / baseHealth + healthPerLevel /
    baseMana + manaPerLevel / factionId / npcFlags /
    typeId / familyId / damageMin..Max / attackSpeedMs /
    baseArmor / walkSpeed + runSpeed / gossipId /
    equippedMain + equippedOffhand + equippedRanged /
    aiFlags

Enums:
  • TypeId:   Beast / Dragon / Demon / Elemental / Giant /
              Undead / Humanoid / Critter / Mechanical
  • FamilyId: Wolf / Cat / Bear / Boar / Raptor / Hyena /
              Spider / Gorilla / Crab (for Beast types)
  • NpcFlags: Vendor / QuestGiver / Trainer / Banker /
              Innkeeper / FlightMaster / Auctioneer /
              Repair / Stable
  • Behavior: Passive / Aggressive / FleeLowHp / CallHelp /
              NoLeash

API: WoweeCreatureLoader::save / load / exists /
findById; presets makeStarter (1 innkeeper),
makeBandit (creatureId=1000 matches WSPN/WLOT bandit
references, equips WIT itemId=1001 sword), makeMerchants
(creatureIds 4001/4002/4003 match WSPN village labels).

CLI added (5 flags, 493 documented total):
  --gen-creatures / --gen-creatures-bandit / --gen-creatures-merchants
  --info-wcrt / --validate-wcrt

Validator catches: creatureId=0, duplicates, level=0,
minLevel>maxLevel, baseHealth=0, damageMin>damageMax,
attackSpeed=0, non-positive walk/runSpeed, behavior flag
contradictions (passive+aggressive), vendor with
aggressive behavior (player can't trade).
2026-05-09 15:18:44 -07:00
Kelsi
356fe53a9a feat(editor): add WIT JSON round-trip authoring workflow
Closes the WIT open-format loop with --export-wit-json /
--import-wit-json, mirroring the WOL/WOW/WOMX/WSND/WSPN
JSON pairs. All 6 binary formats added since WOL now have
full JSON round-trip authoring.

Each entry round-trips all 18 scalar fields plus the
variable-length stats array. Three enum-typed fields emit
dual int + name forms so a hand-author can use either:
  • quality       (poor..heirloom)
  • itemClass     (consumable / weapon / armor / quest / ...)
  • inventoryType (head, chest, weapon-1h, ...)

Stats round-trip with both type int + typeName string so
"strength: 5" reads more naturally than "type=4, value=5"
in hand-edit JSON.

Verified byte-identical round-trip on the makeWeapons
preset (5 items spanning common -> legendary, both 1H and
2H slots, full damage / speed / stat coverage). Adds 2
flags (488 documented total now).
2026-05-09 15:13:26 -07:00
Kelsi
ff0aa1a3c8 feat(pipeline): add WLOT (Wowee Loot Table) format
Novel open replacement for AzerothCore-style
creature_loot_template / gameobject_loot_template SQL
tables. The 13th open format added to the editor.

Pairs naturally with the WIT item catalog from the
preceding commit: each loot drop's itemId references an
entry in a WIT file, so a content pack ships both the
item definitions and the loot tables that reference them.
The runtime composes WIT + WLOT + WSPN to drive the full
"creature dies, drops items" flow without any SQL.

Format:
  • magic "WLOT", version 1, little-endian
  • per table: creatureId / flags / dropCount /
    moneyMin..Max / itemDropCount + drops[]
  • per drop: itemId / chancePercent (float, 0..100) /
    minQty / maxQty / drop_flags

Table flags: QuestOnly, GroupOnly, Pickpocket
Drop flags:  QuestRequired, GroupRollOnly, AlwaysDrop

dropCount is the slot budget — how many distinct drops
to roll per kill. Each item drop is rolled independently
against its chancePercent (so dropCount=2 with 4 candidate
drops at varying chances gives the classic "up to 2 distinct
items per kill" behavior). Drops with the AlwaysDrop flag
bypass the slot budget — used for guaranteed quest items.

API: WoweeLootLoader::save / load / exists /
findByCreatureId; presets makeStarter (1 table, 1 drop),
makeBandit (4 candidates, dropCount=2, matches the camp
spawns from WSPN at creatureId=1000), makeBoss (6 candidates
including guaranteed quest item via AlwaysDrop and a
group-only epic at 5%).

CLI added (5 flags, 486 documented total now):
  --gen-loot / --gen-loot-bandit / --gen-loot-boss
  --info-wlot / --validate-wlot

Validator catches: creatureId=0, duplicates, chance not in
0..100, NaN chance, money min > max, minQty > maxQty,
dropCount=0 with non-empty drops list (silent dead config).

All 3 presets save / load / re-validate clean. The bandit
table's creatureId=1000 deliberately matches WSPN's
makeCamp creatureId so the open-format demo content pack
already has working cross-references.
2026-05-09 15:11:08 -07:00
Kelsi
f85036d580 feat(editor): add --gen-texture-snowflake 6-fold ice crystal
84th procedural texture: 6-fold symmetric snowflake stamp
tiled per cell. Built by computing polar (r, theta) from
the cell center and folding theta into a [0, pi/6] wedge,
so a single arm-shape definition replicates 12 times via
mirror + 60-degree rotation.

Each arm is a thin sliver (sin-based perpendicular distance
test) thickened at two perpendicular knobs at r = 0.40 and
0.70 of the cell-half radius — the knobs provide the
classic "branched ice crystal" silhouette without needing
a separate per-arm subdivision pass.

A small filled center dot anchors the motif at small cell
sizes where the knobs vanish.

Useful for: arctic / winter zones, frost spell effects,
frost-mage themed gear icons, holiday-event decoration,
crystal-shrine backdrops, snowstorm overlays.

Distinct from --gen-texture-snow (random tiny dots, not a
patterned crystal) and --gen-texture-frost (spider-web
crackle, not radial). The first 6-fold-symmetric texture in
the catalogue.
2026-05-09 15:06:32 -07:00
Kelsi
9093975bdd feat(pipeline): add WIT (Wowee Item Template) format
Novel open replacement for Blizzard's Item.dbc +
ItemDisplayInfo.dbc + the SQL item_template tables that
AzerothCore-style servers store item definitions in. The
12th open format added to the editor.

A WIT file holds the catalog of all items in a content
pack: weapons, armor, consumables, quest items, trade
goods. Each entry pairs gameplay metadata (stats, level
reqs, flags, weapon damage / speed) with display metadata
(displayId for icon / model, quality color), so the
runtime can render inventory tooltips and equip slots
from a single load.

Format:
  • magic "WITM", version 1, little-endian
  • per item: itemId / displayId / quality / itemClass /
    itemSubClass / inventoryType / flags / requiredLevel /
    itemLevel / sellPrice / buyPrice / maxStack / durability
    / damageMin / damageMax / attackSpeedMs /
    statCount + stats[] / name / description

Enums:
  • Quality:       Poor..Heirloom (8 levels)
  • Class:         Consumable, Weapon, Armor, Quest, ... (13)
  • InventoryType: Head..Cloak..Weapon2H (18 slots)
  • Flags:        Unique, BoP, BoE, QuestItem, Conjured, ...
  • StatType:     Stamina, Strength, Intellect, Defense, ...

API: WoweeItemLoader::save / load / exists / findById;
presets makeStarter (4-item demo), makeWeapons (5 items
common -> legendary), makeArmor (6-piece mail set with
BoE flag).

CLI added (5 flags, 480 documented total now):
  --gen-items / --gen-items-weapons / --gen-items-armor
  --info-wit / --validate-wit

Validator catches: itemId=0, duplicate itemIds, weapons
with 0 damage or attackSpeed, weapons with non-weapon
slot, equippables with durability=0 or maxStack>1, sell
price >= buy price (vendor would lose money), out-of-range
quality.

All 3 presets save / load / re-validate clean. Info-table
output includes a gold/silver/copper price formatter for
hand-readability.
2026-05-09 15:04:48 -07:00
Kelsi
23f262c655 feat(editor): add WSPN JSON round-trip authoring workflow
Closes the WSPN open-format loop with --export-wspn-json /
--import-wspn-json, mirroring the WOL/WOW/WOMX/WSND JSON
pairs from earlier batches. All 5 binary formats added in
recent batches now have full JSON round-trip authoring.

Each entry round-trips all 12 fields:
  kind (int + kindName string), entryId, position[3],
  rotation[3], scale, flags (int + flagsList string array),
  respawnSec, factionId, questIdRequired, wanderRadius,
  label.

Vector fields are emitted as 3-element arrays for natural
JSON layout. Both kind and flags are emitted in dual form
(int + named) so a hand-author can write the named string
forms and skip the integer boilerplate. Missing optional
fields fall back to WoweeSpawns::Entry defaults.

Verified byte-identical round-trip on the village preset
(12 entries: 6 creature + 2 object + 4 doodad). The
position vec3 round-trips through floats with no precision
loss for the typical small-coordinate test cases.

Adds 2 flags (475 kArgRequired entries total).
2026-05-09 14:59:48 -07:00
Kelsi
88d1e6229f feat(pipeline): add WSPN (Wowee Spawn Point catalog) format
Novel open replacement for AzerothCore-style scattered
creature_template / gameobject SQL spawn tables PLUS the
ADT MDDF / MODF doodad-placement chunks. The 11th open
format, and the first that covers the live world-content
side (atmosphere + sounds + spawns now form the runtime
"what fills this zone" picture).

A WSPN file holds all spawn points for a zone in a single
table, with kind discriminating creature vs game object
vs static doodad. The same format powers:
  • server runtime  — knows what NPCs / objects to spawn
  • editor          — draws spawn markers
  • renderer        — reads the doodad subset directly to
                       draw static props without going
                       through a server roundtrip

Format:
  • magic "WSPN", version 1, little-endian
  • per entry: kind / entryId / position(3f) / rotation(3f)
    / scale / flags / respawnSec / factionId /
    questIdRequired / wanderRadius / label

Flags packed: disabled (0x01), event-only (0x02),
quest-phased (0x04). Reserved bits for future per-entry
encoding extensions.

API: WoweeSpawnsLoader::save / load / exists; presets
makeStarter (1 each kind), makeCamp (4-bandit ring +
chest + 2 tents), makeVillage (6 NPCs + 2 signs + 4
corner trees).

CLI added (5 flags, 473 documented total now):
  --gen-spawns / --gen-spawns-camp / --gen-spawns-village
  --info-wspn / --validate-wspn

Validator catches: out-of-range kind, NaN/inf coords,
non-positive scale, doodad with non-zero respawn (static
prop misuse), creature with respawn=0 (won't respawn after
kill), entryId=0 (orphan reference).

All 3 presets save / load / re-validate clean. Doodad and
game-object entries explicitly set wanderRadius=0 so the
generated catalogs are noise-free.
2026-05-09 14:57:53 -07:00
Kelsi
e30c81ddae feat(editor): add --gen-kitchen-pack composite (11th themed pack)
Tavern back-of-house / cookhouse scene composite. Pairs
naturally with --gen-tavern-pack (front-of-house common
room) for a complete inn setup.

Emits 7 .wom files into outDir:
  • stove        — pot-bellied cookfire stove
  • cauldron     — large hanging pot
  • prep-table   — flat work surface
  • stool        — cook's seat
  • barrel       — water / ale / flour storage
  • mug          — drink ready for the inn customer
  • mortar       — herb / spice grinding tool

This is the 11th themed pack (camp, blacksmith, village,
temple, graveyard, garden, dock, tavern, mining, arena,
kitchen). Notable for being the first pack that uses the
two recently-added drinking-vessel + alchemy primitives
(mug from batch 111, mortar-pestle from batch 113), proving
out the "build small primitives, then compose into themed
packs" workflow.

All 7 emitted primitives pass --validate-wom on first
generation. 468 kArgRequired entries total.
2026-05-09 14:53:01 -07:00
Kelsi
40a0194e18 feat(editor): add WSND JSON round-trip authoring workflow
Closes the WSND open-format loop with --export-wsnd-json /
--import-wsnd-json, mirroring the WOL/WOW/WOMX JSON pairs
from earlier batches.

Each entry round-trips all 9 fields:
  soundId, kind (int + kindName string), flags (int +
  flagsList string array), volume, minDistance, maxDistance,
  filePath, label.

Both kind and flags are emitted in dual form (int + named):
  • kind     : 2,
    kindName : "ambient",
  • flags    : 3,
    flagsList: ["loop", "3d"]

The importer accepts either form per field, so a hand-author
can write only the named string forms and skip the integer
boilerplate. Missing optional fields fall back to
WoweeSound::Entry defaults.

Verified byte-identical round-trip on the tavern preset
(5 entries with mixed flags and 3D distances).

Adds 2 flags (467 kArgRequired entries total). All 4 binary
formats added in recent batches now have full JSON round-trip:
WOL, WOW, WOMX, WSND.
2026-05-09 14:51:44 -07:00
Kelsi
36d63d8dd0 feat(pipeline): add WSND (Wowee Sound Catalog) format
Novel open replacement for Blizzard's SoundEntries.dbc +
SoundEntriesAdvanced.dbc. The 10th open format added to the
editor — covers the audio-metadata gap (the previous 9 cover
geometry, terrain, atmosphere, and world manifests, but no
sound metadata).

Format:
  • magic "WSND", version 1, little-endian
  • catalogName + entry count
  • per entry: soundId / kind / flags / volume /
    minDistance / maxDistance / filePath / label

Kind enum (7 categories):
  sfx, music, ambient, ui, voice, spell, combat

Flags packed (3 bits used, rest reserved):
  loop (0x01), 3d (0x02), stream (0x04)

API: WoweeSoundLoader::save / load / exists; presets
makeStarter (one entry per kind), makeAmbient (wilderness
loops + footsteps), makeTavern (fire + crowd + drink + door
+ lute).

CLI added (5 flags, 465 documented total now):
  --gen-sound-catalog <base> [name]
  --gen-sound-catalog-ambient <base> [name]
  --gen-sound-catalog-tavern <base> [name]
  --info-wsnd <base> [--json]
  --validate-wsnd <base> [--json]

Validator catches: out-of-range kind, NaN/inf volume or
distances, 3D sounds with bad min/max, duplicate sound IDs,
empty filePaths.

All 3 presets verified: save / load / validate clean
on first run. Variable-length string fields use length-
prefixed encoding with a 1 MiB sanity cap on read to
prevent corrupted-file allocation blowups.
2026-05-09 14:47:16 -07:00
Kelsi
13312096ea feat(editor): add --gen-mesh-mortar-pestle alchemy primitive
90th procedural mesh: a wider-than-tall closed cylinder
(the mortar bowl) with a thin tall closed cylinder (the
pestle) rising from its center. Bowl reads as carved stone
or wood; pestle reads as a small grinding rod centered in
the rim.

Distinct from existing kitchenware primitives:
  • --gen-mesh-cauldron — large + has supporting legs
  • --gen-mesh-chalice  — 3-tier goblet, no separate utensil
  • --gen-mesh-mug      — handled drinking cup
  • --gen-mesh-bowl     — does not exist; this primitive
                          covers the small-bowl + tool case

Useful for: alchemy lab counters, kitchen / cooking-stove
dressing, herbalist shop interiors, witchcraft NPCs,
quest-giver desks for "grind these herbs" objectives.

124 verts / 112 tris at default 14 sides — two simple
closed cylinders sharing a single batch with shared
texture coords.
2026-05-09 14:43:21 -07:00
Kelsi
de35ad7814 feat(editor): add --gen-texture-damask 4-petal wallpaper motif
83rd procedural texture: ornate damask wallpaper pattern.
Per cell: 4-fold radial petal lobes formed by sin(theta*2)^2,
faded out toward the cell edge, plus a small filled center
dot to anchor the motif visually.

Reads as gilded fabric / palace wallpaper / noble-faction
tapestry — the missing classic-ornament category in the
catalogue (chevron / herringbone / argyle / houndstooth
cover modern geometric patterns; damask covers the older
Western-European decorative arts side).

Useful for: throne-room walls, cathedral interiors, royal
banners, tavern wallpaper, cushion textures, scroll backgrounds,
manuscript decoration, alchemy-shop bottle-label backdrops.
2026-05-09 14:42:23 -07:00
Kelsi
3e3d6aca9a feat(editor): add WOMX JSON round-trip authoring workflow
Mirrors the WOL/WOW JSON pair from earlier batches: gives
hand-editable access to .womx world-map manifests for
quick tile-bitmap edits without writing a binary patcher.

Tile bitmap is represented as a JSON array of '1'/'0' row
strings — one string per row of the grid. Visual layout
makes missing-row patterns obvious at a glance:

  "tiles": [
    "10000001",
    "01000010",
    "00100100",
    "00011000",
    ...
  ]

Sparse [[x,y]] pair arrays were considered but rejected:
4× larger for a full continent (4096 tiles), and the dense
visual layout is far easier to spot-read for typical
edits like "carve out a hole in this region".

The importer tolerates missing optional fields (uses
WoweeWorldMap defaults), and accepts either worldType
int or worldTypeName string so JSON can be authored by
hand or by tools.

Verified byte-identical round-trip on a 4x4 instance and
a hand-authored 8x8 sparse continent (16/64 tiles, both
defaultLightId and defaultWeatherId preserved through the
JSON layer).

Adds 2 flags to reach 458 documented kArgRequired entries.
All 9 open formats now have established CLI tooling — WOM,
WOB, WOC, WOT, JsonDBC, PNG, WOL, WOW, and WOMX.
2026-05-09 14:39:47 -07:00
Kelsi
db47f00657 feat(pipeline): add WOMX (Wowee World Map index) format
Novel open replacement for Blizzard's WDT (top-level world
definition table). The 9th open format added to the editor.

A WOMX file holds the manifest of which terrain tiles exist
within a world plus a tiny bit of map-level metadata. The
runtime consults it before attempting to load any individual
tile (so missing tiles produce a clean "no data" result
instead of a file-not-found error).

Format:
  • magic "WMPX", version 1, little-endian
  • mapName + worldType (continent/instance/battleground/arena)
  • gridSize 1..128 (typically 64 for continents)
  • defaultLightId / defaultWeatherId (atmosphere preset
    refs, 0 if none — wires into the WOL/WOW pair)
  • packed bitmap, 1 bit per tile, row-major
  • A 64x64 manifest is exactly 512 bytes of bitmap

API: WoweeWorldMapLoader::save / load / exists; presets
makeContinent (64x64 full), makeInstance (4x4 full),
makeArena (1x1 full).

CLI added (5 flags, 456 total now):
  --gen-world-map <base> [name]            (continent)
  --gen-world-map-instance <base> [name]   (4x4)
  --gen-world-map-arena <base> [name]      (1x1)
  --info-womx <base> [--json]
  --validate-womx <base> [--json]

Round-trip verified: continent + instance + arena presets
all save / load / re-validate to byte-identical state with
correct tile counts.
2026-05-09 14:38:05 -07:00
Kelsi
6c3f5cb33f feat(editor): add --validate-wom static sanity checker
Adds a validator for .wom files mirroring --validate-wol /
--validate-wow. Catches malformed hand-built or import-
corrupted models before they reach the renderer (where bad
data usually crashes or renders blank with no diagnostic).

Hard errors (exit non-zero):
  • version not in 1..3
  • empty vertex / index list
  • index count not a multiple of 3
  • triangle indices referencing out-of-range vertices
  • boneIndices referencing out-of-range bones
  • parentBone referencing out-of-range bones
  • inverted AABB (boundMin > boundMax on any axis)
  • WOM3 batch.textureIndex out of range
  • WOM3 batch range past end of index buffer
  • animation has wrong number of bone tracks

Warnings (informational, exit zero):
  • boneWeight slots not summing to 0 or 255
  • triangles uncovered or double-covered by WOM3 batches
  • boundRadius <= 0 (frustum-cull failure)

Adds 451st kArgRequired entry. Smoke test: 0/0/0 errors on
all generated procedural primitives. Both text and --json
output supported, mirroring the other validators.
2026-05-09 14:34:22 -07:00
Kelsi
b907b69fa7 feat(editor): add --gen-mesh-mug tankard primitive
87th procedural mesh: drinking mug / tankard built from a
closed cylindrical body plus a thin side handle slab. The
handle is positioned on +X with its inner face flush against
the body and centered vertically.

Without rotation the handle is a solid box rather than a
real C-loop, so silhouette reads as a tankard handle rather
than a delicate ring — appropriate for the chunky-prop
aesthetic favored by the existing primitive set. Distinct
from --gen-mesh-chalice (3-tier goblet, no handle) and
--gen-mesh-well-pail (top horizontal handle bar).

Useful for tavern / banquet hall / kitchen dressing, inn
table props, and the eventual --gen-tavern-pack-2 follow-on
(once enough drinking-vessel primitives exist to justify a
dedicated drinkware composite).

86 verts / 68 tris at default 14 sides — a single batch
with shared body + handle texture coords.
2026-05-09 14:30:18 -07:00
Kelsi
706c820f8a feat(editor): add --gen-texture-moon disc with phase shadow
82nd procedural texture: solid filled circle of moonHex on
bg, with an optional crescent shadow created by subtracting
a second bg-colored disc offset by `phase` pixels along +X.

Phase reads as the lunar-cycle moment:
  • phase = 0           — full moon
  • 0 < phase < moonR    — gibbous (waning right)
  • phase = moonR        — half moon
  • phase > moonR        — crescent (thinner as phase grows)

Useful for night-sky overlays, banner moons (heraldry of
night-themed orders), shrine medallion centers, druid /
priest emblem inserts, lunar-festival decoration, vampire-
zone backdrops.

The simplest centered-disc texture in the catalogue —
pairs naturally with --gen-texture-stars for a complete
night-sky composite.
2026-05-09 14:26:57 -07:00
Kelsi
d9761046f9 feat(editor): add WOW JSON round-trip authoring workflow
Mirrors the WOL JSON pair from the previous batch — same
hand-edit authoring loop for the binary .wow weather format:

  • --export-wow-json <wow-base> [out.json]
        Dumps a .wow to a human-readable JSON sidecar
        (defaults to <base>.wow.json). Each entry includes
        BOTH the raw typeId (0..6) and the human-friendly
        type name ("clear" / "rain" / "snow" / "storm" /
        "sandstorm" / "fog" / "blizzard").

  • --import-wow-json <json-path> [out-base]
        Reads a JSON sidecar and writes back binary .wow.
        Accepts either typeId int OR type-name string
        (typeId wins if both present). Schema mismatches
        fail with a clear message.

Workflow: --gen-weather-* → --export-wow-json → hand-edit
weights / durations / intensities → --import-wow-json →
use in runtime.

Round-trip verified: elwynn.wow → elwynn.wow.json →
elwynn_rt.wow shows byte-identical entries via --info-wow.

Both atmosphere formats now have full JSON authoring support:
  WOL: --export-wol-json / --import-wol-json
  WOW: --export-wow-json / --import-wow-json
2026-05-09 14:25:41 -07:00
Kelsi
e7695d6e3b feat(editor): add --gen-mesh-well-pail wooden bucket primitive
86th procedural mesh primitive. Wooden well-pail / mop-
bucket:

  • body — closed cylindrical Y-axis cylinder via
    addClosedCylinderY (collision-watertight)
  • handle — thin horizontal box floating handleArc above
    the rim, spanning at least bodyR×2 wide so its ends
    align with the rim's outside edge

Without rotation the handle is a straight bar rather than
a true semicircle, but the bucket-with-handle silhouette
still reads correctly. handleArc parameter controls the
gap between rim and handle (small = handle touches rim;
larger = bucket-being-carried look).

Useful for well scenes (pairs with --gen-mesh-well),
servant-corridor mop scenes, kitchen prep stations,
homestead exteriors, dwarven mine shafts, ocean-ship
swabbing decks. Watertight under weld (verified 102
manifold edges, 0 boundary, 0 non-manifold).
2026-05-09 14:23:23 -07:00
Kelsi
40e25910b1 feat(editor): add --gen-zone-atmosphere paired light+weather composite
Convenience composite that drops both atmosphere.wol AND
atmosphere.wow into <zoneDir> in a single invocation, using
a paired light/weather preset:

  --preset default  → makeDefaultDayNight + makeTemperate
  --preset arctic   → makeNight            + makeArctic
  --preset desert   → makeDefaultDayNight  + makeDesert
  --preset stormy   → makeDefaultDayNight  + makeStormy
  --preset cave     → makeCave             + makeTemperate

The preset pairs are curated so the lighting and weather
match the zone climate (arctic uses always-night WOL +
arctic WOW; cave uses dim cave WOL + temperate WOW since
cave weather is rarely visible).

Useful as the canonical "give me a working atmosphere setup
for a fresh zone in one command" entrypoint. Both files
land at <zoneDir>/atmosphere.{wol,wow} where the runtime
loader can find them by convention.

Smoke-tested both default and arctic presets — both produce
files that pass --validate-wol and --validate-wow cleanly.
2026-05-09 14:21:55 -07:00
Kelsi
25ada9bbfb feat(editor): add --gen-texture-bayer ordered-dither pattern
81st procedural texture: classic 4x4 Bayer ordered-dither
matrix tiled across the image. Each pixel's color comes
from interpolating bg → fg by the matrix value at
(x mod 4, y mod 4) normalized to [0, 1]:

       0  8  2 10
      12  4 14  6
       3 11  1  9
      15  7 13  5

cellSize parameter scales the matrix block (default 4 px,
giving a 16-px seamless tile; cellSize=8 gives a 32-px
chunky retro look).

Useful for 8-bit / monochrome-CRT-style backdrops, ordered-
shadow approximations on low-bit palettes, retro arcade
splash overlays, paper-spritesheet rendering, and as a
deterministic alternative to --gen-texture-noise for
binary or near-binary palettes.
2026-05-09 14:20:02 -07:00
Kelsi
6c2f5cf9ca feat(editor): add WOL JSON round-trip authoring workflow
Two new commands enable a hand-edit authoring loop for the
binary .wol format:

  • --export-wol-json <wol-base> [out.json]
        Dumps a .wol to a human-readable JSON sidecar
        (defaults to <base>.wol.json). Preserves every
        keyframe's time, ambient/directional/fog colors,
        directional vector, and fog distances.

  • --import-wol-json <json-path> [out-base]
        Reads a JSON sidecar and writes back binary .wol.
        Validates schema strictly — missing keyframes /
        wrong field types fail with a clear error message.

Workflow: --gen-light → --export-wol-json → hand-edit values
in any text editor → --import-wol-json → use in renderer.

Round-trip verified byte-for-byte identical on the existing
sunny.wol fixture: re-import produces the same 4 keyframes
with the same colors, fog distances, and zone name.
2026-05-09 14:18:14 -07:00
Kelsi
35a5a9d09f feat(editor): add --gen-mesh-stove pot-bellied wood stove
85th procedural mesh primitive. Two-cylinder pot-bellied
stove silhouette:

  • body — wide round Y-axis cylinder (the stove body that
    holds the firebox)
  • chimney — thin tall Y-axis cylinder rising from the top
    of the body (the smoke vent)

Distinct from --gen-mesh-forge (square stone hearth + hood
+ chimney) and --gen-mesh-chimney (just the rectangular
shaft) — stove is the round-cylinder home / workshop
variant for cottages, smithies, alchemist labs, ranger
huts, dwarven mead-hall corner heaters.

Validates that chimneyR < bodyR so the silhouette always
reads as a wider-bottom-with-thinner-stack shape rather
than a flat tube.

Watertight under weld (verified 192 manifold edges, 0
boundary, 0 non-manifold).
2026-05-09 14:15:14 -07:00
Kelsi
bf8d37c145 feat(editor): add --validate-wow weather schedule audit
Mirrors --validate-wol but for the .wow weather format. Walks
every entry and reports structural problems:

  • unknown weather typeId (above the Blizzard sentinel)
  • intensity bounds outside [0, 1] or min > max
  • non-positive weight (would zero a row in weighted-random
    selection and warp probabilities)
  • zero or inverted duration bounds
  • non-finite floats

Returns exit code 0 on PASS / 1 on FAIL — CI-friendly. JSON
output via --json for tooling.

All four built-in presets (--gen-weather-temperate / arctic /
desert / stormy) validate clean. The WOW format now has
parity with WOL on the inspect/validate front:

   WOL: --info-wol / --info-wol-at / --validate-wol
   WOW: --info-wow / --validate-wow

Milestone: kArgRequired now at 440 documented flags.
2026-05-09 14:13:30 -07:00