Commit graph

744 commits

Author SHA1 Message Date
Kelsi
bd4e7a302d feat(editor): add --gen-texture-brick wall pattern command
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.
2026-05-07 22:39:07 -07:00
Kelsi
6bd7f18328 feat(editor): add --gen-mesh-tree composite tree primitive
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.
2026-05-07 22:19:51 -07:00
Kelsi
010cf3b6c5 feat(editor): add --gen-mesh-fence repeating post + rail primitive
Repeating fence: N square posts along +X spaced postSpacing apart,
with two horizontal rails (top at 80% of post height, bottom at
30%) connecting consecutive posts. Posts are railThick × 2 wide;
rails are thinner.

Useful for fences around plots, pen boundaries, walkway dividers,
garden beds, paddock perimeters. The 14th procedural primitive.

Args: <wom-base> [posts] [postSpacing] [postHeight] [railThick]
Defaults: 5 / 1.0 / 1.0 / 0.05. Posts hard-capped at 256.

Verified: 5 posts × 1.0 spacing → 312 verts / 156 tris, X span
of 4.0 (= 4 gaps × 1.0).
2026-05-07 21:59:58 -07:00
Kelsi
01c6c306f2 feat(editor): add --bench-audit-project per-step perf breakdown
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.
2026-05-07 21:40:49 -07:00
Kelsi
41d50c824a feat(editor): add --gen-texture-noise-color two-color noise blends
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).
2026-05-07 21:22:33 -07:00
Kelsi
f4fd009a0c feat(editor): add --diff-zone-spawns for zone-to-zone comparison
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.
2026-05-07 21:04:14 -07:00
Kelsi
525b48ade7 feat(editor): add --gen-mesh-pyramid N-sided polygonal pyramid
N-sided polygonal pyramid with apex at +Y. 3 sides → tetrahedron-
like shape; 4 → classic square pyramid; 8+ → faceted approximation
of a cone.

Different from --gen-mesh cone: cone has smooth curved sides with
per-vertex radial-ish normals; pyramid has flat per-face normals
on each triangular side, giving a clearly faceted look.

Args: <wom-base> [sides] [baseRadius] [height]
Defaults: 4 / 1.0 / 1.0. Sides hard-capped at 256.

Useful for monuments, witch hats, gem props, treasure piles, dunce
caps. Brings the procedural primitive set to 13.

Verified: 4-sided pyramid → 17 verts / 8 tris (4 side + 4 base);
3-sided → 13 verts / 6 tris.
2026-05-07 20:45:11 -07:00
Kelsi
7a43e4eb3b feat(editor): --audit-project also runs items schema + spawn placement
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.
2026-05-07 20:26:17 -07:00
Kelsi
fb4421e5df feat(editor): add --gen-texture-checker with custom colors
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).
2026-05-07 20:08:34 -07:00
Kelsi
f6f3a7b060 feat(editor): add --gen-mesh-arch doorway/portal frame
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).
2026-05-07 19:50:00 -07:00
Kelsi
ef38998a4e feat(editor): add --copy-project recursive project tree copy
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.
2026-05-07 19:31:09 -07:00
Kelsi
6b6a3189e4 feat(editor): add --info-project-overview project-wide health check
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.
2026-05-07 19:13:11 -07:00
Kelsi
e91764a168 feat(editor): add --info-zone-overview compact one-line summary
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.
2026-05-07 18:55:28 -07:00
Kelsi
d7a389c848 feat(editor): add --gen-mesh-capsule pill-shaped primitive
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
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.
2026-05-07 18:36:56 -07:00
Kelsi
9479973366 feat(editor): status bar shows active biome
Adds a public EditorApp::getActiveBiome() accessor and renders the
biome name in the status bar between the tile coords and the
dirty marker. So the bar reads, e.g., "[Sculpt] mymap [32,32]
Forest *" — at a glance the user knows what biome the painting/
auto-paint passes will use.

Useful especially after creating a new terrain: the biome
selection previously only mattered at create time + during
"Generate Complete Zone", with no visible feedback that it was
remembered.
2026-05-07 18:19:24 -07:00
Kelsi
135b89299b feat(editor): add --gen-mesh-tube hollow cylinder/pipe primitive
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).
2026-05-07 18:01:42 -07:00
Kelsi
5e4a5f350f feat(editor): add --info-cli-categories grouped command discovery
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.
2026-05-07 17:43:58 -07:00
Kelsi
cedeeffc6c feat(editor): add --gen-mesh-disc circular flat plane primitive
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.
2026-05-07 17:27:33 -07:00
Kelsi
3810dfc510 feat(editor): add --info-spawn single-spawn detail view
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.
2026-05-07 17:10:25 -07:00
Kelsi
d64d188b13 feat(editor): add --displace-mesh for heightmap displacement on existing meshes
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.
2026-05-07 16:53:36 -07:00
Kelsi
b5bd7cdc05 feat(editor): add --gen-mesh-grid subdivided plane
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.
2026-05-07 16:36:50 -07:00
Kelsi
c9f054657d feat(editor): add --gen-texture-rings concentric ring pattern
Concentric circles centered on the image, alternating between two
colors every ringPx pixels. Useful for archery targets, magic-seal
floors, dartboards, hypnosis backdrops — anywhere a circular
alternation reads as intentional design.

