Commit graph

3978 commits

Author SHA1 Message Date
Kelsi
5efd5b157d feat(editor): add --convert-m2-batch for bulk M2→WOM migration
Walks <srcDir> recursively for every .m2 file (case-insensitive,
including subdirs) and re-invokes --convert-m2 per file via a child
process so the existing single-file logic — AssetManager init, skin
file resolution, fromM2() pipeline — is reused verbatim. Per-file
[ok]/[FAIL] line streamed live; aggregate summary at the end.

Designed to migrate an entire creature/world model dump in one go.
This is the headline open-format conversion: every .m2 in a
proprietary data tree gets turned into a .wom open-format model. Pair
with the upcoming --convert-wmo-batch / --convert-blp-batch /
--convert-dbc-batch to migrate a complete extracted Data tree.

Verified discovery: 3 placeholder .m2 files (one in subdir, one
.M2 uppercase) found correctly; .txt skipped; all fail conversion as
expected for empty bytes; exit 1 on any failure. Brings command
count to 183.
2026-05-06 22:55:39 -07:00
Kelsi
f1bd7b7f1f feat(editor): add --remove-project-orphans for pre-pack cleanup
Destructive companion to --list-project-orphans. Reuses the same
reference-collection + orphan-detection logic, then deletes the
resulting .wom/.wob files. Honors --dry-run for safe previews.

Completes the list/remove cycle for unreferenced model files: run
--list-project-orphans to audit, then --remove-project-orphans
--dry-run to confirm, then drop --dry-run to actually clean. Useful
right before --pack-wcp so the archive doesn't carry dead weight.

Verified: dry-run preserves files (3 reported, all still present);
real run on a copy removes all 3 with no failures, freeing 1.2 KB.
Brings command count to 182.
2026-05-06 22:42:29 -07:00
Kelsi
0eb20a4069 feat(editor): add --list-project-orphans to find unreferenced models
Inverse of --list-zone-deps. Walks every zone in <projectDir>,
collects the set of .wom/.wob files on disk plus the set of paths
actually referenced by objects.json placements + WOB doodad lists,
and reports any model files in the first set but not the second.

Useful pre-pack cleanup — orphans bloat .wcp archives without
contributing to gameplay. The output table shows zone + path + bytes
so users can decide which to delete.

Comparison normalizes paths by stripping extensions and matching both
the full relative path and the leaf basename, so unqualified refs in
objects.json (e.g. just "cube_a" without ".wom") still resolve.

Verified: empty objects.json → 3 orphans across 2 zones; after
--add-object cube_a → only 2 orphans remain (cube_a correctly removed
from the list). Brings command count to 181.
2026-05-06 22:30:10 -07:00
Kelsi
91bcbb4891 feat(editor): add --info-project-density for cross-zone content rollup
Project-wide companion to --info-zone-density. Walks every zone in
<projectDir>, sums creatures/objects/quests, and emits a per-zone
breakdown table with per-tile averages plus project-wide totals.

Helps spot zones that are abnormally sparse (5 mobs across 16 tiles,
"why is this zone so empty?") or stuffed (200 mobs in 1 tile, frame-
rate bomb), and surfaces the project's overall content footprint.

Brings command count to 180 — 180 distinct CLI flags now.
2026-05-06 22:17:39 -07:00
Kelsi
e54c33a792 feat(editor): add --info-project-water for cross-zone water rollup
Project-wide companion to --info-zone-water. Walks every zone in
<projectDir>, sums water chunks/layers and liquid type counts per
zone, and emits a per-zone breakdown table plus project-wide totals
broken down by liquid type (water/ocean/magma/slime).

Useful for "do my coastal zones actually carry ocean data" sanity
checks and for budget planning when many zones share liquid types
(e.g. an archipelago where every zone has 256 water chunks per tile).

Brings command count to 179.
2026-05-06 22:06:22 -07:00
Kelsi
439cd943e4 feat(editor): add --info-project-extents for combined spatial bbox
Project-wide companion to --info-zone-extents. Walks every zone in
<projectDir>, computes each zone's tile XY range and Z height range,
then unions them into a project-wide world bounding box. Per-zone
breakdown table shows each zone's disjoint world ranges so overlap
issues are visible at a glance.

Useful for sizing the world map overview, sanity-checking that zones
don't overlap (project-union should equal sum of per-zone areas for
disjoint layouts), and understanding total project area in both
yards and meters.

Verified on 2-zone test project: each zone correctly resolves to one
533x533yd tile, project union is 1066x533yd (the two tiles arranged
horizontally), Z range matches manifest baseHeight + heightmap delta.
Brings command count to 178.
2026-05-06 21:55:27 -07:00
Kelsi
14492abc99 feat(editor): add --repair-project for project-wide manifest self-healing
Wraps --repair-zone across every zone in <projectDir>. Spawns the
binary per-zone so each zone's full repair report streams through,
with section markers between, then aggregates a final tally. Honors
--dry-run.

Calls fflush(stdout) before each child spawn so the parent's section
header lands ahead of the child's stdout (separate buffers per
process).

Verified: clean project → 0 fixes / exit 0; project with an orphan
tile file → "would add tile (5, 5) to manifest" / exit 0 (dry-run).
Brings command count to 177.
2026-05-06 21:44:47 -07:00
Kelsi
02c620e620 feat(editor): add --audit-project composite CI gate
Re-invokes the binary itself to run the four key per-project checks
back-to-back and rolls their exit codes into a single PASS/FAIL
verdict. Designed to be the only command CI needs to run before
--pack-wcp or release tagging.

Sub-checks: validate-project (format integrity), validate-project-
open-only (no proprietary leaks), check-project-refs (every model/NPC
ref resolves), check-project-content (sane field values). Sub-output
is suppressed; the audit's own report shows one line per check plus
the final overall result. Users rerun a failing sub-check directly
for detail.

Verified: clean project → all PASS, exit 0; project with a .blp leak
→ open-only gate fails as expected, OVERALL: FAIL exit 1. Brings
command count to 176.
2026-05-06 21:25:27 -07:00
Kelsi
33ddfa9afe feat(editor): add --list-project-textures for cross-zone texture rollup
Project-wide companion to --list-zone-textures. Walks every WOM across
every zone in <projectDir>, dedupes texture paths globally, and emits
both a per-zone WOM/unique-texture summary table and a project-global
texture set with usage counts.

Useful for "how many textures do I need to ship across the whole
project" — texture sharing across zones often makes the global set
much smaller than the per-zone sum, and the usage counts identify
shared workhorses vs zone-specific assets.

Brings command count to 175.
2026-05-06 21:14:57 -07:00
Kelsi
90a88e84a5 feat(editor): add --strip-project for project-wide derived-output cleanup
Wraps --strip-zone across every zone in <projectDir>. Removes derived
outputs (.glb/.obj/.stl/.html/.dot/.csv/.png/ZONE.md/DEPS.md) at each
zone's top level, leaving source files (zone.json + content JSONs +
open binary formats) untouched. Per-zone breakdown table plus aggregate
freed-bytes total. Honors --dry-run for safe previews.

Useful before --pack-wcp to keep release archives lean, or before a
git commit so derived blobs don't bloat history across many zones at
once.

