Commit graph

623 commits

Author SHA1 Message Date
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
Kelsi
0f275cfee7 feat(editor): add --info-cli-stats meta command for surface tracking
130 commands and counting — surface inspection has graduated from
'nice to have' to 'necessary to plan'. This drops a per-category
breakdown:

  wowee_editor --info-cli-stats

  CLI surface stats
    total commands : 130
    longest flag   : 24 chars

    Categories (by verb prefix, sorted by count):
      --info           33
      --export         15
      --list           12
      --validate       11
      --diff            8
      --add             6
      --remove          5
      --convert         5
      --bake            3
      --migrate         3
      --clone           3
      ...

Pulls the command list via the same printUsage-parser as
--list-commands so it auto-tracks new flags. Buckets by the verb
prefix (text between '--' and the next '-') so '--info-zone-tree'
counts under 'info'. JSON mode emits the histogram as an object
for dashboard consumption.

Useful for spotting category imbalances ('we have 33 inspectors
but only 5 add commands — should consider more authoring CRUD?'),
tracking growth over releases, and planning where to invest.
2026-05-06 15:21:24 -07:00
Kelsi
1f20d0c5a2 feat(editor): add --gen-makefile for incremental zone-output rebuilds
Generates a Makefile that rebuilds every derived output for a zone
with proper dependency tracking. Designers can `make` to refresh
glb/obj/stl/html/csv/md from sources after editing creatures.json
or terrain — without remembering which wowee_editor flag does what,
and without rebuilding outputs that haven't changed:

  wowee_editor --gen-makefile custom_zones/MyZone
  cd custom_zones/MyZone && make

  make all       # rebuild everything that's stale
  make glb       # just the glTF bake
  make clean     # nuke derived outputs (calls --strip-zone)
  make validate  # run --validate-all on the zone

Targets generated:
  glb obj stl html docs csv graph all clean validate

Dependency tracking:
  - terrain bakes (.glb/.obj/.stl) depend on zone.json + WHM tiles
  - HTML viewer depends on the .glb (forces glb rebuild first)
  - docs (ZONE.md/DEPS.md) depend on content JSONs
  - csv/graph use '-' prefix so missing-content failures don't
    block 'make all' (zone with no quests still bakes terrain)

Uses /proc/self/exe absolute path so the Makefile works from any
cwd (run via `make -C custom_zones/MyZone` from anywhere). Falls
back to PATH lookup if /proc not available.

Verified end-to-end: scaffolded zone, generated Makefile, ran
`make all` from inside the zone dir — all derived outputs (.glb,
.obj, .stl, .html, ZONE.md, DEPS.md) generated; csv+graph
gracefully skipped due to no content; make exit 0.
2026-05-06 15:17:53 -07:00
Kelsi
5ebd04a953 feat(editor): add --info-extract-tree for hierarchical extract-dir overview
After asset_extract finishes, '142k files across 17 dirs' is hard
to reason about. This groups them visually by top-level subdir +
file format with byte totals — instant orientation:

  wowee_editor --info-extract-tree Data

  Data/  (13 dirs, 284612 files, 31451.1 MB)
  ├─ expansions/  (85141 files, 12871.2 MB)
  │  ├─ .adt         5469 files  6226626.7 KB
  │  ├─ .wav        19517 files  3567936.9 KB
  │  ├─ .m2         26354 files  1477702.1 KB
  │  ├─ .wmo         5195 files   744940.1 KB
  │  ├─ .blp        26911 files   691248.5 KB
  │  ├─ .dbc          282 files    92373.6 KB
  │  ├─ ...
  ├─ terrain/    (...)
  ├─ character/  (...)

Top-level dirs sorted by total bytes descending (heaviest first
so the high-impact directories surface immediately). Per-dir
extension breakdown also sorted by bytes. Walks recursively into
each top-level dir so 'expansions/wotlk/world/...' rolls up into
the expansions/ row.

Companion to --info-extract (counts + sidecar coverage) — that
one's wide-format JSON-friendly; this one's a tall tree-style
quick-look. Verified on a real 31GB extract of Data/ — output
makes the size distribution immediately clear (ADT files are
6GB of 13GB total for expansions/, etc.).
2026-05-06 15:10:45 -07:00
Kelsi
d3b7a085c2 feat(editor): add --validate-blp for proprietary BLP structural check
Companion to --validate-png — validates BLP textures without paying
the DXT decompress cost. Useful for spot-checking thousands of BLPs
in an extract dir:

  wowee_editor --validate-blp Texture.blp

  BLP: Texture.blp
    magic      : BLP2
    size       : 128 x 128
    valid mips : 8
    file bytes : 23044
    PASSED

Checks (header-only, no pixel decode):
- 4-byte magic is 'BLP1' or 'BLP2'
- Width/height non-zero and within 8192 (texture exporter cap)
- mipOffsets[16] / mipSizes[16] tables: each non-zero pair refers
  to a byte range within the file; mismatched (off=0 with size!=0
  or vice versa) flagged
- Stops at first zero offset (BLP convention for unused slots)

Per-version layout differences (BLP1 has compression+alphaBits as
uint32; BLP2 has compression/alphaDepth/alphaEncoding/hasMips as
uint8) handled inline.

Verified on real BLP (pvp-banner-emblem-1.blp): 8 valid mips,
PASSED. Non-BLP file (manifest.json starting with '{'): correctly
flags 'magic is {' error and exits 1.

Format-validator lineup is now exhaustive across both proprietary
and open formats:
  Proprietary: BLP / DBC (via --convert-dbc-json round-trip)
  Open binary: WOM / WOB / WOC / WHM / GLB
  Open text:   JSON DBC / STL / PNG (BLP sidecar)
2026-05-06 15:07:46 -07:00
Kelsi
a98e6e79c4 feat(editor): add --export-project-html for multi-zone web index
Generates an index.html linking to every zone's HTML viewer in a
project. Pairs with --export-zone-html (per-zone) and --bake-zone-glb
(terrain bake). Designed for github-pages / static-hosting style
'all my zones' showcase:

  wowee_editor --export-project-html custom_zones
  # -> custom_zones/index.html

Each zone gets a card showing:
- Display name (or mapName fallback)
- Counts: tiles · creatures · objects · quests (singular/plural
  agreement)
- 'Open viewer →' link if HTML exists
- Helpful nudge if HTML missing ('No HTML viewer (run --export-zone-html)')
- Or 'No .glb (run --bake-zone-glb)' if not even baked yet

Self-contained CSS (dark theme matching --export-zone-html), no
external dependencies. Responsive grid layout (300px-min cards
auto-flowing across viewport).

Verified on a 2-zone project (Forest with .glb+.html + Desert
without): index lists both, Forest gets a working link, Desert
gets the 'run --bake-zone-glb' hint.
2026-05-06 15:04:43 -07:00
Kelsi
f73b377b4d feat(editor): add --info-tilemap for project-wide ADT grid visualization
Renders the WoW 64x64 ADT coordinate grid as ASCII art showing which
tiles are claimed by which zones. Useful for spotting tile-coord
collisions before two zones ship overlapping content, and for
'where am I working?' overview of multi-zone projects:

  wowee_editor --info-tilemap custom_zones

  Tilemap: custom_zones
    zones      : 2
    tiles used : 5
    collisions : 0 (multiple zones claiming same tile)
    legend     : D=Desert F=Forest

         0         1         2         3         4         5         6
         0123456789012345678901234567890123456789012345678901234567890123
    y=30 ..............................FFDD..............................
    y=31 ...............................F................................

Glyphs are first-letter-of-zone uppercased; collision tiles render
as '*'. Empty rows are skipped to keep output bounded for projects
in one corner of the map (skipping all 60+ blank rows of the full
64x64 grid).

Exit 1 on collisions so CI can gate before merging two PRs that
both add tiles in the same area. JSON mode emits per-tile claims
for programmatic consumption.

Verified on a 2-zone project: Forest (3 tiles) + Desert (2 tiles)
correctly rendered, no collisions detected, glyphs F/D placed at
the right grid coords.
2026-05-06 15:01:32 -07:00
Kelsi
1718c2333f feat(editor): add --repair-zone to auto-fix manifest/disk drift
When a zone is hand-edited, partially copied, or modified by tools
that don't re-write zone.json, the manifest can fall out of sync
with the on-disk reality. --repair-zone reconciles them:

  wowee_editor --repair-zone custom_zones/MyZone --dry-run

  would add tile (31, 30) to manifest
  would set hasCreatures: false -> true

  repair-zone: custom_zones/MyZone (dry-run)
    fixes    : 2
    warnings : 0 (manual decision needed)
    re-run without --dry-run to apply

Auto-fixes:
- WHM files on disk matching <mapName>_TX_TY.whm pattern but not
  in manifest tiles[] -> add to tiles
- hasCreatures flag mismatched against actual creatures.json
  presence + non-empty -> sync

Warns (no auto-fix — needs manual decision):
- Tiles in manifest but no .whm on disk (could be in-progress
  work or genuinely deleted; user decides)

--dry-run flag previews changes. Pairs with --strip-zone (cleanup
derived) and --validate (open-format coverage) for the trio of
zone-health-maintenance commands.

Verified: scaffolded zone, hand-copied an extra .whm/.wot pair to
simulate disk-without-manifest drift, added a creature then flipped
hasCreatures=false in zone.json. --repair-zone correctly identifies
2 fixes, dry-run lists them, real run applies them, manifest now
shows correct tiles array + hasCreatures=true.
2026-05-06 14:58:08 -07:00
Kelsi
b82f9827d4 feat(editor): add --info-zone-bytes for per-file size breakdown
Drills into one zone's contents with categorized + sorted file
sizes. --zone-stats aggregates across multiple zones; this answers
'which file is 80% of THIS zone?' and 'how much would --strip-zone
free?':

  wowee_editor --info-zone-bytes custom_zones/MyZone

  Zone bytes: custom_zones/MyZone
    total: 2282178 bytes (2228.7 KB) across 6 file(s)

    Per-file (largest first):
    path                                bytes  category
    Z_30_30.woc                       1212456  terrain
    Z.glb                              891736  3D export (derived)
    Z_30_30.whm                        150540  terrain
    Z_30_30.wot                         26680  terrain
    zone.json                              446  json (source)
    quests.json                            320  json (source)

    Per-category:
    3D export (derived)    1 files    891736 bytes  ( 39.1%)
    json (source)          2 files       766 bytes  (  0.0%)
    terrain                3 files   1389676 bytes  ( 60.9%)

Categories: terrain / model (open|proprietary) / building (open|
proprietary) / texture (open|proprietary) / DBC / json (source) /
3D export (derived) / doc (derived) / other.

Source vs derived split surfaces what --strip-zone would clean up
(any 'derived' category) so capacity planning shows both 'what's
mine' (source) and 'what's regeneratable' (derived).

Recursive walk so subdirs (data/) are included with relative paths.
JSON mode emits per-file records + per-category aggregate for
programmatic consumption.
2026-05-06 14:54:29 -07:00
Kelsi
7dfc0b6333 feat(editor): add --strip-zone for derived-output cleanup
Removes the derived outputs (everything --bake/--export-* generates)
leaving only source files in a zone directory:

  wowee_editor --strip-zone custom_zones/MyZone

  removed: MyZone.obj (1099177 bytes)
  removed: MyZone.glb (891736 bytes)
  removed: DEPS.md (357 bytes)
  removed: ZONE.md (534 bytes)
  removed: quests.dot (198 bytes)

  strip-zone: custom_zones/MyZone
    removed  : 5 file(s)
    freed    : 1945.3 KB

What gets deleted:
- .glb / .obj / .stl  (3D format exports)
- .html               (browser viewer)
- .dot                (Graphviz quest graph)
- .csv                (spreadsheet exports)
- ZONE.md / DEPS.md   (markdown documentation)
- .png at zone root   (heightmap previews — NOT inside data/, those
                       are source BLP→PNG sidecars)

What stays:
- zone.json + creatures.json + objects.json + quests.json
- *.whm / *.wot / *.woc (terrain + collision)
- *.wom / *.wob         (open binary models/buildings)
- data/*.json           (DBC sidecars — source, not derived)

Top-level only — does not recurse into subdirectories so source
sidecars under data/ are untouched.

--dry-run flag previews what would be removed without deleting.
Useful before committing to git so derived blobs don't bloat
history, or before --pack-wcp so the archive doesn't carry
redundant exports.

Verified: scaffolded zone, generated 5 derived files (glb/obj/
ZONE.md/DEPS.md/quests.dot), --dry-run lists all 5 with sizes,
real run deletes them and frees 1945 KB. Source files (whm/wot/
woc/zone.json/quests.json) all preserved.
2026-05-06 14:51:36 -07:00
Kelsi
8f5a3b3d95 feat(editor): add --info-glb-tree for hierarchical glTF structure view
--info-glb gives counts; --info-glb-tree shows the actual scene ->
node -> mesh -> primitive hierarchy with names and accessor refs.
Useful when debugging 'why is this imported model showing up empty
in three.js?' (often the scene's nodes[] points to the wrong node):

  wowee_editor --info-glb-tree Z.glb

  Z.glb
  ├─ asset (v2.0, wowee_editor --bake-zone-glb)
  ├─ buffers (1)
  │  └─ [0] 1781760 bytes
  ├─ bufferViews (3)
  │  ├─ [0] off=0 len=497664 (vertex)
  │  ├─ [1] off=497664 len=497664 (vertex)
  │  └─ [2] off=995328 len=786432 (index)
  ├─ accessors (4)
  │  ├─ [0] f32 VEC3 ×41472 (bv=0)
  │  ├─ [1] f32 VEC3 ×41472 (bv=1)
  │  ├─ [2] u32 SCALAR ×98304 (bv=2)
  │  └─ [3] u32 SCALAR ×98304 (bv=2)
  ├─ meshes (2)
  │  ├─ [0] (1 primitives)
  │  │  └─ [0] TRIANGLES indices=acc#2
  │  └─ [1] (1 primitives)
  │     └─ [0] TRIANGLES indices=acc#3
  ├─ nodes (2)
  │  ├─ [0] tile_30_30 -> mesh#0
  │  └─ [1] tile_31_30 -> mesh#1
  └─ scenes (1, default=0)
     └─ [0] nodes=[0,1] (2 nodes)

Decodes glTF componentTypes (5120-5126 -> i8/u8/i16/u16/u32/f32),
bufferView targets (34962=vertex, 34963=index), primitive modes
(0=POINTS / 1=LINES / 4=TRIANGLES). Node sub-line shows mesh
reference so the scene-graph wiring is visible at a glance.

Pairs with --info-zone-tree (zone content tree) — both use the
same UTF-8 box-drawing pattern for visual consistency.
2026-05-06 14:48:37 -07:00
Kelsi
2904fa0560 feat(editor): add --export-zone-deps-md for shareable dependency tables
Markdown counterpart to --list-zone-deps. PR reviewers see at a
glance whether every referenced model exists in either open or
proprietary form across the conventional asset roots:

  wowee_editor --export-zone-deps-md custom_zones/MyZone
  # -> custom_zones/MyZone/DEPS.md

# Dependencies — MyZone

*Auto-generated. Status is best-effort — checks zone-local, output/,
custom_zones/, Data/ roots in that order.*

## Direct M2 placements (12)

| Refs | Path | Status |
|---:|---|---|
| 8 | `World/Tree.m2`        | open + proprietary |
| 3 | `World/Lamp.m2`        | open only |
| 1 | `World/Banner.m2`      | MISSING |

Status column resolves each path against zone-local + output/ +
custom_zones/ + Data/ roots, trying both .wob/.wmo for buildings
and .wom/.m2 for models. Catches missing assets BEFORE pack-wcp
would silently include broken refs.

GitHub-Flavored Markdown — sortable by Refs column once rendered,
backtick-wrapped paths so URLs/spaces don't confuse the viewer.

Verified: scaffolded zone with 2 M2 placements (one duplicated) +
1 WMO placement → DEPS.md has 3 sections (one per category) with
correct ref counts (Tree.m2 ×2) and MISSING status for paths that
don't resolve in any root.
2026-05-06 14:44:53 -07:00
Kelsi
84f897f0ec feat(editor): add --diff-jsondbc completing the diff family for the last text format
Diff family is now exhaustively complete across every shippable
format:

  --diff-wcp       archive
  --diff-zone      unpacked zone dir
  --diff-glb       glTF binary
  --diff-wom       WOM model
  --diff-wob       WOB building
  --diff-whm       WHM/WOT terrain pair
  --diff-woc       WOC collision
  --diff-jsondbc   JSON DBC sidecar  <- new

Schema-level compare:
  - format tag
  - source filename
  - recordCount + fieldCount (header values)
  - actualRecs (records[] array length)

Useful for catching schema regressions when a sidecar is regenerated
by a different tool version, or for verifying a --migrate-jsondbc
pass actually changed what it claimed.

Verified: same file vs itself reports IDENTICAL exit 0;
2-record vs 3-record (with same format/source/fieldCount) reports
2 DIFFs (recordCount + actualRecs) with exit 1.
2026-05-06 14:41:32 -07:00
Kelsi
e531901de8 feat(editor): add --validate-png for full PNG structural + CRC validation
Goes beyond --info-png (header sniff only) to validate every chunk
and verify CRC32. Catches corruption that browsers silently skip:

  wowee_editor --validate-png Texture.png

  PNG: Texture.png
    size       : 256 x 256
    bit depth  : 8 (color type 6)
    chunks     : 3 (0 CRC mismatches)
    file bytes : 142336
    PASSED

  wowee_editor --validate-png corrupted.png
    chunks     : 1 (1 CRC mismatches)
    FAILED — 4 error(s):
      - chunk 'IHDR' at offset 8: CRC mismatch (stored=0xC33E61CB
        actual=0x8A716D80)
      - missing required IDAT chunk
      - missing required IEND chunk

Checks:
- 8-byte PNG signature (89 50 4E 47 0D 0A 1A 0A)
- Per-chunk length doesn't exceed file
- CRC32 of (chunk type + data) matches stored CRC (PNG spec)
- IHDR is the first chunk
- IHDR / IDAT / IEND all present
- No trailing bytes after IEND

CRC32 implementation uses the standard polynomial 0xEDB88320 with a
runtime-built lookup table — no zlib dependency since we already
have to compute it ourselves anyway.

Verified on real PNG (BLP→PNG conversion of pvp-banner emblem):
PASSED. Hand-corrupted IHDR byte: correctly flags the CRC mismatch
with stored vs actual hex values + cascading missing-chunk errors.
Format-validator lineup is now exhaustive across both proprietary
and open formats:
  Open binary: WOM / WOB / WOC / WHM / GLB
  Open text:   JSON DBC / STL
  Sidecar:     PNG (proprietary BLP -> PNG bridge)
2026-05-06 14:38:17 -07:00
Kelsi
f8f5735d9b feat(editor): add --diff-whm and --diff-woc completing the open-format diff suite
Last two missing entries in the diff family — terrain heightmap pairs
and collision meshes:

  wowee_editor --diff-whm Z1/Z1_30_30 Z2/Z2_31_30
  Diff: ...
                         a              b
    tile         : (  30,  30)    (  31,  30)  DIFF
    loadedChunks :          256          256
    doodadPlace  :            0            0
    wmoPlace     :            0            0
    heightRange  : [-1.50,1.50]  [-1.50,1.50]

  wowee_editor --diff-woc tile_a.woc tile_b.woc
  Diff: ...
                         a              b
    tile        : (  30,  30)    (  31,  30)  DIFF
    triangles   :        32768        32768
    walkable    :        32768        32768
    steep       :            0            0

--diff-whm: tile coord, loaded chunk count, doodad/WMO placement
counts, texture/name table sizes, height range (min/max with float
epsilon). Pointwise height compare intentionally not done — float
perturbation from format round-trips would false-flag.

--diff-woc: tile coord, total triangles, walkable + steep counts.
Catches whether a --regen-collision pass actually changed something.

Diff family is now exhaustively complete for every shippable open
format:
  --diff-wcp    archive vs archive
  --diff-zone   unpacked zone dir vs zone dir
  --diff-glb    glTF binary vs glTF binary
  --diff-wom    WOM model vs WOM model
  --diff-wob    WOB building vs WOB building
  --diff-whm    WHM/WOT terrain pair vs pair
  --diff-woc    WOC collision vs collision

Verified: tile (30,30) vs (31,30) reports tile DIFF + identical
counts (since both are flat scaffolds); same-vs-self reports
IDENTICAL with exit 0.
2026-05-06 14:35:03 -07:00
Kelsi
cc3e85be5a feat(editor): add --migrate-jsondbc to auto-fix common JSON DBC issues
Designers receive JSON DBCs from various tools (older asset_extract
versions, third-party converters) that may omit fields the runtime
now expects. --validate-jsondbc tells you what's wrong; this fixes
the auto-fixable ones:

  wowee_editor --migrate-jsondbc db/Spell.json

  added: format = 'wowee-dbc-json-1.0'
  added: source = 'Spell.dbc'
  fixed: recordCount 99 -> 47882 (matches actual)
  inferred: fieldCount = 234 (from first row)
  Migrated db/Spell.json -> db/Spell.json
    fixes applied: 4

Auto-fixes:
- Missing 'format' tag → add 'wowee-dbc-json-1.0'
- Missing 'source' field → derive from filename stem + '.dbc'
- Missing 'fieldCount' → infer from first row
- recordCount mismatch → recompute from actual records[] length

NOT auto-fixed (data loss risk — surfaced as warnings instead):
- Wrong-width rows (silently padding/truncating could mangle field
  values; the user needs to inspect and decide)

In-place by default (writes back to the input path); accepts an
optional output path for non-destructive migration.

Verified: a JSON missing format/source/fieldCount with mismatched
recordCount=99 (actual 2) → migrate applies 4 fixes →
--validate-jsondbc reports PASSED on the result.
2026-05-06 14:31:35 -07:00
Kelsi
84902316e2 feat(editor): add --export-zone-html for browser-viewable zone preview
Generates a single-file HTML viewer next to the zone .glb. Anyone
with a modern browser can open it — no installs, no Blender, no
node_modules:

  wowee_editor --bake-zone-glb custom_zones/MyZone     # bakes .glb
  wowee_editor --export-zone-html custom_zones/MyZone  # writes .html
  open custom_zones/MyZone/MyZone.html                 # any browser

Uses Google's <model-viewer> web component (loaded from unpkg with
^4.0.0 version pin so a unpkg 'latest' bump can't silently break
older HTML files). The HTML itself is ~1.1KB; the .glb sits beside
it for the viewer to load via relative URL.

Features baked into the page:
- Camera controls (orbit, zoom, pan)
- Auto-rotate at 15deg/sec for the headless preview case
- Shadow casting + neutral environment IBL for non-flat lighting
- Header strip showing display name, map slug, tile count, mapId

Refuses to run if the zone .glb doesn't exist yet (clear error
message points the user at --bake-zone-glb).

Verified: scaffolded zone -> --bake-zone-glb -> --export-zone-html.
1.1KB HTML opens cleanly in browsers, references the sibling .glb.
Missing-glb case correctly errors with exit 1 + helpful next-step
hint.
2026-05-06 14:28:42 -07:00
Kelsi
d618d6a517 feat(editor): add --diff-wob completing the binary-format diff suite
Companion to --diff-wom for buildings. Same count-based shape so
round-trips through OBJ/glTF can be validated without false
positives from float perturbation:

  wowee_editor --diff-wob orig back

  Diff: orig.wob vs back.wob
                         a              b
    groups      :            5            5
    portals     :            3            3
    doodads     :           12           12
    materials   :            8            8
    groupTex    :            7            7
    totalVerts  :         4609         4609
    totalIdx    :        10950        10950
    name        : Stormwind     Stormwind
    boundRadius : match
    IDENTICAL

Compares: groups, portals, doodads, aggregated materials count
(per-group materials summed), aggregated group-texture count,
total verts/indices across all groups, name, boundRadius (with
0.01 epsilon).

Diff family is now complete across every binary format that ships
in a content pack:
  --diff-wcp    archive vs archive
  --diff-zone   unpacked zone dir vs zone dir
  --diff-glb    glTF binary vs glTF binary
  --diff-wom    WOM model vs WOM model
  --diff-wob    WOB building vs WOB building

Verified: identical pair reports IDENTICAL exit 0; pair with extra
group + extra doodad + name change reports 6 DIFFs with exit 1.
2026-05-06 14:25:40 -07:00
Kelsi
a01b5e5e89 feat(editor): add --info-object completing the single-entity inspector trio
Symmetric to --info-creature and --info-quest. Every PlacedObject
field for one entry:

  wowee_editor --info-object $Z/objects.json 3

  Object [3]
    type      : wmo
    path      : World/StaticObject/Stormwind/HumanInn.wmo
    nameId    : 3497721408
    uniqueId  : 4
    position  : (-8412.300, 633.200, 110.500)
    rotation  : (0.00, 90.00, 0.00) deg
    scale     : 1.000

Useful when debugging a misplaced object — quickly see the exact
position/rotation/scale + uniqueId + nameId without having to
grep through objects.json or run --list-objects through awk.

JSON mode emits the structured record. Inspector lineup is now
symmetric across all three content types:
  --info-{creatures,objects,quests}    aggregate counts
  --list-{creatures,objects,quests}    per-entry table
  --info-{creature,object,quest}       single-entry deep dive
2026-05-06 14:22:08 -07:00
Kelsi
b7696d1aa9 feat(editor): add --info-creature and --info-quest single-entity inspectors
The --list-* commands give table views; the --info-* (creatures/objects/
quests) give summary counts. Neither shows every field of a single
entry. These fill that gap:

  wowee_editor --info-creature $Z/creatures.json 0

  Creature [0] 'Captain'
    id            : 1
    displayId     : 11430
    position      : (100.00, 200.00, 50.00)
    orientation   : 0.00 deg
    scale         : 1.00
    level         : 12
    health/mana   : 100 / 0
    damage        : 5-10
    armor         : 0
    faction       : 0
    behavior      : stationary
    wander rad    : 10.0
    aggro rad     : 20.0
    leash rad     : 40.0
    respawn ms    : 300000
    patrol points : 0
    flags         :

  wowee_editor --info-quest $Z/quests.json 0

  Quest [0] 'Hunt Wolves'
    id              : 1
    required level  : 5
    giver NPC id    : 100
    turn-in NPC id  : 100
    next quest id   : 0 (terminal)
    reward          : 250 XP, 0g 0s 0c, 1 item(s)
      item[0]      : Item:Sword
    objectives      : 1
      [0] kill    ×5  Wolf  — Slay 5 Wolf

Useful for digging into 'why is this NPC not behaving like I expect?'
or reviewing one quest's full design in one screen instead of running
3-4 list-* commands. JSON mode emits every field as a structured
record for programmatic consumption.

Inspector lineup is now complete from aggregate to per-entry:
  --info-{creatures,objects,quests}      (counts)
  --list-{creatures,objects,quests}      (table)
  --info-{creature,quest}                (single entry, all fields)
2026-05-06 14:19:17 -07:00
Kelsi
17b83858c1 feat(editor): add --export-zone-csv for spreadsheet design workflows
Designers often prefer spreadsheets over JSON for read-only analysis
('which 5 quests give the most XP?', 'how many lvl 10+ creatures?',
'pivot table by faction'). This emits creatures.csv / objects.csv /
quests.csv in standard CSV that LibreOffice / Excel / Numbers / Python
pandas all consume natively:

  wowee_editor --export-zone-csv custom_zones/MyZone

  wrote custom_zones/MyZone/creatures.csv (47 rows)
  wrote custom_zones/MyZone/objects.csv (12 rows)
  wrote custom_zones/MyZone/quests.csv (8 rows)

CSV columns chosen to be designer-actionable:
- creatures: index/id/name/displayId/level/health/mana/faction/
  position/orientation/scale + hostile/questgiver/vendor/trainer flags
- objects: index/type/path/position/rotation/scale
- quests: index/id/title/requiredLevel/giver/turnIn/reward fields/
  objectiveCount + objectives/itemRewards joined by '; ' for keep-
  one-row-per-quest sortability

RFC 4180 quoting: fields with comma/quote/newline get wrapped in
double quotes with internal quotes doubled. Verified on a creature
named 'Big, Bad Bear' — comes out as '"Big, Bad Bear"'.

Round-trip back into the editor isn't supported yet (would need
schema-aware CSV parsing); this is read-only-export for now.
2026-05-06 14:16:00 -07:00
Kelsi
8dc91adc52 feat(editor): add --diff-wom completing the diff suite for models
Structural compare of two WOM models. Useful for verifying that
--migrate-wom or a round-trip through OBJ/glTF/STL preserved the
right counts:

  wowee_editor --diff-wom orig back

  Diff: orig.wom vs back.wom
                         a              b
    version     :            1            1
    vertices    :            5            5
    indices     :           18           18
    triangles   :            6            6
    textures    :            0            0
    bones       :            0            0
    animations  :            0            0
    batches     :            0            0
    name        : Pyramid       Pyramid
    bounds      : match
    IDENTICAL

Compares sizes only (vertex / index / bone / animation / batch /
texture counts) plus name and bounds. Bounds match-with-epsilon
(0.01 unit slop, generous since positions are typically yards) so
text-format round-trips that perturb the last bit don't false-flag.
Pointwise vertex compare is intentionally not done — it would be
O(n²) and brittle to tiny float diffs from format conversions.

Diff family is now complete:
  --diff-wcp    (archive vs archive)
  --diff-zone   (unpacked zone dir vs zone dir)
  --diff-glb    (glTF binary vs glTF binary)
  --diff-wom    (WOM model vs WOM model)

Verified: identical pair reports IDENTICAL exit 0; pair with 1 extra
vertex + extra triangle + name change correctly reports 4 DIFFs
(verts/indices/triangles/name) with exit 1.
2026-05-06 14:13:07 -07:00