Args: <out.png> <colorAHex> <colorBHex> [ringPx] [W H]
Defaults: ringPx 16, 256x256.

9th procedural texture pattern (joining solid, checker, grid,
vertical/horizontal/radial gradients, noise, stripes, dots).
Matches the established hex parser + size guards.
2026-05-07 16:20:27 -07:00
Kelsi
2a0cae36d4 feat(editor): add --gen-texture-dots polka-dot pattern
Solid background with circular dots arranged on a regular grid.
Useful for fabric/clothing textures, game-board patterns, mushroom
caps, and decorative tiling.

Args: <out.png> <bgHex> <dotHex> [radius] [spacing] [W H]
Defaults: radius 8, spacing 32, 256x256.

Adds the 8th procedural texture pattern (joining solid, checker,
grid, vertical/horizontal/radial gradients, noise, stripes).
Verified: 128x128 white-bg / red-dot at radius 12 + spacing 48
writes successfully.
2026-05-07 16:03:51 -07:00
Kelsi
6a7ea6dcfc feat(editor): in-editor "Audit Spawns Against Terrain" menu
EditorApp::auditSpawnsAgainstTerrain(threshold) counts every
creature + object whose Z is more than `threshold` yards off the
sampled terrain. Returns the issue count; non-mutating.

Generate menu gets a new "Audit Spawns Against Terrain" item that
runs the audit at the default 5y threshold and shows a toast: a
clean count if everything's fine, or the issue count + a hint to
run "Snap All" if not. Surfaces placement bugs without dropping
to the CLI.

Pairs with the existing "Snap All Spawns to Ground" so the
workflow stays inside the editor: audit → see count → snap →
audit again.
2026-05-07 15:47:26 -07:00
Kelsi
6c84a8da6c feat(editor): add --gen-random-project for batch zone generation
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).
2026-05-07 15:31:20 -07:00
Kelsi
e316cae94c feat(editor): add --list-project-spawns cross-zone spawn listing
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.
2026-05-07 15:15:20 -07:00
Kelsi
fba11943e3 feat(editor): add --gen-random-zone end-to-end one-shot generator
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.
2026-05-07 14:59:42 -07:00
Kelsi
998ee7ce04 feat(editor): add --list-zone-spawns combined creature+object listing
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.
2026-05-07 14:43:46 -07:00
Kelsi
bc5d22e357 feat(editor): zone audio panel gets "Play" preview buttons
Adds a small "Play" button next to the music and ambience dropdowns
that hands the selected file off to the OS default audio player —
'open' on macOS, 'xdg-open' on Linux, 'start ""' on Windows.
Buttons only appear when a file is set.

Avoids pulling SDL_mixer (or any new dep) into the editor binary
just for the preview workflow. Designers already have a working
audio player; this just routes the file there with one click
instead of having them hunt through Data/Sound/Music in their file
manager.

Closes the third leg of the original "dropdown + browse + play
audio to hear it" ask: dropdown is the auto-scanned combo from the
prior commit; browse-via-real-files works through the dropdown;
play preview works now via OS shell.
2026-05-07 14:28:52 -07:00
Kelsi
96ec734d06 feat(editor): add --audit-project-spawns project-wide placement audit
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.
2026-05-07 14:27:51 -07:00
Kelsi
f870a516dd feat(editor): in-editor "Snap All Spawns to Ground" menu
Adds EditorApp::snapAllSpawnsToGround() and a menu item under
Generate, mirroring the --snap-zone-to-ground CLI for users who
want the action without context-switching to a terminal.

Walks every NPC + object, casts a downward ray from baseZ+500y,
and writes the hit Z into spawn.position.z. Patrol waypoints on
each NPC are snapped too. Marks the project dirty + auto-save
pending so the change persists without a manual Ctrl+S.