Verified: dry-run shows "would-remove" totals matching the actual
delete pass on the 2-zone test project (870.8 KB per zone, 1741.6 KB
freed). Brings command count to 174.
2026-05-06 21:04:32 -07:00
Kelsi
f7bf1b026a feat(editor): add --validate-project-open-only release gate
CI-friendly check that exits 1 if any proprietary Blizzard asset
(.m2/.skin/.wmo/.blp/.dbc) remains in <projectDir>. Designed as the
final gate before shipping a wholly open-format project release —
once it returns exit 0 the project ships only WOM/WOB/PNG/JSON
content with no Blizzard binary payloads at rest.

Reports per-extension counts and lists the offending files (capped at
50 to avoid flooding the terminal on a wholly unmigrated project).

Verified: pass case on open-only test project (exit 0); fail case
after dropping fake .blp + .m2 (exit 1, both files listed under their
extensions). Brings command count to 173.
2026-05-06 20:54:41 -07:00
Kelsi
098674860e feat(editor): add --info-project-bytes with open-vs-proprietary split
Project-wide byte audit. Walks every zone in <projectDir>, re-uses the
--info-zone-bytes categorization, and emits a per-zone breakdown table
plus aggregated category totals. The headline is the open-vs-proprietary
size split — surfaces how much disk a project still spends on .m2/.wmo/
.blp/.dbc payloads vs the open WOM/WOB/PNG/JSON replacements.

Useful as a migration progress metric: an "open share" of 100% means
the project no longer ships any proprietary Blizzard assets at rest.

Verified: 2-zone test project shows 1246B open, 0B proprietary, 100%
open share (the expected state after WOM-only authoring). JSON form
emits the same numbers structured. Brings command count to 172.
2026-05-06 20:44:43 -07:00
Kelsi
2fa8bfe08a feat(editor): add --validate-project-checksum for cross-platform verification
Closes the emit/verify loop for --export-project-checksum natively, so
Windows runners and CI containers without coreutils' sha256sum can
still validate PROJECT_SHA256SUMS. Re-hashes every file listed in the
manifest, reports ok/missing/mismatched counts, and exits 1 on any
failure with a per-file failure list.

Verified all three paths: success (9 ok / exit 0), missing file
(1 missing / exit 1), mismatched hash (2 mismatched / exit 1).
Brings command count to 171.
2026-05-06 20:34:45 -07:00
Kelsi
13d7c42a96 feat(editor): add --info-project-models-total for cross-zone WOM/WOB rollup
Project-wide companion to --info-zone-models-total. Walks every zone in
<projectDir>, sums verts/tris/bones/anims/batches per WOM and groups/
verts/tris/doodads/portals per WOB, then prints a per-zone breakdown
table plus a TOTAL row. Both human (table) and --json output modes.

Useful for capacity planning across an entire content project — e.g.
"how many bones across all my creatures" or "total triangles per frame
if every model loaded simultaneously."

Verified on the 2-zone migration test project: per-zone counts match
--info-zone-models-total output, totals row aggregates correctly,
JSON form parses cleanly. Brings command count to 170.
2026-05-06 20:25:00 -07:00
Kelsi
576ffc38e0 feat(editor): add --export-project-checksum with project fingerprint
Project-wide companion to --export-zone-checksum. Walks every zone in
<projectDir>, hashes every source file, and emits PROJECT_SHA256SUMS in
the standard sha256sum format with paths kept relative to projectDir
(so entries look like "<hex>  <zone>/<file>"). Also computes a single
SHA-256 fingerprint over the manifest body — a one-line identity for
the whole project, useful for CI release gates and reproducibility.

Verified: external 'sha256sum -c PROJECT_SHA256SUMS' passes all 9
entries on a 2-zone test project, and standalone 'sha256sum
PROJECT_SHA256SUMS' matches the emitted fingerprint byte-for-byte.

Adds wowee_sha256::hex(buf, len) helper. Brings command count to 169.
2026-05-06 20:15:45 -07:00
Kelsi
82fc1dbd80 feat(editor): add --migrate-project for multi-zone WOM upgrades
Wraps --migrate-zone across every zone under <projectDir>, so a single
invocation upgrades legacy WOM1/WOM2 files to WOM3 with batches[]
populated across an entire content project. Per-zone breakdown table
shows scanned/upgraded/already-v3/failed counts for visibility.

Verified end-to-end: scaffold 2 zones, drop 3 v1 cubes, run --migrate-
project once → 3/3 upgraded; second run → 0 upgraded, 3 already-v3
(idempotent). Brings total command count to 168.
2026-05-06 20:05:51 -07:00
Kelsi
b831bbcbc9 fix(editor): toast on non-finite fly-to target instead of silent no-op
Camera::setPosition now refuses NaN, so flyToSelected() would silently
do nothing if the selected entity had a corrupted position. Surface it
as a toast so the user knows the selection is unusable rather than
wondering why the camera didn't move.
2026-05-06 20:05:45 -07:00
Kelsi
072513d0f9 feat(editor): add --info-zone-models-total for aggregate WOM/WOB stats
Aggregates WOM/WOB stats across every model in a zone. Useful for
capacity planning and perf budgeting:

  wowee_editor --info-zone-models-total custom_zones/MyZone

  Zone models total: custom_zones/MyZone

    WOM (open M2):
      files     : 47
      vertices  : 184320
      triangles : 122880
      bones     : 612
      anims     : 94
      batches   : 188

    WOB (open WMO):
      files     : 8
      groups    : 32
      vertices  : 28480
      triangles : 18960
      doodads   : 156
      portals   : 24

    Combined  :
      vertices  : 212800
      triangles : 141840

Per-format aggregation (WOM = open M2, WOB = open WMO) plus a
combined totals row. Catches:
- 'how many bones across all my creatures?' (rigging budget)
- 'total triangles per frame if all loaded?' (perf ceiling)
- 'do my models actually have multi-batch material support?'
  (counts batch entries; 0 means everything is WOM1/WOM2)

Walks the zone dir recursively so models in subdirs (creatures/,
buildings/, props/) all roll up. JSON mode emits per-format records
for capacity dashboards.
2026-05-06 19:53:31 -07:00
Kelsi
5b719eaa56 feat(editor): add --check-project-refs for project-wide reference validation
Project-level cross-reference checker. Walks every zone and runs the
same model-path / NPC-id checks --check-zone-refs does, with per-zone
breakdown:

  wowee_editor --check-project-refs custom_zones

  check-project-refs: custom_zones
    zones        : 12 (2 failed)
    total missing: 5

    zone                       obj_chk obj_miss  q_chk  q_miss  status
    AshenForest                     12        0      4       0  PASS
    BoneOasis                       17        2      6       0  FAIL
    Stranglethorn                   34        1      8       2  FAIL
    ...

    2 zone(s) have dangling refs

Per-zone columns split objects vs quests so designers see at a
glance whether the issue is missing models (asset bundle drift) or
NPC IDs (creature definitions out of sync with quest references).

Pairs with --check-project-content (data quality) and
--validate-project (binary integrity). Together the three give a
complete pre-ship validation gate:
  --validate-project        binary structure
  --check-project-content   field plausibility
  --check-project-refs      cross-references resolve

