Commit graph

669 commits

Author SHA1 Message Date
Kelsi
f3578a14cc feat(editor): add --list-project-meshes cross-zone mesh inventory
Project-wide companion to --list-zone-meshes. Walks every zone in
<projectDir>, collects every .wom across all zones, sorts by
triangle count descending, and emits a global per-mesh table with
the originating zone in the first column.

Useful for project-wide outlier detection ("which mesh anywhere in
the project is the heaviest?") and for mesh-sharing audits where
many zones might reference the same heavy model. Both human (table)
and --json output modes.

Verified on 2-zone synthetic project (3 meshes total): table sorted
by triangle count descending (sphere 384 → cube 12 → plane 2),
zone column correctly identifies origin, totals row matches sum of
individual rows. Brings command count to 207.
2026-05-07 03:37:41 -07:00
Kelsi
cf7b5c66e3 feat(editor): add --set-item for in-place item field edits
Edit existing item fields without recreating the record. Lookup is
by id by default, '#N' for index. Only specified flags are changed —
everything else (including any extra hand-added fields) is preserved.

Supported flags: --name, --quality, --displayId, --itemLevel,
--stackable. Each takes one positional value. Range checks mirror
--validate-items (quality 0..6, stackable 1..1000) so saved JSON
stays validator-clean.

Unknown flags fail with a "typo?" hint rather than silently no-op,
and an empty flag list is rejected explicitly so the user sees that
nothing happened.

Verified: name + quality + itemLevel updated together (one line each
in the report); --quality 99 → range error exit 1; --foo bar → unknown
flag exit 1; no flags → no-op error exit 1. Brings command count to
206.
2026-05-07 03:26:38 -07:00
Kelsi
3a2bd64970 feat(editor): add --list-zone-meshes per-mesh breakdown
Per-mesh listing of every .wom in a zone. Complements
--info-zone-models-total (aggregate) by surfacing individual mesh
metrics: version, vertex count, triangle count, bone count, batch
count, texture-slot count, and on-disk bytes.

Sorted by triangle count descending so the heaviest meshes float to
the top of the table — useful for spotting outliers ("which mesh is
using 80% of my triangle budget?") and for content audits.

Both human (table) and --json output modes. Verified on synthetic
3-mesh zone (sphere 384 tris, cube 12, plane 2): table sorted
descending, totals row matches sum of individual rows, sub-dir mesh
shown with its relative path. Brings command count to 205.
2026-05-07 03:15:05 -07:00
Kelsi
0014ed4b31 feat(editor): items.csv now emitted by --export-zone-csv
Extends the existing CSV exporter so items.json gets the same
spreadsheet treatment as creatures/objects/quests. Reads items.json
inline (no dedicated editor class needed yet) and emits items.csv
with columns: index,id,name,quality,itemLevel,displayId,stackable.

Quotes the name field via the existing csvEsc helper so commas in
names don't break the format. Empty-zone error message updated to
include 'items' in the list of expected content.

Verified: zone with creatures.json + items.json (2 entries) emits
both creatures.csv and items.csv, header line present, rows sorted
by insertion order, no duplication when re-running.
2026-05-07 03:04:18 -07:00
Kelsi
ff1a974e3a feat(editor): add --info-item single-item detail view
Detail view for one record from items.json. Lookup is by id by
default; prefix the argument with '#' (e.g., "#3") to look up by
0-based array index instead. Useful for inspecting all fields
without sifting through the full --list-items table.

Surfaces any extra fields the user added by hand (description, lore,
flavor, custom flags) in a separate section so the command stays
useful as the schema evolves without code changes here.

Verified all three paths: id=7 → finds Sword, shows quality "rare",
surfaces extra "description" field; "#1" → finds Helm at index 1
("epic"); id=999 → no match, exit 1 with clear error. Brings
command count to 204.
2026-05-07 02:53:50 -07:00
Kelsi
8588279a88 feat(editor): add --add-texture-to-mesh manual texture binder
Manual companion to --gen-mesh-textured. Binds an existing PNG into
a WOM's texturePaths and points the chosen batch (default 0) at it.
Useful when the texture wasn't synthesized — e.g., an artist-painted
PNG, a converted .blp, or a texture shared across multiple meshes.

Slot dedup: if the leaf path already exists in texturePaths, the
existing slot is reused rather than appended, so repeated invocations
don't bloat the table. Warns (but doesn't fail) if the PNG isn't
sitting next to the WOM, since runtime path resolution is relative
to the WOM's own directory.

Verified: bind once → slot 1 added (slot 0 is the placeholder from
--gen-mesh), batch wired; rebind same path → slot 1 reused, count
stays at 2; missing PNG → exit 1 with clear error. Brings command
count to 203.
2026-05-07 02:43:32 -07:00
Kelsi
b882709d30 feat(editor): add --gen-mesh-textured one-shot mesh+texture composer
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
Pairs --gen-mesh and --gen-texture into a single call, then patches
the resulting WOM so its texturePaths[0] points at the freshly-
written PNG sidecar. Output is a textured model that renders out of
the box — no need to chain three commands by hand and remember the
relative-path conventions.

Layout:
  <wom-base>.wom    — mesh with texturePaths[0] = "<base>.png"
  <wom-base>.png    — 256x256 texture (color or pattern spec)

The PNG path stored in the WOM is just the leaf — the runtime
resolves textures relative to the model file's own directory, so
the bound model is portable as long as the .wom and .png ship
together.

Verified end-to-end: textured cube emits both files, --info-batches
shows "check.png" in the texture column for batch 0 (proving the
binding is permanent in the saved WOM, not just in memory). Brings
command count to 202.
2026-05-07 02:32:43 -07:00
Kelsi
099da16f0b feat(editor): add --info-project-items cross-zone item rollup
Project-wide companion to --list-items. Walks every zone in
<projectDir>, sums item counts and per-zone quality histograms,
emits a project-wide quality histogram plus a per-zone breakdown
table.

Useful for "do my zones have enough loot variety?" capacity checks
and balance-passes — surfacing that one zone has 50 commons and 0
rares is the kind of thing only the rollup makes obvious.

Verified on 2-zone project (4 items): project totals correct,
quality histogram (2 common / 1 uncommon / 1 legendary) matches
per-zone counts, JSON form parses cleanly. Brings command count
to 201.
2026-05-07 02:22:26 -07:00
Kelsi
f4e90b387c feat(editor): add --validate-items schema check + 200-command milestone
Catches the issues --add-item / --clone-item only enforce on
insertion (e.g., duplicate ids if items.json was hand-edited
outside the CLI), plus general field-range issues:
  - missing/zero/non-uint id
  - missing or empty name
  - quality outside 0..6
  - itemLevel > 1000 (suspicious typo)
  - stackable == 0 or > 1000 (out of range)
  - duplicate id across multiple item indices

Reports per-error lines with item index and the offending field;
exit 1 if any error.

Verified: clean 2-item zone → PASS exit 0; injected file with one
clean entry plus one with all-five-error-types fields → 5 errors
detected (4 field-level + 1 duplicate-id), exit 1.

Brings command count to 200 — round-number milestone for the
headless CLI surface.
2026-05-07 02:12:35 -07:00
Kelsi
86b4527eb2 feat(editor): add --clone-item for items.json parity with clone-creature/object/quest
Duplicates the entry at given 0-based index. Auto-assigns the
smallest unused positive id so numbering stays contiguous. Optional
<newName> argument overrides the cloned name; without it the new
entry appends " (copy)" to the original name (matches the
established convention from --clone-creature).

Useful for variant items: clone "Iron Sword" → "Steel Sword" and
edit just the displayId, instead of typing every field again.

Verified: clone with no name override → "Iron Sword (copy)" id=2;
clone with explicit "Steel Sword" → id=3 (next free); fields
(quality, ilvl, displayId) preserved verbatim; out-of-range index
99 fails with exit 1. Brings command count to 199.
2026-05-07 02:02:35 -07:00
Kelsi
a7ba6bf711 feat(editor): add --remove-item completing the items CRUD set
Removes the entry at given 0-based index from <zoneDir>/items.json.
Mirrors --remove-creature/--remove-object/--remove-quest semantics:
bounds-checked, file rewrites on success, exit 1 on out-of-range or
malformed JSON. Reports the removed item's name + id in the success
line so the user has feedback on what they just dropped.

Items CRUD set is now complete: --add-item / --list-items /
--remove-item. Together they form the items.json half of the new
content-creation direction (textures/meshes/items) the user asked
for in the prior batch.

Verified: 3-item zone → remove idx 1 ('Bread', id=2) leaves 2 items
intact and renumbered; out-of-range index 99 fails with exit 1 and
clear error. Brings command count to 198.
2026-05-07 01:52:43 -07:00
Kelsi
6eeac1c861 feat(editor): add --list-items companion to --add-item
Pretty-prints every record in <zoneDir>/items.json as a table with
idx / id / itemLevel / stackable / quality (named) / displayId /
name columns. Also supports --json for machine-readable output
(emits the items array verbatim).

Verified on a 3-item zone: table output aligns columns, quality
labels resolve correctly (common/uncommon/legendary), --json mode
emits a clean array consumable by jq or downstream tools. Brings
command count to 197.
2026-05-07 01:43:12 -07:00
Kelsi
dd36182cd3 feat(editor): add --add-item, introducing zone items.json content type
Introduces a new per-zone content file alongside creatures.json /
objects.json / quests.json. Schema: {"items": [{id, name, quality,
displayId, itemLevel, stackable}, ...]}. Inline JSON manipulation
via nlohmann::json — items are simple records and don't yet need
NpcSpawner-style infrastructure.

ID assignment: pass 0 (or omit) to auto-pick the smallest unused
positive integer so numbering stays contiguous. Explicit IDs are
honored. Duplicate IDs rejected with exit 1 so collisions are
visible.

Quality is 0..6 (poor/common/uncommon/rare/epic/legendary/artifact)
— summary line maps the number to the human name so users see what
they wrote.

Verified: auto-id sequential (1, 2) → explicit id (99) honored →
duplicate id rejected with exit 1; JSON schema stable, quality
labels correct, file auto-created on first call. Brings command
count to 196.
2026-05-07 01:33:58 -07:00
Kelsi
d28094593c feat(editor): add --gen-mesh for procedural WOM primitives
Synthesizes a procedural WOM model with proper per-face normals,
planar UVs, a bounding box, and a single batch covering all indices
so the model renders immediately in the editor without further
processing.

Three shapes:
- cube   — 24 verts / 12 tris, axis-aligned, ±size/2 (per-face flat
           normals, requires duplicated verts at edges)
- plane  — 4 verts / 2 tris on the XY plane (Z=0), ±size/2
- sphere — UV sphere, 16 segments × 12 stacks, radius=size/2
           (221 verts / 384 tris)

Default size=1.0 (unit cube/plane/unit-diameter sphere). Pair with
--gen-texture to make a ready-to-place model from scratch.

Verified all three shapes write valid v3 WOM files with correct
vertex/triangle counts, correct bounds, and a single opaque batch;
cube.wom round-trips cleanly through --export-obj (24 verts in →
24 verts out). Brings command count to 195.
2026-05-07 01:24:01 -07:00
Kelsi
6cf2043de4 feat(editor): add --gen-texture for synthesizing placeholder PNG textures
Lets users add a working texture to a project without firing up an
external image editor. Useful for prototyping new meshes, filling
out a zone before art is final, or generating CI test fixtures.

Three spec modes:
- "RRGGBB" or "RGB" hex (case-insensitive, optional leading '#') →
  solid fill
- "checker" → 32x32 black/white checkerboard
- "grid" → black background with white 1-px grid every 16

Default 256x256, configurable via optional W H positional args
(clamped to 1..8192). Writes via stbi_write_png so the output is a
standard PNG every WoW open-format pipeline already accepts.

First commit in the new "add content" direction (items / textures /
meshes) the user requested. Verified: solid hex (ff0000), 3-char
hex (8a3), checker, grid all produce valid PNGs of correct sizes;
'notacolor' input fails with exit 1 and a clear error. Brings
command count to 194.
2026-05-07 00:52:29 -07:00
Kelsi
61fc3847c3 feat(editor): add --export-data-tree-md migration progress markdown report
Generates MIGRATION.md with: status badge ("100% migrated", "Mostly
migrated", "Partially migrated", "Migration pending"), summary
counts, per-pair table with shares, and recommended next steps as
copy-pasteable wowee_editor invocations. Drops cleanly into PR
descriptions, CI artifacts, or GitHub Pages status pages.

Verified: 50%-migrated test tree → "Partially migrated" badge,
correct per-pair shares (.m2→.wom 100%, .blp→.png 0%), recommended
steps point at the actual srcDir. Brings command count to 193.
2026-05-07 00:42:55 -07:00
Kelsi
6057190a62 feat(editor): add --list-data-tree-largest for migration prioritization
Top-N largest proprietary files (.m2/.wmo/.blp/.dbc) in <srcDir>.
Helps users decide what to migrate first when sequencing the work
across a large extracted Data tree — convert the heaviest files
first to free the most disk space soonest.

Each row annotates whether an open sidecar already exists ("migrate")
or is still pending ("pending"), so the heavy hitters that are
already migrated are visible at a glance.

Default N = 20 (one terminal page); pass an explicit N for a
different cutoff. Header reports total proprietary bytes plus the
share captured by the top-N rows so users know how much of the work
the displayed list represents.

Verified: 3-file mixed tree (100/50/10 KB) → ranked descending by
size, .m2 (with sidecar) shows "migrate", others show "pending",
total/shown bytes match. Brings command count to 192.
2026-05-07 00:33:34 -07:00
Kelsi
7c0edbb421 feat(editor): add --bench-migrate-data-tree wall-clock perf benchmark
Times each step of --migrate-data-tree (m2/wmo/blp/dbc) end-to-end
and reports wall-clock per step plus the total. Useful for capacity
planning ("how long will the full extracted Data tree take?") and
regression detection (a recent change shouldn't make M2 conversion
2x slower).

Sub-batches dispatched the same way --migrate-data-tree dispatches
them, so the timings are exactly what the user will experience
running the migration. Both human (table with share %) and --json
output modes.

Verified: 4-format synthetic tree → all 4 steps timed individually,
share percentages sum to 100, total reported in both ms and seconds.
M2 + WMO dominate the share even on empty inputs (AssetManager init
overhead surfaces here, useful insight). Brings command count to 191.
2026-05-07 00:24:28 -07:00
Kelsi
97519645a6 feat(editor): add --audit-data-tree CI gate for migration completeness
Non-destructive CI gate that exits 1 if any proprietary file
(.m2/.wmo/.blp/.dbc) lacks a matching open sidecar at the same
(parent, stem). The pre-strip safety check: don't run --strip-data-
tree until this returns exit 0.

Lists the missing sidecars (capped at 50) so the user can re-run
--migrate-data-tree to fill the gaps. Per-extension breakdown
identifies which converter to investigate if specific formats are
underrepresented.

Completes the data-tree workflow:
  --info-data-tree     visibility
  --migrate-data-tree  fill sidecars
  --audit-data-tree    confirm 100% before stripping
  --strip-data-tree    delete migrated proprietary originals

Verified: empty tree → PASS exit 0; fully migrated → PASS exit 0;
gaps (1 .m2 + 1 .blp lacking sidecars) → FAIL exit 1 with both
files listed sorted, per-ext counts correct. Brings command count to
190.
2026-05-07 00:15:02 -07:00
Kelsi
97d52802b7 feat(editor): add --strip-data-tree to delete migrated proprietary files
Destructive cleanup that completes the data-tree migration workflow.
Walks <srcDir>, finds every proprietary file (.m2/.wmo/.blp/.dbc)
that already has a matching open sidecar at the same (parent, stem),
and deletes the proprietary file. Files without sidecars are
preserved (still need migration).

Honors --dry-run for safe previews. Defaults to actually delete
(matches --strip-zone convention).

Recommended workflow:
  --info-data-tree       see migration share
  --migrate-data-tree    fill in missing sidecars
  --strip-data-tree --dry-run    confirm kill list
  --strip-data-tree              apply

Verified: 11-file mixed tree → dry-run preserves all 11; apply
removes exactly the 4 that have sidecars (foo.m2, castle.wmo,
sky.blp, Spell.dbc); the 3 unmatched proprietary files
(bar.m2, grass.blp, Item.dbc) are correctly kept. Brings command
count to 189.
2026-05-07 00:05:54 -07:00
Kelsi
8b01ccd77f feat(editor): add --info-data-tree for non-destructive migration progress
Companion to --migrate-data-tree. Walks <srcDir> recursively, counts
files per format pair (.m2 vs .wom, .wmo vs .wob, .blp vs .png, .dbc
vs .json), and reports per-pair counts plus an overall migration
share — the fraction of source files that already have an open
sidecar present.

A "sidecar" is matched by parent dir + stem (case-insensitive ext),
so the comparison is sound across nested trees. Orphan open files
(present without a matching proprietary) are also reported — those
are the candidates for keeping after stripping the originals.

Designed to drop into CI dashboards: a 100% migration share means
every proprietary asset has a deterministic open counterpart on disk
and the originals are safe to delete.

Verified on a mixed tree: 8 proprietary / 4 sidecars (50% share)
correctly reported per pair, plus 1 orphan .json detected. Brings
command count to 188.
2026-05-06 23:57:13 -07:00
Kelsi
b4ae83e639 feat(editor): add --migrate-data-tree end-to-end open-format orchestrator
Composes the four bulk converters into a single one-shot migration
of an extracted Data tree. Runs --convert-m2-batch → --convert-wmo-
batch → --convert-blp-batch → --convert-dbc-batch in order, streams
each step's full output through, then emits an aggregate summary
with per-step PASS/FAIL.

The headline open-format command: point it at a freshly extracted
Data dir and every .m2/.wmo/.blp/.dbc gets converted to its open
counterpart in one call. Idempotent — re-running on a partially-
migrated tree just re-attempts the originals (sidecars are
deterministic).

Verified: 4-format synthetic tree → all four sub-batches dispatched,
aggregate summary correctly reports per-step rc, exit 1 when any
sub-converter reports failures (verified with true exit code, not
shell pipeline gotcha). Brings command count to 187.
2026-05-06 23:44:23 -07:00
Kelsi
d93bf30978 feat(editor): add --convert-dbc-batch for bulk DBC→JSON migration
Final commit in the four-format batch-converter set. Walks <srcDir>
recursively for every .dbc file (case-insensitive) and re-invokes
--convert-dbc-json per file, writing a .json sidecar next to each
source.

The batch converters now cover the full proprietary→open transition:
  --convert-m2-batch   .m2  → .wom
  --convert-wmo-batch  .wmo → .wob (skipping _NNN group files)
  --convert-blp-batch  .blp → .png
  --convert-dbc-batch  .dbc → .json
Run all four against an extracted Data tree to migrate it end-to-end
to the open format ecosystem.

Verified: 3 .dbc files (case-insensitive, sub-dir) discovered;
.json file in same tree skipped; exit 1 on failures. Brings command
count to 186.
2026-05-06 23:31:38 -07:00
Kelsi
8f890ef1f3 feat(editor): add --convert-blp-batch for bulk BLP→PNG migration
Walks <srcDir> recursively for every .blp file (case-insensitive) and
re-invokes --convert-blp-png per file. The single-file converter
writes the .png as a sidecar next to the source by default, so a
batched run mirrors the standard "PNG sidecar everywhere" layout
that the editor's open-format runtime expects.

Verified discovery: 3 placeholder .blp files (lowercase, uppercase,
sub-dir) found correctly; .png file in same tree skipped; all fail
conversion as expected for empty bytes; exit 1 on failures. Brings
command count to 185.
2026-05-06 23:19:38 -07:00
Kelsi
3e2580e8b5 feat(editor): add --convert-wmo-batch for bulk WMO→WOB migration
Sibling to --convert-m2-batch. Walks <srcDir> recursively for every
.wmo file (case-insensitive) and re-invokes --convert-wmo per file
via a child process so the existing root-WMO + group-loading logic
is reused verbatim.

Skips group files (e.g. Stormwind_001.wmo) since the root WMO
converter already pulls those in transitively. Detection: stem ends
in _NNN with NNN being three digits.

Verified: 5 .wmo files → 2 root candidates, 3 group files correctly
skipped, summary line accurate. Brings command count to 184.
2026-05-06 23:07:34 -07:00
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