Existing per-selection "Snap Ground" button on the right panel is
unchanged; this is the bulk version for "I just edited terrain
under a populated area."
2026-05-07 14:12:39 -07:00
Kelsi
f589fa20ce feat(editor): add --audit-zone-spawns to flag misplaced spawns
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.
2026-05-07 13:57:25 -07:00
Kelsi
8f17f5ae34 feat(editor): add --snap-project-to-ground orchestrator
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.
2026-05-07 13:42:19 -07:00
Kelsi
ddea06a0e4 feat(editor): add --info-project-audio cross-zone audio rollup
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.
2026-05-07 13:27:16 -07:00
Kelsi
d1138e283c feat(editor): add --snap-zone-to-ground bulk Z-resnap for spawns
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.
2026-05-07 13:12:09 -07:00
Kelsi
ff5f6a9070 feat(editor): extend --gen-mesh with ramp (right-triangular prism)
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.
2026-05-07 12:58:10 -07:00
Kelsi
7a624adada feat(editor): zone audio panel scans Data dir for music + ambience files
The Zone Audio panel previously only offered five hardcoded preset
paths. Replaced with a recursive directory walk of <data>/Sound/
Music and <data>/Sound/Ambience for any .mp3/.wav/.ogg files.
Results cached after the first scan; a Refresh button forces a
rebuild for when the user drops new files in.

Two ImGui combos populate from the scan:
- "Music File"     — picks from Sound/Music
- "Ambience File"  — picks from Sound/Ambience

Each combo has a "(none)" option to clear. Selecting an entry
updates both the manifest field and the existing manual text input
buffer below, so users can fine-tune the path after picking from
the combo.

EditorApp gets a public getDataPath() so the UI can reach the
configured asset root without exposing the rest of the private
state.