Verified on a 2-zone project: Forest mvp-zone has 1 unresolved
object placement (no Tree.m2 on disk in test cwd), Bad zone has
1 missing object → 2 zones FAIL, exit 1.
2026-05-06 19:44:28 -07:00
Kelsi
20bd417002 feat(editor): add --export-bones-dot for Graphviz bone-tree visualization
Renders WOM bone hierarchy as Graphviz DOT. Mirrors --export-quest-graph
for skeleton trees: a 50-bone tree from --info-bones is hard to read in
text; pipe this through 'dot -Tpng' for the picture:

  wowee_editor --export-bones-dot HumanMale
  dot -Tpng HumanMale.bones.dot -o bones.png

Visual encoding:
- lightgreen fill: keybones (named anchor points referenced by gameplay
  systems — head, hands, feet, etc.)
- lightgrey fill: internal/blend bones (non-key, used for shape only)
- goldenrod border: root bones (parent=-1, top of skeleton hierarchy)

Edges flow parent -> child (rankdir=TB so root is at top, leaves at
bottom).

Useful for skeleton-debugging:
- 'why is this finger not following its hand?' -> see the parent chain
- 'is this bone really a root or did its parent get deleted?' ->
  goldenrod border makes intentional roots vs accidental ones obvious
- understanding which bones gameplay code can reference by key id

Verified on a 5-bone synthesized skeleton (3-deep chain + 1 detached
root + mix of key/internal): DOT correctly emits 5 nodes with the
right colors (3 lightgreen for key bones, 2 lightgrey for internal,
2 with goldenrod root borders), 3 edges traversing the chain.
2026-05-06 19:35:32 -07:00
Kelsi
8d9690a57a feat(editor): add --remove-zone for safe zone deletion
Counterpart to --scaffold-zone / --copy-zone / --rename-zone — completes
the zone-lifecycle CRUD. Defense-in-depth against accidental
destruction:

  wowee_editor --remove-zone custom_zones/Doomed

  remove-zone: custom_zones/Doomed ('Doomed')
    would delete: 6 file(s), 174.9 KB
    re-run with --confirm to actually delete

  wowee_editor --remove-zone custom_zones/Doomed --confirm

  Removed custom_zones/Doomed ('Doomed')
    deleted: 7 filesystem entries, 174.9 KB freed

Two-step safety:
1. Without --confirm: dry-run that lists what would be deleted
   (file count + total bytes + zone display name from manifest).
2. With --confirm: actually wipes the directory.

Belt-and-suspenders refusal: even with --confirm, refuses to delete
anything that doesn't have a zone.json at the top level. Catches
typos like '--remove-zone .' that would otherwise nuke an entire
project.

