Procedural grass texture: jittered base color + scattered short
vertical blade strokes blending toward a brighter highlight hue.
Strokes wrap on Y so the texture tiles vertically. Reproducible
from a seed.
Defaults: density=0.15, seed=1, W=H=256. Brings procedural
texture pattern set to 14 commands.
Subdivided octahedron + smooth sin-product noise displacement.
Each seed produces a unique silhouette so scattering looks
varied. Defaults: radius=1, roughness=0.25, subdiv=2 (128
triangles), seed=1.
Pairs naturally with random-populate-zone for outdoor decoration
and brings the procedural mesh primitive set to 16.
New zone-scope command that drops a 6-texture starter pack
(grass, dirt, stone, brick, wood, water) into <zoneDir>/textures/
by fanning out to the procedural --gen-texture-* family. Saves
users from sourcing proprietary art when bringing up a new zone —
the entire pack is open PNG output from procedural generators.
Adds to the gen-random-zone family of project orchestrators.
Procedural wood grain texture: vertical streaks of varying width
between two hues, plus pseudo-random knots. Reproducible from a
seed via tiny LCG (no <random> dep). Suitable for doors, planks,
fences, crates.
Defaults: spacing=12px, seed=1, W=H=256. Brings procedural
texture pattern set to 13 commands.
Procedural brick wall texture with offset rows + mortar lines. Each
row alternates by half a brick width, mortar gap appears between
rows and within each row. Useful for walls, chimneys, paths, and
medieval-zone props.
Defaults: brickW=64 brickH=24 mortarPx=4 W=H=256. Brings procedural
texture pattern set to 12 commands.
Procedural tree: cylindrical trunk (12 segments) + UV-sphere
foliage (12 segs × 8 stacks). Foliage centered above the trunk
with a slight overlap so the trunk pokes into the bottom of the
canopy.
Useful for ambient zone decoration, distant tree placeholders,
magic-grove props. The 15th procedural primitive — composite of
cylinder + sphere with everything in a single batch so it ships
as one mesh.
Args: <wom-base> [trunkRadius] [trunkHeight] [foliageRadius]
Defaults: 0.1 / 2.0 / 0.7 (3.2y total height — small/medium tree).
Pairs naturally with --add-texture-to-mesh for the bark+leaf
textures, or just one composite texture since this is a single-
batch mesh. Verified: defaults produce 143 verts / 216 tris.
Times each --audit-project sub-step end-to-end so users can see
where the slow checks are. Useful for tuning a CI pipeline: drop
the slowest check from a fast-feedback pre-commit hook, run the
full audit on push.
Reports wall-clock per step, share of total, and pass/fail status
for each. Status column means the check ran cleanly even on a zone
with content issues — bench timings are about runtime, not
correctness.
On a small test zone, items-schema and spawn-placement dominate
the share (~25% each) since they involve loading content +
sampling terrain. Format/open-only/refs/content are sub-10ms each.
Same value-noise function as --gen-texture-noise but interpolated
between two RGB endpoints rather than emitted as grayscale.
Useful for terrain detail (grass+dirt mottle), magic fog, marble
veining, or any "natural variation" pass that shouldn't be
desaturated.
Args: <out.png> <colorAHex> <colorBHex> [seed] [W H]
Defaults: seed 1, 256x256.
11th procedural texture pattern (joining solid, BW checker, color
checker, grid, vertical/horizontal/radial gradients, grayscale
noise, stripes, dots, rings).
Compares two zones' creatures + objects with two-pointer match-by-
(kind, name): paired entries with mismatched positions report as
"moved" with the (dx, dy, dz) delta; entries that exist in only one
zone are added/removed.
Useful for "what did this branch change vs main" before merging,
for confirming a copy-zone-items produced the expected result, and
for cross-zone consistency reviews.
Move threshold is 0.5y; smaller deltas count as "same" since
typical drift from snap-zone-to-ground is sub-yard. Exit 1 if
anything differs so CI can gate on cross-zone consistency.
Verified: 2 different random-populated zones → reports 6 added /
5 removed / 1 moved / 0 same with the moved entry showing its
position delta.
Extended the composite project audit from 4 sub-checks to 6,
adding the two new gates:
- validate-project-items (items.json schema across zones)
- audit-project-spawns (spawn Z within 5y of terrain)
Sub-checks now ordered cheapest → most expensive so a fast failure
surfaces before the slow ones run: format validation runs in
milliseconds, but spawn-placement audit requires loading every WHM
tile and casting rays.
Single-command release-readiness gate: a green --audit-project
now means format-clean + open-only + items-clean + refs-resolve +
content-sane + spawns-grounded.
The existing --gen-texture's "checker" pattern is fixed black/white
at 32px. This is the configurable variant: pass two colors and an
optional cell size to generate boards in any palette. Useful for
chess/checker game boards, kitchen-floor textures, hazard markers
in colors other than monochrome, retro UI backgrounds.
Args: <out.png> <colorAHex> <colorBHex> [cellPx] [W H]
Defaults: cellPx 32, 256x256.
10th procedural texture pattern (joining solid, the BW checker,
grid, vertical/horizontal/radial gradients, noise, stripes, dots,
rings).
Builds a doorway arch from two rectangular columns (one on each
side of the opening) plus a semicircular curved band across the
top. Total height = openingHeight + archRadius (where archRadius
= openingWidth/2). Aligned so the inside of the opening is
centered on the Y axis.
Args: <wom-base> [openingW] [openingH] [thickness] [depth] [segments]
Defaults: 1.0 / 1.5 / 0.2 / 0.3 / 12. Curve segments hard-capped
at 256.
Useful for cave entrances, gates, portal frames, dungeon
thresholds. The 12th procedural primitive (joining cube/plane/
sphere/cylinder/torus/cone/ramp/grid/disc/tube/capsule).
Verified: 1.0×1.5 opening with 12 segments produces 96 verts /
48 tris, bounds Z spans 0..2.0 (= 1.5 + 0.5 arch radius).
Recursively copies an entire project tree (every zone subdir +
manifests + content files). Refuses to overwrite an existing
destination so a typo doesn't silently merge into the wrong
project — user must delete the destination first if intentional.
Reports zone count, total file count, and total byte count of
what was copied. Useful for forking a project for experimentation,
or for creating snapshot backups before risky bulk operations
like --strip-data-tree or --remove-project-orphans.
Verified: 2-zone gen-random-project source → copy-project →
"zones: 2, files: 12, total bytes: 395989" reported correctly,
existing destination correctly rejected.
Project-wide companion to --info-zone-overview. Single-page table
with one row per zone showing biome / tile count / creature / object
/ quest / item counts and audio yes-or-no, plus aggregate totals
across the project.
Useful for "what's in this project" at-a-glance reviews and CI
artifacts that need a compact status snapshot. Cheap content
counts via direct JSON parse (same approach as --info-zone-overview)
so even a 100-zone project takes milliseconds.
Verified on 3-zone gen-random-project run: per-zone rows show
identical counts (5c/2o/3i each), totals row aggregates correctly
to 15c/6o/9i.
Where --info-zone dumps every manifest field, --info-zone-overview
is a tweet-length status: zone name, biome, content counts, audio
flag. Easy to grep through --for-each-zone output to spot
outliers.
Format: name [biome] Nt/Cc/Oo/Qq/Ii [+audio]
N = tiles, C = creatures, O = objects, Q = quests, I = items
+audio shown when music or any ambience is set
Cheap content counts via direct JSON parse — no need to spin up
the full NpcSpawner/ObjectPlacer/etc classes for an overview.
Both human and --json output modes.
Capsule along the Y axis: cylindrical body of length cylHeight
bookended by two hemispheres of `radius`. Total height is
cylHeight + 2*radius.
Useful for character collision shells, pill-shaped buttons,
hot-dog props, physics-friendly placeholders, sausages.
Layout: top hemisphere (stacks rings, north pole at the top) →
cylindrical body (1 quad band) → bottom hemisphere (mirror).
Per-vertex normals follow the smooth surface so shading works
correctly through the body-cap transition.
Args: <wom-base> [radius] [cylHeight] [segments] [stacks]
Defaults: 0.5 / 1.0 / 16 / 8.
Verified: defaults produce 340 verts / 544 tris with bounds
spanning the expected total height of 2.0 (= 1.0 cylinder +
2*0.5 hemispheres). Brings the procedural primitive set to 11.
Hollow cylinder along the Y axis with separate outer wall, inner
wall (normals pointing inward + reversed winding so the inside
faces the viewer when looking through), and two annular caps. UV
coords on the caps map the inner ring proportionally so a radial
texture stays continuous across both rings.
Useful for railings, fence posts, pipes, hollow logs, ring towers
— anywhere a solid cylinder would feel wrong because you should
be able to see through the middle.
Args: <wom-base> [outerR] [innerR] [height] [segments]
Defaults: 1.0 / 0.7 / 2.0 / 24. Validates innerR < outerR so the
walls don't intersect.
Verified: defaults produce 200 verts / 192 tris (4 surfaces × 48
quads × 2 tris = 384, halved by per-segment-pair indexing → 192).
Brings the procedural primitive set to 10 (cube/plane/sphere/
cylinder/torus/cone/ramp/grid/disc/tube + stairs + heightmap).
Where --info-cli-stats just counts per category, this lists every
command in each category. Categories sorted by count descending,
commands within each alphabetically. Header shows total count for
context.
Useful for "I know I want to gen something but what shapes/textures
are available?" — opens a clear view into the procedural toolset
that --help's flat dump buries. With 250 commands today the
categorization makes the surface tractable.
Flat circular disc on XY centered at origin. Center vertex + ring
of <segments>+1 vertices (extra one to break the UV seam),
indexed as a fan.
Useful for magic circles, coin meshes, lily pads, top caps for
cylinders the user wants without making a full cylinder, table
tops, etc. UV mapping is centered (0.5, 0.5) at the disc's center
and unit-circle-aligned at the rim, so radial textures (like the
new --gen-texture-rings) align correctly.
Args: <wom-base> [radius] [segments]
Defaults: radius 1.0, segments 32. Hard-capped at 1024 segments.
Verified: radius 0.5 / segments 24 produces 26 verts / 24 tris.
Detailed view of one creature or object by index. Where
--list-zone-spawns shows headline fields in a table, --info-spawn
dumps every field for a single record:
Creatures: id, name, modelPath, displayId, position, orientation,
level, health/mana, faction, scale, behavior mode, wander/aggro/
leash radii, respawn time, flag set (hostile/questgiver/vendor/
trainer), patrol-path waypoint count.
Objects: uniqueId, path, type (m2/wmo), position, rotation, scale.
Both kinds support --json for downstream scripts. Useful for
spot-checking individual records discovered via list-zone-spawns or
audit-zone-spawns.
Verified: random-populated zone → info-spawn creature 0 shows
Spider with all behavior fields populated; object 0 shows mushroom
WMO with rotation/scale.
Offsets each vertex along its current normal by heightmap brightness
× scale. UVs determine where each vertex samples the heightmap;
sampling uses bilinear filtering with UV-wrap so repeating UVs
tile correctly.
Pairs naturally with --gen-mesh-grid: gen a flat grid, then
--displace-mesh with a noise PNG to create procedural terrain. Or
use it on a sphere to make a bumpy planet, on a cylinder for tree-
bark deformation, etc.
Output reports the actual delta range produced so the user can
gauge the scale. A hint suggests running --smooth-mesh-normals
afterward since the post-displacement shading would otherwise
follow the original (now-stale) flat normals.
Verified pipeline: --gen-mesh-grid (32×32, 1089 verts) → --gen-
texture-noise → --displace-mesh (scale 2) → bounds X/Y stay ±5
while Z spans 0..1.93.
Flat plane on the XY plane subdivided into NxN cells. Useful for
LOD demos, deformable surfaces (later --displace passes), and
testbench geometry that needs many triangles for benchmarking.
Default size 1.0 (centered on origin), subdivisions hard-capped at
256 so a typo can't generate 130k+ vertices accidentally.
Vertex count is (N+1)² and triangles are 2N², both reported in the
output so the user sees the budget. Verified: N=16 size=4 produces
289 verts / 512 tris.
Generates <count> random zones in a single pass. Names default to
"Zone1, Zone2..."; tile coordinates step outward from (32, 32) in
a small raster so the zones don't overlap. Each zone gets a
unique sub-seed (seed + n) so its random content differs from its
siblings.
Args:
<count> required (1..100)
--prefix S name prefix (default "Zone")
--seed N base seed (default 100)
--creatures N per zone (default 20)
--objects N per zone (default 10)
--items N per zone (default 25)
Useful for spinning up a multi-zone playtest project from a single
command. Verified: count=3 with prefix Test produces Test1/Test2/
Test3 each with 4 creatures + 2 objects + 5 items, all on separate
tile coordinates around (32,32).
Project-wide companion to --list-zone-spawns. Combines creatures +
objects across every zone into one listing keyed by (zone, kind,
name) with position + a per-kind extra column (level for creatures,
scale for objects). Both human (table) and --json output.
Useful for project-wide review and for piping into spreadsheets via
--json — the zone column lets a pivot table group/filter by zone
which the per-zone command can't.
Composes scaffold-zone + random-populate-zone + random-populate-
items into a single invocation. Useful for "I just want a complete
test zone, don't make me chain three commands" — common case for
playtest fixtures and CI scenario setup.
Args:
<name> required (becomes the slug)
[tx ty] optional (default 32 32)
--seed N default 42 (items use seed+1 so they don't
correlate with creature/object placements)
--creatures N default 20
--objects N default 10
--items N default 25
Each sub-step's output streams through. If any step fails the
generator stops and reports which one. Slug-cleans the name to
match scaffold-zone's expectations.
Verified: TestZone with 5/3/8 counts produces a complete zone dir
containing creatures.json, items.json, objects.json, the .whm/.wot
tile pair, and zone.json.
One-command survey of "what's in this zone" without running both
--info-creatures and --info-objects separately. Pretty-prints two
tables (creatures and objects) with idx / level-or-type / position /
key-field columns. Also supports --json for downstream scripts.
Useful for quick spot-checks during placement and for piping into
review notes — the JSON form makes it easy to grep/jq for
specific entries.
Project-wide companion to --audit-zone-spawns. Spawns the binary
per-zone (only those with creatures.json or objects.json),
streams the per-zone reports, and exits 1 if any zone has spawns
flagged.
Optional --threshold N flag is forwarded to each sub-invocation so
projects with stricter or looser placement requirements get
consistent audits across zones.
CI-friendly pre-release placement check: drop into a pipeline,
fail the build if any spawn is more than N yards off terrain.
Non-destructive companion to --snap-zone-to-ground. Loads the
zone's terrain, samples the height under each creature + object,
and flags any whose Z is more than <threshold> yards (default 5)
off from the terrain.
Useful for surveying placement issues before deciding whether to
run --snap-zone-to-ground (which would silently rewrite every
spawn). The flagged report shows kind / delta / spawnZ / terrainZ /
name so the user can spot whether the spawn is buried, floating,
or just out of bounds.
Threshold configurable via --threshold N. Exit 1 if any issue is
flagged so CI can gate on placement validity.
Verified: zone with FloatyMurloc at Z=5000 (vs terrain Z=99.9)
correctly flags it with +4900.1 delta; GroundedWolf at Z=100 is
within threshold and not flagged.
Project-wide companion to --snap-zone-to-ground. Spawns the binary
per-zone (only zones that actually have creatures.json or
objects.json — pure-terrain zones are skipped to avoid noise),
streams the per-zone output, then aggregates a summary.
Useful after a project-wide terrain regen, or after batch-running
--random-populate-zone across multiple zones — one command snaps
all the spawns at once.
Verified: 2-zone test where only z1 has populated content correctly
selects z1 alone, snaps 3 creatures + 1 object, exits 0.
Project-wide companion to --info-zone-audio. Walks every zone in
<projectDir>, reads the audio fields, emits a per-zone yes/no
table for music + ambience plus the volume sliders. Counts how
many zones have music and how many have any ambience set so the
header tells you at a glance how much audio work remains.
Useful for spotting zones still missing audio assignment before a
release pass — rather than opening each zone in the editor to
check.
Verified: 2-zone project where only A has audio configured →
header reports "with music: 1, with ambience: 1" and the per-zone
table shows A=yes/yes, B=no/no.
Walks every creature in creatures.json and every object in
objects.json, samples the actual terrain height at each spawn's
(x, y), and writes that into the spawn's Z. Useful after terrain
edits or after --random-populate-zone if the spawn baseZ doesn't
match the carved terrain.
Height lookup: loads every WHM tile listed in zone.json, then for
each spawn finds the chunk containing its (x, y) and uses the
chunk's average heightmap height + base Z. Average rather than
bilinear because spawns don't need sub-yard precision and the
average dodges sampling-induced spikes near chunk seams.
Verified: random-populate-zone followed by snap-zone-to-ground on a
fresh tile snaps 4 creatures + 2 objects without errors. Brings
command count to 235.
Right-triangular prism that climbs along +X. Footprint is size×size
on the XY plane (centered in X, +Y from 0 to size); rises from
Z=0 at -X up to Z=size at +X. Useful for ramps onto platforms,
simple roof slopes, cliff-face placeholders.
Five faces emitted as quads (top slope, bottom, back-tall vertical
wall, two side triangles encoded as degenerate quads so the
indexing stays uniform): 20 verts / 10 tris (some side tris are
zero-area; renderers skip them cleanly).
Top-slope normal is computed from the actual rise/run so shading
shows the slope angle correctly. Help text on both --gen-mesh and
--gen-mesh-textured updated to advertise the new shape — primitive
set is now cube/plane/sphere/cylinder/torus/cone/ramp + the
gen-mesh-stairs/-from-heightmap commands.
Prints the music track, day/night ambience tracks, and the two
volume sliders stored in zone.json. Also supports --json. Useful
for spot-checking that the right audio assets are wired up before
bake/export, and for CI to assert the zone has audio configured.
Empty fields render as "(none)" in the human view; JSON form emits
the actual empty string. Brings command count to 234.
Seeded random items.json populator. Pulls a quality-banded prefix
("Worn" → "Eternal" depending on rolled quality) plus a noun
("Sword", "Tome", "Cloak"...) for plausible names, then randomizes
itemLevel and stack size around quality-appropriate baselines.
Useful for playtest loot tables that need bulk content without
hand-typing each entry. Reproducible from --seed; respects
--max-quality so a "trash loot" pass won't accidentally drop
artifacts.
Verified on a fresh zone: 8 items rolled with the seed=5 produces
a sensible spread (1 epic Tome, 1 epic Cuirass, 1 uncommon Bow,
5 common/poor) and unique IDs starting at 1. Brings command count
to 233.
Walks <zoneDir>/zone.json to compute the world AABB the zone
occupies, then drops N random creatures and M random objects with
positions inside that bbox. Seeded LCG so the same seed always
produces the same population — useful for reproducible playtest
scenarios and for CI fixtures that need deterministic content.
Default flags: --seed 42 --creatures 20 --objects 10. Creatures
draw from a small built-in bestiary (Wolf/Boar/Bear/Spider/Bandit/
Kobold/Murloc/Skeleton/Wisp/Goblin/Stag/Crab) with level jitter
around a per-name baseline. Objects pick from a generic placeholder
prop set (Tree/Boulder/Bush/Stump/Mushroom). Both lists are inline
so users can extend them without dragging in the asset browser.
Verified: 5-creature 3-object run produces a creatures.json with
real names + level-9 wolves + behavior=2 (Wander) + an objects.json
with real WMO paths, positions inside the zone bbox, randomized
rotation + scale, and unique IDs. Brings command count to 232.
Estimated bytes-per-category breakdown for a single WOM. Numbers
are based on the in-memory struct sizes (vertex 40B, index 4B,
bone 22B, batch 16B, anim keyframe 44B, plus texture path strings)
— not the actual on-disk encoding (which has framing overhead) —
but the relative shares are accurate and help users decide where
shrinking efforts pay off.
Surfaces actionable tips when a category dominates: animations >
vertices → suggest --strip-mesh --anims with the saved KB; bones
non-trivial → suggest --bones for static placement; vertices
dominate → suggest a lower-poly variant.
Verified on procedural cube: 960B vertices (85%) + 144B indices
(13%) + 16B batches + 8B textures, total 1128 estimated vs 1184
on-disk (~5% framing overhead). Tip correctly fires for
vertex-dominated mesh. Brings command count to 231.
Extracts a grayscale heightmap PNG from a row-major W×H heightmap
mesh. Y values are normalized to 0..255 using the mesh bounds so
the output PNG uses the full dynamic range.
User supplies W and H explicitly: arbitrary meshes aren't
necessarily heightmap-shaped, and taking the dimensions as args
avoids guessing wrong on a mesh with vertex count W*H but a
different layout.
Round-trips with --gen-mesh-from-heightmap modulo the 1-byte
quantization step.
Verified: noise PNG (32×32) → heightmap mesh → exported PNG (32×32)
chain executes cleanly, height 0..1.984 mapped to 0..255 grayscale,
both PNGs share the same dimensions. Brings command count to 230.
Recomputes per-vertex normals as the area-weighted average of
incident face normals. The cross-product magnitude is twice the
triangle area, so larger faces contribute more to the local
direction — gives a clean smooth-shaded result on curved surfaces.
Use cases:
- Imported geometry has no normals (--import-obj leaves them zero
or face-flat).
- Custom transforms have desynced normals from positions.
- Faceted-by-construction meshes (cube, stairs) need a smooth
re-shade for stylistic reasons.
Degenerate verts (unreferenced or with sum that cancels to zero —
e.g., the two poles of a UV sphere) fall back to (0,1,0) rather
than leaving NaN; reported separately so the user sees how many.
Verified: sphere → 219 of 221 normalized + 2 degenerate poles
handled cleanly; minimal triangle → 3/3 normalized. Brings command
count to 229 (kArgRequired 210).
Converts a grayscale PNG into a heightmap mesh. Each pixel becomes
one vertex; brightness becomes Y. Mesh is centered on the XZ plane
with X spanning [-W*scaleXZ/2, +W*scaleXZ/2] and Z spanning the
same for H.
Defaults: scaleXZ=0.1 (a 64×64 PNG covers a 6.4×6.4 yard patch),
scaleY=2.0 (full white pixels rise 2 yards above black). Capped at
512×512 source images to keep vertex count bounded (the cap raises
to 262K verts for the largest legal input).
Normals are computed from finite differences against the height
field, giving smooth shading across the surface. Single batch +
one empty texture slot ready for downstream binding via
--add-texture-to-mesh.
Pairs naturally with --gen-texture-noise (synthetic terrain) or
any artist-painted heightmap exported from Photoshop / GIMP.
Adds stb_image.h include — implementation is already linked via
the existing tools/editor/stb_image_impl.cpp.
Verified: 64×64 noise heightmap → 4096 verts (= 64²) + 7938 tris
(= 2×63²), 6.4×6.4 span, height range 0..1.99; 1024×1024 input
rejected with size-cap error. Brings command count to 228.
Synthesizes a two-color stripe pattern PNG. Stripe width in pixels,
plus direction (diagonal default, or horizontal/vertical). Color
endpoints share the same RRGGBB / RGB hex syntax as other texture
gens.
Useful for caution tape, marble bands, hazard markers, racing-style
start/finish flags — patterns that --gen-texture's checker/grid
don't capture.
Verified: 128×128 diagonal yellow/black at 32px ('caution tape')
and 256×256 horizontal red/white at 16px both written successfully
with correct headers reported. Brings command count to 227 (and
the texture-synthesis family to 6: solid + checker + grid + linear
gradient + radial gradient + noise + stripes).
Concatenates two WOMs into a single output. Vertex buffer is the
two inputs spliced; the second mesh's indices are offset by the
first mesh's vertex count; texture slots from both are appended;
batches from the second mesh have their indexStart shifted by the
first's index count and their textureIndex shifted by the first's
texture-slot count.
Single-batch / no-batch inputs are auto-promoted to one synthetic
batch each so the merged output is always a well-formed v3.
Bones/animations are NOT merged — that requires skeleton retargeting
which is out of scope. If either input has bones, the merged output
is treated as static (bones cleared, weights reset to identity-on-
bone-0) so renderers don't read mismatched indices.
Verified: cube (24v/12t/1 batch) + sphere translated to +X (221v/
384t/1 batch) → combined (245v/396t/2 batches/2 textures), bounds
union (-0.5..3.5 in X), clean v3 reload via --info-mesh. Brings
command count to 226.
Mirrors every vertex position and normal across the chosen axis
(x, y, or z). Negating just one position component reverses face
winding (the triangle's signed area flips), so the second and
third index of every triangle is also swapped to keep front-faces
forward and lighting correct. Bone pivots mirror too.
Useful for "I have a left arm, mirror it for the right arm" content
reuse — the open-format mesh ecosystem can build symmetric content
from one half-asset without artist round-trips.
Verified: cube translated to (+5,0,0) mirrored across X → bounds
correctly land at (-5.5,-0.5,-0.5)..(-4.5,0.5,0.5); 24 vertices
touched / 12 triangles flipped reported; bad axis 'w' rejected
exit 1. Brings command count to 225.
Single CSV with every item across every zone in <projectDir>. The
zone name is the first column so a pivot table can group by it;
remaining columns mirror --export-zone-csv items output (index, id,
name, quality, itemLevel, displayId, stackable).
Saves running the per-zone CSV exporter N times and concatenating
manually. Reuses the existing CSV-escape lambda so commas and
quotes in names round-trip cleanly.
Verified: 2-zone project (A: Sword, "Helm, with comma" / B: Crown)
emits 3 rows with the comma'd name correctly double-quoted; zone
column lets a downstream pivot group correctly. Brings command
count to 224 (kArgRequired 205).
Copies items from one zone to another. Two modes:
Default (replace): destination items.json becomes a verbatim copy
of the source. Useful when zoneB is a fresh copy of zoneA's loot
table.
--merge: appends source items to existing destination items, but
preserves the destination's existing IDs by re-id'ing source
entries on collision. The destination's items take precedence; the
source's entries get fresh IDs picked from the smallest unused
positive integer.
Verified: merge with id-1 collision (dest has Boot id=1; source has
Sword id=1, Helm id=2) → Sword becomes id=2, then Helm becomes id=3
because 2 was just claimed; "re-ided: 2 (id collisions)" reported.
Replace mode wholesale-overwrites as expected. Brings command count
to 223.
Synthesizes a radial gradient PNG fading from <centerHex> at the
image center to <edgeHex> at the corner. Distance is normalized so
the corner is t=1 (works for non-square images), with a smoothstep
curve giving a soft falloff rather than a harsh disc edge.
Useful for spell glow rings, vignettes, soft-edged decals, and
particle-emitter masks — the common "circular blob" cases that
linear gradients can't produce.
Verified: 64×64 white-center / black-edge "glow" PNG written
successfully; invalid 'notacolor' hex rejected with exit 1.
Brings command count to 222.
Two convenience commands for the common "fix imported mesh" cases:
--center-mesh <wom-base>
Translates the mesh so the bounds center lands at the origin.
Useful when an OBJ comes in with its pivot in some weird corner
and the user wants center-pivoted placement. Pure translation —
shape unchanged, radius preserved.
--flip-mesh-normals <wom-base>
Inverts every vertex normal. Use case: an OBJ with flipped
winding renders inside-out — flipping normals fixes shading
without re-winding the index buffer (which would also need
batch-aware care). Also useful for skybox-like inside-facing
meshes.
Verified: cube translated to (5,0,0) → center brings it back to
±0.5 with shift line "(-5, -0, -0)" reported; flip reports 24
vertices touched. Brings command count to 221 (kArgRequired 202).