Audio playback preview is the remaining piece — needs SDL_mixer
or similar wired into the editor; not in this commit.
2026-05-07 12:43:58 -07:00
Kelsi
0cb6a4c536 feat(editor): add --info-zone-audio for inspecting zone audio config
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.
2026-05-07 12:30:00 -07:00
Kelsi
8c20f732ce fix(editor): random-populate ground-snaps spawns to actual terrain
Previously every random-populated creature and object dropped at
baseZ (the manifest's flat height), which sat below the carved
terrain on hills and floated above it in valleys. After
generateCompleteZone the terrain is far from flat, so the spawns
showed up clipping or floating.

Added an inline groundZ(x, y) helper that casts a downward ray
from baseZ+500y at the spawn's (x, y) and uses the hit position's
Z. Falls back to baseZ if the ray misses terrain (shouldn't happen
inside the loaded tile bbox but is safer than NaN).

Both creature and object loops now consult groundZ. Snap-to-ground
is the runtime placer's default behavior anyway, so this lines up
with the rest of the editor's expectations.
2026-05-07 12:15:45 -07:00
Kelsi
89dacba666 feat(editor): in-editor "Random Populate" menu mirrors CLI
Adds EditorApp::randomPopulateZone(creatureCount, objectCount, seed)
and a Generate-menu submenu with sliders for both counts plus a
seed input. Same logic and bestiary as the --random-populate-zone
CLI: 12 creature templates with level jitter, 5 placeholder WMO
prop types with randomized rotation/scale.

Spawns are placed within the loaded tile's world bbox, marked
dirty + auto-save pending so the populated content persists across
restarts.

The CLI command remains for CI / scripting / batch workflows; the
menu serves designers who want to populate from the GUI without
context-switching to a terminal.

Same-seed reproducibility: a hover hint reminds the user that the
seed determines the output deterministically.
2026-05-07 12:14:13 -07:00
Kelsi
bdf2497f46 feat(editor): paint roads to/from selected objects/NPCs
The path tool gains a "Append selected <object|NPC> as path point"
button that captures the currently-selected entity's world position
into pathPoints_. Workflow: place an inn + a town hall, select inn
→ click append, select town hall → click append, click Apply Path.
The road is then drawn between the two object positions.

When no object/NPC is selected, the button area shows a hint
instead. The button auto-kicks pathCapture_ from None into Waiting*
on first use so the user doesn't have to remember the setup
sequence.

Works with the multi-point polyline path tool from the previous
commit — append three or more objects to route a road through them
in order.
2026-05-07 11:59:52 -07:00
Kelsi
d96496147f feat(editor): add --random-populate-items seeded loot generator
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.
2026-05-07 11:57:55 -07:00
Kelsi
9dad8c2aa0 feat(editor): add --random-populate-zone seeded creature/object spawner
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.
2026-05-07 11:43:03 -07:00
Kelsi
4e4102bf4a feat(editor): river/road tool supports multi-point polylines
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
Path capture replaced with std::vector<glm::vec3> pathPoints_. The
flow is now: click "Click Start Point" → click terrain for first
point → keep clicking for additional waypoints → press Apply when
done, or Cancel to scrap. Apply iterates each consecutive pair as a
segment, calling carveRiver + paintAlongPath + fillWaterAlongPath
(or flattenRoad for road mode) per segment.

Hard-capped at 64 captured points so a runaway click handler can't
unboundedly grow the polyline. Toast at end reports the segment
count so users see the polyline took.

PathCapture states extended: None / WaitingStart / WaitingEnd /
WaitingMore. Backwards-compatible getPathStart()/getPathEnd()
return first/last points so the existing path-preview wiring keeps
working.
2026-05-07 10:32:19 -07:00
Kelsi
158ab192f0 feat(editor): river tool now fills water along the carved path
The river path tool used to carve the channel and texture the banks
but never added a water layer — users had to manually run
"Fill Water" afterward, which floods the entire tile. The fix is a
new TerrainEditor::fillWaterAlongPath() method that adds water
layers only to chunks the river segment passes through (within
width + chunk-half-diagonal of the line).

Per-chunk water height is set to the chunk's post-carve minimum
terrain height + 0.5y offset so the water sits visibly in the
channel without overflowing onto banks.

The river apply path now invokes carveRiver → paintAlongPath →
fillWaterAlongPath in sequence. Toast updated to mention all three.

Multi-point rivers are still next on the list — the underlying
math takes a single segment today, so a polyline river needs a UI
revamp to capture N points + a per-segment loop. Ack'd, not done
this commit.
2026-05-07 10:17:36 -07:00
Kelsi
ecba93d4a4 fix(editor): NPCs default to Wander behavior + UI tooltip
CreatureSpawn::behavior defaulted to Stationary, so newly placed
NPCs would never move at runtime. The "NPCs do not patrol or
wander, they stay put" complaint was a default-value issue, not a
missing feature — switching the default to Wander (radius 10y)
gets normal NPCs roaming out of the box.

Added a tooltip on the Behavior combo making it explicit that this
is the runtime mode (the editor's preview doesn't run movement
logic; the behavior kicks in once the zone ships and the server
consumes the exported SQL). Lists what each mode means:
Stationary / Patrol / Wander / Scripted.

Existing zones with explicitly-set Stationary NPCs are preserved
on load — only fresh defaults are affected.
2026-05-07 10:03:17 -07:00
Kelsi
3a6f119f7a fix(editor): NPC nameplates align with NPC + togglable to selected only
Three issues fixed in one pass:

1. Z-offset was 35 yards — nameplates floated way above the actual
   NPC position. Dropped to 3.5 yards (just above an average
   creature's head) so the label reads as attached.

2. Horizontal alignment was a fixed -30 px offset, which only looked
   centered for ~6-character names. Switched to ImGui::CalcTextSize
   and centering on the projected position.

3. Nameplates were always-on, which got noisy fast on populated
   zones. Added "Nameplate on selected only" checkbox in the NPC
   panel (default ON); when unchecked, all NPCs show their names.

Vertical position also slightly tightened (sy - textSz.y - 2 vs
sy - 10) so the text baseline aligns rather than floating below.
2026-05-07 09:48:59 -07:00
Kelsi
0e2b55f9fd fix(editor): crater button now click-to-place on terrain
The "Create Crater at Cursor" button used the brush's last hover
position, which was stale by the time the user clicked the button —
the cursor had to move onto the button itself, dragging the brush
position along with it. Result: crater spawned somewhere between
where the user wanted and the button.

New flow: button arms a "click on terrain to place crater" mode
(captures the user's chosen radius/depth/rim at arm time). The next
left-click anywhere on terrain spawns the crater at the actual click
position, regardless of brush state. Button label changes to "ARMED
— click on terrain to place" while waiting, with a Cancel button +
Esc to dismiss.

Misses on terrain leave the mode armed so the user can retry without
re-pressing the button.
2026-05-07 09:34:17 -07:00
Kelsi
b35c9341ec fix(editor): WASD/QE flycam works while hovering ImGui panels
Camera key events were gated on io.WantCaptureKeyboard, which goes
true whenever any ImGui panel has focus — not just when typing into
a text widget. Hovering the cursor over a side panel silently
disabled the flycam, which read as "WASD doesn't move the camera."

Switched the gate to io.WantTextInput, which is true only while a
text widget is actively accepting input. Now WASD/QE/Shift work
exactly when they should: always, except while you're typing into a
field.
2026-05-07 09:20:01 -07:00
Kelsi
c2eec42eb8 fix(editor): generateCompleteZone honors active biome
Previously generateCompleteZone() applied a hardcoded heightband
texture set (Tanaris sand → Elwynn grass → Barrens rock →
Dragonblight snow) regardless of which biome the user picked in the
"New Terrain" dialog. The biome's textures were correctly applied
by createNewTerrain, then immediately overwritten by the
hardcoded auto-paint pass. Result: every "Create + Generate" run
looked the same, masquerading as "the first biome sticks."

Fix: store the active biome on EditorApp and read it in
generateCompleteZone(), pulling the four-band texture set + slope
accent from getBiomeTextures() instead of hardcoding. Now selecting
Forest vs Desert vs Snow vs ... actually changes the painted
textures.
2026-05-07 09:19:08 -07:00