Why not just 'rm -rf'? --remove-zone gives:
- Per-zone display name in the confirmation
- Byte-count audit before deletion
- The non-zone-dir guard (rm doesn't know what a zone is)
- Symmetric with the rest of the zone-lifecycle CLI

Verified: dry-run lists 6 files / 175 KB; '. --confirm' correctly
refused (no zone.json at top level); zone-dir --confirm wiped 7
fs entries with byte tally.
2026-05-06 19:26:10 -07:00
Kelsi
bc9033eb43 feat(editor): add --info-pack-tree for WCP directory hierarchy view
Tree view of a WCP archive's contents with per-file byte sizes.
--list-wcp shows the flat sorted file list; this gives the
hierarchical view that's easier to read for archives with
subdirectories:

  wowee_editor --info-pack-tree custom_zones/MyZone.wcp

  custom_zones/MyZone.wcp  (47 files, 2348.21 KB)
  ├─ Forest_28_30.whm  (150540 bytes)
  ├─ Forest_28_30.wot  (26685 bytes)
  ├─ buildings/
  │  ├─ inn.wob  (45120 bytes)
  │  └─ tavern.wob  (38104 bytes)
  ├─ creatures.json  (694 bytes)
  ├─ data/
  │  ├─ Spell.json  (15032 bytes)
  │  └─ Item.json  (8194 bytes)
  ├─ objects.json  (234 bytes)
  └─ zone.json  (500 bytes)

Recursive renderer with UTF-8 box-drawing connectors. Files show
their byte size; directories show the subtree subtotal aggregated
from children. Children sorted alphabetically (std::map).

Pairs with --info-pack-budget (per-extension byte breakdown) and
--list-wcp (flat sorted list) — three lenses on the same archive:
hierarchy / extension cost / flat search.

Verified on a 6-file mvp-zone WCP: tree correctly shows top-level
files (no subdirs in mvp-zone output) with byte sizes and total
175 KB summary.
2026-05-06 19:17:42 -07:00
Kelsi
4d78b9dbf1 feat(editor): add --check-project-content for project-wide content validation
Project-level content sanity check. Walks every zone and runs the
same per-zone checks --check-zone-content does, aggregating warnings
per zone. Designed for CI gates before --pack-wcp:

  wowee_editor --check-project-content custom_zones

  check-project-content: custom_zones
    zones        : 12 (2 failed)
    total warns  : 7

    zone                       creat  object   quest  status
    AshenForest                    0       0       0  PASS
    BoneOasis                      3       0       1  FAIL
    CrystalCaverns                 0       0       0  PASS
    Stranglethorn                  2       1       0  FAIL
    ...

    2 zone(s) have content warnings

Per-zone warning columns (creature/object/quest) make it instantly
clear which content category needs attention. Exit 1 if any zone
has any warning so CI can gate.

Pairs with --validate-project (binary integrity) — both needed for
release gates: a zone can have valid file structure (validate
passes) AND content data-quality issues (check-content fails).

Verified on a 2-zone project: mvp-zone Forest (all defaults sane)
PASS, Empty zone with displayId=0 creature FAIL with 1 warning,
exit 1.
2026-05-06 19:08:31 -07:00
Kelsi
dc7aec507f feat(editor): add --export-zone-spawn-png for top-down spawn-position maps
Renders a top-down PNG showing creature + object spawn positions
colored by type. Bound by the zone's tile range so the image is
properly framed at zone scale:

  wowee_editor --export-zone-spawn-png custom_zones/MyZone
  # -> custom_zones/MyZone/MyZone_spawns.png

Layout:
- Tile-grid lines at tile boundaries (subtle grey on dark grey)
- Red 3×3 dots: creature spawns
- Green 3×3 dots: M2 object placements
- Blue 3×3 dots: WMO object placements
- 256 px per tile (so a 4-tile zone is 512×512); cap at 4096
  largest dim for huge multi-tile projects

WoW coord -> image transform: +X world is north (up in image),
+Y world is west (left in image). Same convention --info-tilemap
uses, so the spawn map and the tilemap line up visually.

Useful for design review ('does the spawn distribution match the
encounter design?'), screenshot bait for blog posts, and instant
visual validation of new content before opening the GUI.

Verified on a 1-tile mvp-zone with 3 creatures + 1 object plotted:
256×256 RGB PNG, dots placed at expected positions, --info-png
confirms the output is well-formed (8-bit RGB, 2184 bytes).
2026-05-06 18:59:44 -07:00
Kelsi
2604269fb1 feat(editor): add --bench-bake-project for terrain-load timing analysis
Companion to --bench-validate-project. Times the WHM/WOT load step
(the dominant cost in --bake-zone-glb/obj/stl) per zone. The actual
write side adds ~constant cost proportional to vertex count, so
load time is a strong proxy for bake cost:

  wowee_editor --bench-bake-project custom_zones

  Bench bake (load-only): custom_zones
    zones    : 12
    total    : 4731.20 ms (terrain load)
    per zone : avg=394.27 min=87.42 max=2103.55 ms
    slowest  : Stranglethorn (2103.55 ms)

    Per-zone:
      zone                       ms     tiles  chunks  ms/tile
      AshenForest                  87.42      1     256    87.42
      BoneOasis                   245.10      4    1024    61.27
      Stranglethorn              2103.55      9    2304   233.73

Useful for tracking 'has my latest geometry change made baking 3×
slower?' across releases. Per-tile timing surfaces zones with
high-variance chunk loads (e.g. dense doodad placements that
inflate ADT load time).

Pairs with --bench-validate-project (validation timing). Both use
std::chrono::steady_clock for monotonic measurement; JSON modes
emit per-zone records for CI dashboard consumption.

Verified on a 2-zone project (Forest 2 tiles + Desert 1 tile):
correctly reports per-zone timings + ms/tile ratio.
2026-05-06 18:50:20 -07:00
Kelsi
0cdefb2611 feat(editor): add --info-glb-bytes for per-section GLB byte breakdown
Drills into a .glb's byte composition. Pairs with --info-glb (counts)
and --info-glb-tree (structure) — three lenses on the same file:

  wowee_editor --info-glb-bytes custom_zones/Z/Z.glb

  GLB bytes: custom_zones/Z/Z.glb
    total: 891736 bytes (0.85 MB)

    Sections:
      header     :    12 bytes   0.00%
      JSON hdr   :     8 bytes   0.00%
      JSON       :   828 bytes   0.09%
      BIN hdr    :     8 bytes   0.00%
      BIN        : 890880 bytes  99.90%

    BufferViews:
      idx  target   bytes      MB    share-of-bin
        0  vertex     248832    0.24  27.93%
        1  vertex     248832    0.24  27.93%
        2  index      393216    0.38  44.14%

    By attribute:
      INDICES        393216 bytes  (44.14% of BIN)
      NORMAL         248832 bytes  (27.93% of BIN)
      POSITION       248832 bytes  (27.93% of BIN)

Three breakdowns:
- Section costs (header + JSON chunk + BIN chunk + their headers)
- Per-bufferView with target hints (vertex / index / other)
- Per-attribute (POSITION/NORMAL/TEXCOORD_0/INDICES/etc.) bucketed
  by walking accessors referenced from primitives

Catches asymmetric BIN allocation ('why is INDICES 44% of my .glb?')
and helps tune vertex layout decisions (drop normals if synthesized,
quantize positions if precision allows). Verified on a single-tile
zone bake: header+JSON is 0.1% of total, BIN is 99.9%, attribute
breakdown shows POSITION/NORMAL each 28% and INDICES 44%.
2026-05-06 18:41:55 -07:00
Kelsi
339256a794 feat(editor): add --info-extract-budget for sortable extract-dir byte breakdown
Companion to --info-pack-budget (which operates on .wcp archives).
Per-extension byte breakdown of an extract dir, sorted largest-first.
Answers 'where did my 31 GB extract go?' with a flat sortable table:

  wowee_editor --info-extract-budget /home/k/Desktop/wowee/Data

  Extract budget: /home/k/Desktop/wowee/Data
    total: 284613 file(s), 31482.11 MB

    ext           count        bytes        MB    share
    .adt          11213  11985924414   11430.7   36.3%
    .wav          39396   8107038542    7731.5   24.6%
    .blp         133742   4990640480    4759.4   15.1%
    .m2           48466   2568180656    2449.2    7.8%
    .wmo          16526   2286454107    2180.5    6.9%
    .mp3           1222   1976864519    1885.3    6.0%
    ...

Caps to top 30 extensions with the rest rolled into '(other)' so
big extracts (this one has 30+ format types) don't drown the
output.

Pairs with --info-extract-tree (hierarchical view) and
--info-extract (sidecar coverage) — three lenses on an extract
directory: structure, formats, byte costs.

Verified on a real 31GB Data/ extract: ADT files dominate at 36%
(11GB), with WAV audio second at 25% (8GB).
2026-05-06 18:33:29 -07:00
Kelsi
3e260453a5 feat(editor): add --info-quests-by-level + --info-quests-by-xp analytics
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Quest-side analytics paralleling --info-creatures-by-faction/-level.
Two distribution views for difficulty-curve and reward-pacing analysis:

  wowee_editor --info-quests-by-level $Z/quests.json

  Quests by required level: ... (47 total)
    range : 1 to 60 (avg 22.4)

    level   count  bar
        1       8  ████████████████████████████████████████
        5       6  ██████████████████████████████
        ...
       60       1  █████

  wowee_editor --info-quests-by-xp $Z/quests.json

  Quests by XP reward: ... (47 total)
    range : 100 to 5000 (avg 1462, 0 with 0 XP)

    bucket (≥XP)   count  bar
               0       8  ████████████████████████████████████████
             250       6  ██████████████████████████████
             500       4  ████████████████████
            5000       1  █████
    (bucket size: 250 XP)

--by-level: catches difficulty-curve gaps (every quest level 1 → no
mid-game; cluster at 60 → no early game) and outliers (level-30
quest dropped into a starter zone).

--by-xp: bucket size auto-grows with the max XP value so the
histogram stays readable for both starter zones (10-100 XP per bin)
and endgame (5000+ XP per bin). Surfaces no-reward quests
explicitly so designers spot ones they forgot to fill in.

JSON modes emit per-bucket records for dashboards. Verified on a
4-quest seed (xp 100/250/500/5000): bucket-size correctly auto-
selected as 250 XP, range and avg match.
2026-05-06 18:20:37 -07:00
Kelsi
d12ea8e23e feat(editor): add --for-each-tile for per-tile batch operations
Per-tile batch runner. Pairs with --for-each-zone (project-level
tile iteration is too coarse for tile-level commands like
--build-woc, --validate-whm, --info-whm).

  wowee_editor --for-each-tile custom_zones/MyZone -- \
    wowee_editor --build-woc {}

  [custom_zones/MyZone/MyZone_30_30 (30, 30)]
  WOC built: custom_zones/MyZone/MyZone_30_30.woc (32768 triangles, ...)
  [custom_zones/MyZone/MyZone_31_30 (31, 30)]
  WOC built: custom_zones/MyZone/MyZone_31_30.woc (32768 triangles, ...)

  for-each-tile: 2 tiles, 0 failed

The {} substitution receives the tile-base path (zoneDir/mapName_TX_TY)
which is the form most tile-level commands accept. Sorts tiles by
(tx, ty) so output ordering is deterministic.

Use cases:
- Build WOC for every tile in one shot after editing terrain
- Validate WHM headers across all tiles
- Export per-tile previews via --export-whm-obj
- Per-tile data dump via --info-whm

Same shell-escaping + cmd-substitution machinery as --for-each-zone
(safe against names with spaces and quotes). Returns failed-run
count as exit code (capped at 255).

Verified on a 2-tile zone running --build-woc per tile: both
tiles built correctly, exit 0.
2026-05-06 18:11:54 -07:00
Kelsi
aba0a63dd0 feat(editor): add --bench-validate-project for validation timing analysis
Times --validate-project per zone. Useful for catching unusually
slow zones (huge WHM/WOC pairs, lots of WOM batches) and tracking
validation overhead growth across releases:

  wowee_editor --bench-validate-project custom_zones

  Bench validate: custom_zones
    zones    : 12
    total    : 4731.20 ms
    per zone : avg=394.27 min=87.42 max=2103.55 ms
    slowest  : Stranglethorn (2103.55 ms)

    Per-zone timings:
      zone                       ms       files  ms/file
      AshenForest                  87.42      8   10.928
      BoneOasis                   245.10     17   14.418
      Stranglethorn              2103.55     94   22.378
      ...

Reports total + avg/min/max per zone + slowest zone callout. Per-
zone table with ms/file ratio surfaces formats that scale poorly
('this zone has only 5 files but takes 2 seconds — one is a 100MB
WHM that's slow to load').

Useful for:
- Pre-commit profiling: 'did my validator change make Stranglethorn
  3× slower?'
- CI cycle time budgeting
- Catching pathological inputs (a 10000-creature creatures.json that
  blows up validation cost)

JSON mode emits per-zone records + aggregate stats for dashboards.
Verified on a 2-zone project: per-zone timings make sense (Forest
with .woc takes 5× longer than Desert without).
2026-05-06 18:03:55 -07:00
Kelsi
33b231b8a2 feat(editor): add --info-zone-density for spawn distribution analysis
Per-tile content density. Catches sparse zones (5 mobs across 16
tiles → boring) and over-stuffed ones (200 mobs in 1 tile → frame-
rate bomb). Reports overall averages plus per-tile bucket counts:

  wowee_editor --info-zone-density custom_zones/MyZone

  Zone density: custom_zones/MyZone
    tiles      : 4
    totals     : 47 creatures, 23 objects, 8 quests
    per-tile   : 11.75 creatures, 5.75 objects, 2.00 quests

    Per-tile breakdown:
      tile        creatures  objects
      (28, 30)            12        7
      (29, 30)            18        4
      (29, 31)             9        8
      (30, 30)             8        4

Spawn-to-tile bucketing reverses the WoW grid transform from world
position back to tile (tx, ty) coords. Out-of-zone spawns silently
drop (they show up in --check-zone-refs / --check-zone-content as
their own warning class).

Useful for difficulty-curve work ('how packed is this hub vs this
zone-edge?'), perf budgeting ('which tile do I need to lod-out
first?'), and content-pacing reviews ('is the early game too empty?').

JSON mode emits per-tile records for dashboards. Verified on a
1-tile mvp-zone: 1 creature + 1 object + 1 quest, all bucketed
into the correct tile (28, 30).
2026-05-06 17:55:15 -07:00
Kelsi
f4e8623d58 feat(editor): add --info-objects-by-path + --info-objects-by-type analytics
Object-side counterparts to --info-creatures-by-faction/-level. Two
analytics commands for placement audits:

  wowee_editor --info-objects-by-path $Z/objects.json

  Objects by path: ... (47 total, 12 unique)
    count   share   path
       18    38.3%  World/Generic/Tree.m2
        9    19.1%  World/Generic/Lamp.m2
        4     8.5%  World/Building/Inn.wmo
        ...

  wowee_editor --info-objects-by-type $Z/objects.json

  Objects by type: ...
    M2  : 38  (scale 0.80-2.50, avg 1.12)
    WMO : 9   (scale 1.00-1.00, avg 1.00)

--info-objects-by-path: most-used model paths first. Catches
'this looks repetitive, diversify the doodads' design feedback
and surfaces texture-budget hot spots (one model used 50× pulls
its textures into the working set 50×).

--info-objects-by-type: M2 vs WMO split + per-type scale stats.
Catches scale outliers ('this WMO is at 0.001 scale, did you mean
1.0?') and gives composition sense (mostly props vs mostly
buildings).

JSON modes emit per-path / per-type records for dashboards.
Verified on a 4-object seed (3 M2 + 1 WMO with mixed scales):
correctly reports Tree.m2 used 2× (50%), M2 scale range 0.80-1.50
avg 1.10.
2026-05-06 17:46:51 -07:00
Kelsi
1797ffd280 feat(editor): add --info-pack-budget for per-extension WCP byte breakdown
Where --info-wcp shows file counts per category, this drills into
per-extension byte costs so users can spot what's bloating an
archive before shipping:

  wowee_editor --info-pack-budget custom_zones/MyZone.wcp

  WCP budget: custom_zones/MyZone.wcp
    total: 47 file(s), 2.34 MB

    ext           count        bytes      KB    share
    .whm              4      1683456  1644.0   70.3%
    .wob              3       451200   440.6   18.8%
    .wom             12       163840   160.0    6.8%
    .json             8        85120    83.1    3.6%
    .woc              1        12672    12.4    0.5%

Sorted by bytes descending so the heaviest contributors surface
first. Useful for:
- Spotting accidental .glb/.obj inclusion in shipping packs
  (`--pack-wcp` should run after `--strip-zone` to keep
  derived outputs out)
- Capacity budgeting when targeting a max-pack-size
- Comparing pre/post compression ratios

Pairs with --info-wcp (counts), --list-wcp (full file list),
--diff-wcp (compare two packs), --info-pack-budget (this one,
byte costs).

Verified on a freshly-mvp-zone packed WCP: 6 files / 0.17 MB
correctly broken down (whm 84%, wot 14.9%, json 1.1%).
2026-05-06 17:38:28 -07:00
Kelsi
baf54d5e47 feat(editor): add --info-zone-water for water-layer aggregation
Aggregates water-layer stats across every tile in a zone. Useful for
confirming a 'lake zone' actually has water, budgeting water-heavy
zones, or auditing what liquid types appear (water vs ocean vs magma
vs slime affects gameplay rules):

  wowee_editor --info-zone-water custom_zones/Z

  Zone water: custom_zones/Z
    loaded tiles : 1
    water chunks : 0 (out of 256 possible)
    total layers : 0
    (no water in this zone)

  wowee_editor --info-zone-water custom_zones/Stranglethorn

  Zone water: custom_zones/Stranglethorn
    loaded tiles : 4
    water chunks : 387 (out of 1024 possible)
    total layers : 412
    height range : 12.40 to 18.50

    By liquid type:
      water (0): 380 layer(s)
      ocean (1): 28 layer(s)
      magma (2): 4 layer(s)

Per-chunk water can have multiple layers (different liquid types or
height regions overlapping). Liquid types: 0=water, 1=ocean, 2=magma,
3=slime — different gameplay rules apply (oceans are swimable, magma
is damage-over-time, etc.).

JSON mode emits per-type layer counts + height range for programmatic
audit. Verified on a freshly-scaffolded zone: correctly reports 0
water chunks.
2026-05-06 17:30:05 -07:00
Kelsi
8257ae72db feat(editor): add --validate-project for whole-project validation gate
Multi-zone wrapper around --validate-all. Walks every zone in a
project and runs the per-format validators (WOM/WOB/WOC/WHM).
Aggregates pass/fail per zone with file-level breakdown:

  wowee_editor --validate-project custom_zones

  validate-project: custom_zones
    zones        : 12 (1 failed)

    zone                       files  failed  errors  status
    AshenForest                    47       0       0  PASS
    BoneOasis                      31       0       0  PASS
    CrystalCaverns                 28       2      14  FAIL
    ...

    1 zone(s) failed validation

Designed for CI gates before --pack-wcp (or before tagging a release):
one command checks the whole project's binary integrity, exits 1 if
anything's broken with a clear breakdown of which zone went wrong.

Pairs with --validate-all (single-zone, all formats) and the
per-format validators (--validate-wom etc.). Three levels of
granularity now:
  --validate-{wom,wob,woc,whm}  single file
  --validate-all                single zone (or any dir)
  --validate-project            entire project

JSON mode emits per-zone records (totalFiles + failedFiles +
totalErrors) for dashboard consumption. Verified on a 2-zone
project with one tile having .woc built and one without — both
zones PASS, exit 0.
2026-05-06 17:21:57 -07:00
Kelsi
e0ed2ab58e feat(editor): add --info-creatures-by-faction + --info-creatures-by-level
Two analytics commands for combat-balance work. Where --info-creatures
gives totals + behavior counts, these give the distributions:

  wowee_editor --info-creatures-by-faction $Z/creatures.json

  Creatures by faction: ... (47 total)
    faction    count   share
          7        12    25.5%
         14        29    61.7%
         35         6    12.8%
    (factions: 7=human, 14=monster, 35=neutral, etc.)

  wowee_editor --info-creatures-by-level $Z/creatures.json

  Creatures by level: ... (47 total)
    range : 5 to 32 (avg 14.2)

    level   count  bar
        5       4  ████████████████████████████████████████
        6       3  ██████████████████████████████
        ...
       30       1  ██████████

Faction histogram catches single-faction zones (one giant melee) and
mixed-faction tuning issues. Level histogram catches difficulty-curve
problems (cluster at 5, gap, cluster at 30) and outlier spawns
(level-60 boss accidentally placed in starter area).

ASCII bar chart for level distribution since gameplay tuning is
visual — '60% of mobs are levels 8-12 with a long tail' is more
intuitive as a bar than as numbers. Bars scale to longest bin so
small zones still get usable visualization.

JSON mode emits per-faction / per-level records for dashboards.
Verified on a 4-creature seed (3×faction-14 + 1×faction-35; levels
7/8/12/30): faction percentages and level range/avg both correct.
2026-05-06 17:13:44 -07:00
Kelsi
1eb8232bb8 feat(editor): add --validate-cli-help self-check for documentation drift
Self-check that every flag in kArgRequired (the master list of
commands needing positional args) appears in the help text emitted
by printUsage. Catches drift where a handler+arg-check pair gets
added but the help line is forgotten:

  wowee_editor --validate-cli-help

  CLI help self-check
    kArgRequired entries : 126
    PASSED — every kArgRequired flag is documented

If it ever fails, the missing flags are listed:

    FAILED — 2 flag(s) missing from help text:
      - --new-thing-i-forgot-to-document
      - --another-undocumented-flag

CI integration: add this to the test target so a PR that adds a
new command without docs can't merge. The --info-cli-stats command
gives the surface-size view; this gives the surface-completeness
view.

Verified: ran on the current binary (126 kArgRequired entries),
PASSED — every flag is documented.
2026-05-06 17:04:57 -07:00
Kelsi
d048beaa1e feat(editor): add --info-project-tree for multi-zone project overview
Project-level tree view: every zone with quick counts + bake/viewer
artifact status. --info-zone-tree drills into one zone; this gives
the bird's-eye view across the whole project:

  wowee_editor --info-project-tree custom_zones

  custom_zones/  (2 zones, 2 tiles, 1 creatures, 1 objects, 1 quests)
  ├─ Empty/  (tiles=1, creat=0, obj=0, quest=0)
  │  ├─ name      : Empty
  │  ├─ mapName   : Empty
  │  ├─ artifacts : (none)
  │  └─ status    : empty (only terrain)
  └─ Forest/  (tiles=1, creat=1, obj=1, quest=1)
     ├─ name      : Forest
     ├─ mapName   : Forest
     ├─ artifacts : .glb
     └─ status    : populated

Per-zone summary line shows counts; sub-tree shows display name,
map slug, which derived artifacts have been baked (.glb / .obj /
.stl / .html / ZONE.md), and a populated/empty status.

Project-header line aggregates totals across all zones for the
'how big is my project?' answer in one glance.

Pairs with --info-tilemap (spatial coverage) and --zone-stats
(quantitative aggregate) — three different lenses on the project:
spatial, quantitative, and structural.
2026-05-06 16:57:26 -07:00
Kelsi
2152b230c8 feat(editor): add --export-project-md for GitHub-renderable project README
Markdown counterpart to --export-project-html. Generates a README.md
indexing every zone with counts + bake/viewer/doc artifact status.
GitHub renders it natively at the project root:

  wowee_editor --export-project-md custom_zones

# Wowee Project — Zone Index

*Auto-generated. 2 zone(s) discovered in `custom_zones`.*

## Summary

| Metric | Total |
|---|---:|
| Zones      | 2 |
| Tiles      | 2 |
| Creatures  | 2 |
| ...

## Zones

| Zone | Tiles | Creatures | Objects | Quests | Bake | Viewer | Docs |
|---|---:|---:|---:|---:|:---:|:---:|:---:|
| Desert | 1 | 1 | 1 | 1 | — | — | — |
| [Forest](Forest/ZONE.md) | 1 | 1 | 1 | 1 | ✓ | [view](Forest/Forest.html) | [md](Forest/ZONE.md) |

Per-zone row links to its ZONE.md (if --export-zone-summary-md was
run) and its HTML viewer (if --export-zone-html was run). The Bake
column shows ✓ if .glb exists. Status columns make it instantly
visible which zones are bake-ready vs documentation-only.

Pairs with --export-project-html (interactive viewer index) — same
data, different presentation: HTML for browsers, Markdown for
GitHub Pages READMEs and PR descriptions.

Verified on a 2-zone project where one zone had been baked +
viewer-exported + doc-exported and the other hadn't: README.md
correctly shows ✓/links for the baked zone, em-dashes for the
unbaked one.
2026-05-06 16:48:46 -07:00
Kelsi
f3130bbd3d feat(editor): add --info-zone-extents for spatial bounding box analysis
Computes the zone's spatial bounding box: XY world coords from
manifest tile coords (each tile is 533.33 yards), Z height range
across all loaded chunks. Useful for sizing camera frustums,
planning where new tiles can fit contiguously, or quick sanity
checks ('this zone is 4km across? something's wrong'):

  wowee_editor --info-zone-extents custom_zones/MyZone

  Zone extents: custom_zones/MyZone
    tile count   : 3 (3 loaded, 0 missing on disk)
    tile range   : x=[30, 31]  y=[30, 31]
    world box    : (0.0, 0.0, 98.5) - (1066.7, 1066.7, 101.5) yards
    size         : 1066.7 x 1066.7 x 3.0 yards (975m x 975m x 2.7m)

WoW grid math: tile (32, 32) is at world origin; +X tile = -X world
(north convention), +Y tile = -Y world (west convention). The
displayed world coords use the same transform the renderer uses
so they line up with --bake-zone-glb output bounds.

Per-axis size in yards + meters (0.9144 conversion) since some
designers think in metric, others in WoW-canonical yards.

Tracks loaded vs missing tiles in case the manifest references a
tile whose .whm got deleted — surfaces silently bad zones early.

JSON mode emits full bounding box + tile range + size for
programmatic consumption (camera autofit, layout planning).
2026-05-06 16:41:07 -07:00
Kelsi
9b362fc825 feat(editor): add --diff-checksum for SHA256SUMS comparison
Compares two SHA256SUMS files (from --export-zone-checksum). Reports
added / removed / changed entries between two zone snapshots — much
faster than walking the filesystem to recompute hashes of unchanged
content:

  wowee_editor --export-zone-checksum custom_zones/Z /tmp/before.sha256
  ... edits happen ...
  wowee_editor --export-zone-checksum custom_zones/Z /tmp/after.sha256
  wowee_editor --diff-checksum /tmp/before.sha256 /tmp/after.sha256

  Diff: /tmp/before.sha256 vs /tmp/after.sha256
    added   : 1
    removed : 0
    changed : 0
    +  creatures.json

Standard diff-style markers (+/-/~) for added/removed/changed,
sorted within each category alphabetically.

Use cases:
- Audit what an editing session actually touched (snapshot before,
  snapshot after, diff)
- Verify a zone bundle re-extracts identically (transfer integrity
  beyond the per-file PASS/FAIL of sha256sum -c)
- CI gate: fail build if a refactor touches files it shouldn't

Diff family for content/integrity formats:
  --diff-zone      unpacked zone dir vs zone dir (high-level)
  --diff-extract   per-extension counts in two extracts
  --diff-checksum  per-file hash diff between two snapshots  <- new

Verified: scaffold + export checksum, add creature, re-export,
diff correctly reports '+ creatures.json' (added) and exit 1.
2026-05-06 16:32:55 -07:00
Kelsi
dbf973e29e feat(editor): add --mvp-zone for one-command demo zone setup
Quick-start: scaffold a zone AND populate one of each content type
(1 creature, 1 object, 1 quest with objective + XP reward) in a
single command. Goes from empty filesystem to 'something to look at'
without 7 chained --add-* commands:

  wowee_editor --mvp-zone 'Demo Land' 30 30

  Created demo zone: custom_zones/Demo_Land
    tile     : (30, 30)
    contents : 1 creature, 1 object, 1 quest (with objective + reward)
    next     : wowee_editor --info-zone-tree custom_zones/Demo_Land

  Demo Land/
  ├─ Manifest ...
  ├─ Tiles (1) — (30, 30)
  ├─ Creatures (1) — lvl 5 Demo Wolf
  ├─ Objects (1) — m2 World/Generic/Tree.m2
  ├─ Quests (1) — [1] Welcome to Demo Land (lvl 1, 100 XP)
  │     └─ kill ×1 Demo Wolf

Demo content is positioned roughly at tile center (533.33-yard
intervals from origin tile 32/32). Quest references the demo
creature's auto-id so --check-zone-refs passes immediately.

Use cases:
- Smoke-testing the bake/validate pipeline
- Screenshot bait for docs / blog posts
- Editor onboarding (open a zone in the GUI to see the format)
- CI sanity check (does our editor still produce a viewable zone?)

Verified end-to-end: --mvp-zone 'Demo Land' → --info-zone-tree
shows all 4 sections populated correctly, file list matches
expected 6 files.
2026-05-06 16:25:23 -07:00
Kelsi
9c7b6aebfc feat(editor): add --info-quest-graph-stats for chain topology analysis
Where --export-quest-graph visualizes the quest dependency graph,
this quantifies it. Useful for spotting authoring issues (orphan
quests that only appear as one-offs, broken chains via cycles)
and getting a sense of quest density:

  wowee_editor --info-quest-graph-stats $Z/quests.json

  Quest graph: $Z/quests.json
    total quests : 4
    roots        : 2 (no inbound chain — entry points)
    leaves       : 2 (no outbound chain — terminal)
    orphans      : 1 (root AND leaf — one-shot)
    cycles       : 0
    max depth    : 3
    avg depth    : 2.00 (chain length per root)

Definitions:
  roots    = quests no other quest chains TO (player entry points)
  leaves   = quests with no nextQuestId or nextQuestId pointing to
             a missing quest (terminal — chain ends here)
  orphans  = root AND leaf (one-shot quests with no neighbors)
  cycles   = number of roots whose forward walk hits a node twice
  maxDepth = longest path from any root forward through the chain
  avgDepth = mean path length across all roots

Cycle-guarded forward walk uses a visited-set per root, so the
cycle count is bounded even on intentionally-broken inputs.

Exit 1 if cycles > 0 so CI can gate before shipping a broken
chain. JSON mode emits all six stats for dashboard consumption.

Verified on 4-quest zone (Q1→Q2→Q3 chain + Loner orphan):
correctly reports 2 roots, 2 leaves, 1 orphan, 0 cycles, max
depth 3, avg depth 2.00.
2026-05-06 16:17:30 -07:00
Kelsi
cc91a1146f feat(editor): add --bake-project-stl + --bake-project-glb completing the project trio
Three project-bake formats now match the three zone-bake formats —
full project terrain reachable from every universal-3D ecosystem:

  wowee_editor --bake-project-obj  custom_zones    # DCC tools
  wowee_editor --bake-project-stl  custom_zones    # 3D printing
  wowee_editor --bake-project-glb  custom_zones    # web viewers

Shared per-zone walking pass collects vertex+index pools per zone,
then the format-specific tail emits:
  STL → per-triangle 'facet normal'+'outer loop'+vertex×3
  GLB → packed BIN chunk + JSON describing per-zone meshes

GLB output gives one mesh+node per zone (named 'zone_NAME') so
viewers can toggle zones independently — same pattern as
--bake-zone-glb but at project scope. STL is single-solid since
slicers don't have a useful concept of multi-part STL.

Coords align across all three exporters and across zone vs project
scope, so:
- A zone .obj overlaid with its containing project .obj lines up
- A project .glb opened in three.js shows zones at the same coords
  the renderer uses

Verified on a 2-zone project (Forest + Desert):
- project.stl: 2 zones, 2 tiles, 65536 facets
- project.glb: 2 zones, 2 tiles, 41472 verts, 65536 tris, 1.78MB BIN
- --validate-glb on project.glb: PASSED

Bake granularity matrix complete:
                     OBJ              STL              GLB
  single model   --export-obj    --export-stl     --export-glb
  single zone    --bake-zone-obj --bake-zone-stl  --bake-zone-glb
  whole project  --bake-project-obj --bake-project-stl --bake-project-glb
2026-05-06 16:08:44 -07:00
Kelsi
54c309a779 feat(editor): add --bake-project-obj for whole-project terrain export
Project-level OBJ bake — combines every zone's terrain into one
giant OBJ with one 'g zone_NAME' block per zone. Useful for
previewing an entire multi-zone project's terrain in MeshLab/
Blender at once, or for printing the full map:

  wowee_editor --bake-project-obj custom_zones

  Baked custom_zones -> custom_zones/project.obj
    2 zone(s), 3 tiles, 62208 verts, 98304 tris

Layout: single global vertex pool (so OBJ indexing stays valid),
per-zone face groups so designers can hide individual zones in
their viewer for area-by-area inspection. Hole bits respected.
Coords match WoweeCollisionBuilder's outer-grid layout exactly so
zones spatially line up at WoW grid boundaries — adjacent tiles
across zones connect seamlessly.

Pairs with the existing --bake-zone-* family (single zone) and
--export-project-html (web index of per-zone viewers). Three
levels of granularity now available:
  --export-glb / --export-obj / --export-stl     single model/file
  --bake-zone-glb / -obj / -stl                  single zone
  --bake-project-obj                             entire project  <- new

Verified: 2-zone project (Forest 2 tiles + Desert 1 tile) baked
to project.obj with 62208 verts (3 × 20736), 98304 tris (3 ×
32768), 2 'g' blocks correctly named (zone_Desert, zone_Forest).
2026-05-06 15:59:51 -07:00
Kelsi
b628535a91 feat(editor): add --export-zone-checksum for SHA-256 integrity manifests
Emits a SHA-256 manifest of every source file in a zone in the
standard sha256sum format. Lets users verify zone integrity after
download/transfer using the standard system tool — no custom
verifier needed:

  wowee_editor --export-zone-checksum custom_zones/MyZone

  3298c35a...  Z_30_30.whm
  f81e3d37...  Z_30_30.wot
  6a49519f...  creatures.json
  4625e30b...  zone.json

  sha256sum -c custom_zones/MyZone/SHA256SUMS
  Z_30_30.whm: OK
  Z_30_30.wot: OK
  ...

Source-only by design — derived outputs (.glb/.obj/.stl/.html/.png/
ZONE.md/DEPS.md/quests.dot/SHA256SUMS itself/Makefile) are excluded
since they're regeneratable and would invalidate the checksum on
every rebuild.

Includes a self-contained 90-LoC SHA-256 (FIPS 180-4 / RFC 6234)
in an internal namespace — no OpenSSL/Crypto++ dependency added.
Streaming hash (16KB chunks) so it scales to giant terrain WHMs
without holding the whole file in memory.

Verified end-to-end: scaffolded zone with 1 creature → checksum
manifest of 4 source files (zone.json, creatures.json, .whm, .wot)
in standard format → sha256sum -c reports all 4 OK.
2026-05-06 15:51:50 -07:00
Kelsi
4668140eed feat(editor): add --info-cli-help for substring searching the help text
130+ commands. 'Is there a thing for X?' is a common question.
Scrolling --help to find out is slow. This searches:

  wowee_editor --info-cli-help quest

  --add-quest <zoneDir> <title> [giverId] [turnInId] [xp] [level]
                         Append one quest to <zoneDir>/quests.json and exit
  --add-quest-objective <zoneDir> <questIdx> <kill|collect|...> ...
                         Append one objective to a quest by index
  --remove-quest-objective <zoneDir> <questIdx> <objIdx>
                         Remove the objective at given 0-based index from a quest
  --clone-quest <zoneDir> <questIdx> [newTitle]
                         Duplicate a quest (with all objectives + rewards)
  ...

  32 line(s) matched 'quest'

Case-insensitive substring match. Continuation lines (the indented
description right after a flag) are emitted along with their flag
line for context. Match-count summary on stderr so it doesn't
contaminate piped output.

Pairs with --list-commands (full list) and --info-cli-stats (counts
by category). Three meta commands now cover the discovery loop:
'how many?' (--info-cli-stats), 'which ones?' (--list-commands),
'what does X do?' (--info-cli-help X).
2026-05-06 15:43:02 -07:00
Kelsi
f5cb2adbda feat(editor): add --gen-project-makefile for top-level multi-zone builds
Pairs with --gen-makefile (per-zone) — generates a project-level
Makefile that delegates to each zone's per-zone Makefile, enabling
parallel rebuilds across zones:

  wowee_editor --gen-project-makefile custom_zones
  make -C custom_zones -j$(nproc)        # all zones in parallel

  make Forest-bake                       # one zone
  make clean                             # strip every zone
  make validate                          # validate every zone
  make index                             # rebuild project HTML index
  make stats / tilemap                   # project-level reports

Per-zone targets (one set per zone):
  ZONE-bake     -> ensures ZONE/Makefile exists then `make -C ZONE all`
  ZONE-clean    -> --strip-zone ZONE
  ZONE-validate -> --validate-all ZONE

Auto-generates the per-zone Makefile if missing (so the project
Makefile bootstraps a fresh project without an extra setup step).

Top-level utility targets reuse the existing project-level commands:
- index    -> --export-project-html (HTML zone index)
- stats    -> --zone-stats (aggregate counts)
- tilemap  -> --info-tilemap (ADT grid visualization)

Verified: 2-zone project (Forest + Desert) generates a Makefile
with 6 zone-level targets (3 per zone) + 5 top-level targets, sorted
alphabetically by zone name.
2026-05-06 15:37:29 -07:00
Kelsi
dadefab64e feat(editor): add --check-zone-content for content data-quality validation
Sanity-checks creature/object/quest fields for plausible values.
Where --check-zone-refs catches dangling references, this catches
data-quality issues that pass technical validation but break
gameplay:

  wowee_editor --check-zone-content custom_zones/MyZone

  Zone content: custom_zones/MyZone
    creature warnings: 1
    object warnings  : 0
    quest warnings   : 2
    FAILED — 3 total warning(s):
      - creature[0] 'Wolf' has displayId=0 (will render invisibly)
      - quest[0] 'Hunt' has no objectives (uncompletable)
      - quest[0] 'Hunt' has no reward at all

Per-type checks:

Creatures:
  - empty name
  - 0 health (dies on spawn)
  - level 0
  - minDamage > maxDamage (broken combat math)
  - non-positive or non-finite scale
  - displayId=0 (invisible at runtime)

Objects:
  - empty path
  - non-positive or non-finite scale
  - non-finite position

Quests:
  - empty title
  - no objectives (player can never complete)
  - no reward at all (XP=0, items=[], coins all 0)
  - requiredLevel=0

Both --check-zone-refs (link integrity) and --check-zone-content
(data quality) needed — a quest can have valid NPC IDs (refs OK)
AND no objectives (content broken). Run both before --pack-wcp.

Verified end-to-end: zone with displayId=0 creature + objective-
less + rewardless quest reports 3 warnings; after fixing all three,
PASSED.
2026-05-06 15:31:12 -07:00
Kelsi
c9e8ad9930 feat(editor): add --diff-extract for asset-extract directory comparison
Compares two extracted asset directories side-by-side per file
extension. Useful for diffing a fresh asset_extract run against
a previous baseline (did the new MPQ add files? did any get
dropped?), or comparing what each WoW expansion contributes:

  wowee_editor --diff-extract baseline/ new/

  Diff: baseline/ vs new/
    totals: 4 files / 0.0 MB    vs    4 files / 0.0 MB

    Per-extension (count then bytes):
    ext            a count   b count    a bytes      b bytes  status
    .blp                 2         2           0            0
    .dbc                 1         0           0            0  -A
    .m2                  1         2           0            0  DIFF

    2 extension(s) differ

Status column flags imbalance:
  -A   only in A (extension dropped going B-ward)
  +B   only in B (extension added)
  DIFF count differs but both sides have some

Recursive walk so subdirectories aggregate into the parent's
extension counts. JSON mode emits per-extension {count,bytes}
pairs for both sides plus union diff count for CI consumption.

Diff family for directory-shaped formats:
  --diff-zone     unpacked zone dir vs zone dir
  --diff-extract  extracted asset dir vs extract dir  <- new

Verified on synthesized 4-file dirs (a: 2 blp + 1 dbc + 1 m2;
b: 2 blp + 0 dbc + 2 m2): correctly flags -A on .dbc, DIFF on
.m2, exit 1.
2026-05-06 15:25:18 -07:00