Commit graph

3767 commits

Author SHA1 Message Date
Kelsi
eb251639cf fix(editor): NaN-safe baseHeight propagation in addAdjacentTile
Source tile's chunks[0].position[2] could be NaN if mid-edit
terrain hadn't run stitchEdges yet. Fall back to 100.0 so the
adjacent tile doesn't start with poisoned base.
2026-05-06 08:37:19 -07:00
Kelsi
b7e3266c7a fix(m2): NaN guards on createInstanceWithMatrix boundary
Mirrors the createInstance guard. position drives the dedup hash
key (std::round of NaN is implementation-defined) and the matrix
flows into the GPU UBO.
2026-05-06 08:36:11 -07:00
Kelsi
61116e94a5 fix(m2): NaN guards on setInstancePosition and setInstanceTransform
Same boundary-rejection pattern as createInstance. NaN in either
function would corrupt the spatial grid (stale cells pointing at
NaN-bounded instances) and the GPU model-matrix UBO.
2026-05-06 08:34:53 -07:00
Kelsi
f96ea12fe7 fix(m2): reject NaN inputs at M2Renderer::createInstance
Even with all the upstream guards I've been adding, internal callers
or addon-style scripted spawns could pass NaN. Reject at the API
boundary so we never hash-key with NaN coords (std::round of NaN
is implementation-defined) or push a NaN instance into the model-
matrix uniform buffer (GPU crash / origin render).
2026-05-06 08:32:48 -07:00
Kelsi
4cbffe17d5 feat(editor): add --diff-wcp CLI for archive comparison
Compares two WCP archives file-by-file from their info JSON: lists
added (+), removed (-), and size-changed (~) entries. Useful for
verifying that an authoring tweak changed only what it claimed to
change, and for editor-version regression detection. Exit code 0
if identical, 1 otherwise.
2026-05-06 08:29:21 -07:00
Kelsi
07f4043343 fix(viewport): clear ghost preview on NaN/non-positive inputs
Without this guard, NaN cursor position from a degenerate raycast
would feed directly into the M2 renderer instance transform and
either crash on GPU or silently render at the origin.
2026-05-06 08:24:51 -07:00
Kelsi
303eeb9107 fix(validate): require minimum body bytes when checking format magic
A 4-byte file with just the right magic and no body would pass
the previous magic-only check but fail any actual loader. Require
at least 8 bytes (magic + 1 field) for a file to count as 'valid'
in the score.
2026-05-06 08:24:08 -07:00
Kelsi
8fb7690ea1 feat(editor): add --export-png CLI for terrain preview rendering
Renders heightmap, normal-map, and zone-map PNGs alongside a
WHM/WOT terrain pair. Useful for portfolio screenshots, ground-
truth map comparison, and quick visual validation without
launching the GUI.
2026-05-06 08:22:26 -07:00
Kelsi
21078f8806 feat(editor): add --build-woc CLI for headless collision generation
Loads a WHM/WOT terrain pair and writes a .woc collision mesh
alongside it. Terrain triangles only (no WMO overlays — those need
the asset manager) but enough for first-pass walkability while
authoring.

Verified end-to-end: scaffold-zone → build-woc → info-woc reports
32k triangles for a flat 256-chunk tile.
2026-05-06 08:21:14 -07:00
Kelsi
81832ea676 feat(editor): add --pack-wcp CLI for headless zone packaging
Mirrors --unpack-wcp. Accepts either a zone name (auto-resolved
under custom_zones/ then output/) or a directory path. Default
output is <name>.wcp in the current directory. Combined with
--scaffold-zone and --unpack-wcp, the editor can do the full
zone authoring round-trip from the command line.
2026-05-06 08:19:42 -07:00
Kelsi
b2fa4cd509 feat(editor): add --unpack-wcp CLI for headless extraction
Mirrors --info-wcp / --list-wcp. Default destination is
custom_zones/ (matches the GUI's preferred location). With both
this and --scaffold-zone, the editor binary can fully bootstrap
a zone install without launching the GUI.
2026-05-06 08:18:21 -07:00
Kelsi
ab5d574758 feat(editor): add --scaffold-zone CLI for empty zone bootstrap
Creates custom_zones/<slug>/ with a flat-terrain WHM, default WOT,
and minimal zone.json — score 3/7 on --validate, ready to open in
the GUI for further authoring. Saves the round-trip of launching
the GUI just to make a starter directory.
2026-05-06 08:17:33 -07:00
Kelsi
6657f08252 fix(wob): sanitize portal vertices/group indices on load and save
Three issues addressed:
- NaN portal vertices break the WMO portal-frustum cull, defeating
  the indoor optimization and forcing the whole interior to draw.
- Out-of-range portal.groupA/groupB indices walk past wmo.groups
  during cull; clamp to -1 (invalid) on load.
- Symmetric save-side scrub so we don't persist bad in-memory data.
2026-05-06 08:15:31 -07:00
Kelsi
9d37da45d8 test(wcp): add truncation detection test
Locks in the recent unpack truncation guard. Hand-writes a WCP
that declares dataSize=1000 but only ships 50 bytes; verifies
the unpack returns false AND that no partial file landed on disk.
2026-05-06 08:13:43 -07:00
Kelsi
aa2a70de8d fix(wcp): detect truncated WCP files on unpack
Previously a short read (truncated WCP, partial download, etc.)
would silently write the partial bytes that were read and report
success — leaving the consumer with a half-extracted zone that
would fail in confusing ways at runtime. Check gcount and return
false so the caller can refuse the broken pack.
2026-05-06 08:13:04 -07:00
Kelsi
44777c7d58 fix(mesh): clamp NaN terrain heights to 0 in vertex generation
WHM load already scrubs, but mid-edit terrain can briefly carry
NaN before stitchEdges runs. A single NaN vertex propagates into
normal computations and the chunk's frustum cull, crashing both.
2026-05-06 08:11:43 -07:00
Kelsi
19a4716ec1 fix(sql): scrub NaN coords/orientation when emitting INSERTs
ostream prints NaN as 'nan' which AzerothCore's SQL import rejects
with a syntax error — would silently break the entire export from
a single bad spawn. Defensive scrub at write time, mirroring the
load-side guard pattern used everywhere else.
2026-05-06 08:09:06 -07:00
Kelsi
a0876bbe3d fix(cli): error on non-GUI options that are missing their argument
Previously '--info-wcp' (no path) silently dropped into the GUI
because the option-parse loop's i+1<argc guard hid the typo. Pre-
scan and bail out with a helpful message before trying to start
the editor, so users get fast feedback on bad invocations.
2026-05-06 08:08:05 -07:00
Kelsi
2400271a4f test(brush): add 6 EditorBrush::getInfluence tests
Locks in the recent NaN/zero-radius/zero-falloff guards:
- inner radius returns full influence (1.0)
- at/beyond outer radius returns 0
- rim falls off as 1 - t² (smooth)
- NaN/inf distance rejected (returns 0, not 1 from short-circuit)
- zero/negative/NaN radius rejected
- zero falloff produces hard-edge brush
2026-05-06 08:05:43 -07:00
Kelsi
c9c4665642 fix(water): skip chunks with NaN water height in mesh build
Belt-and-braces — WOT load already scrubs the height to 0 if non-
finite, but if a downstream caller mutates terrain.waterData
in-memory the bad value would leak into the water mesh and Vulkan
would drop the entire batch on a single bad chunk.
2026-05-06 08:04:44 -07:00
Kelsi
55f9616aa6 fix(camera): NaN guards on pivot orbit, setPosition, setYawPitch
Three issues:
- processMiddleMouseMotion: NaN pivot poisons camera position
  permanently; the next frame produces NaN view/proj matrices.
- setPosition: no input validation — used by bookmark restore and
  fly-to-target which could be passed a NaN target.
- setYawPitch: same; also clamp pitch to [-89, 89] to match the
  mouse-motion path so a saved bookmark with bad pitch doesn't
  roll the camera upside down.
2026-05-06 08:03:57 -07:00
Kelsi
d03c96e3bd test(wcp): add unpack security tests
6 tests covering ContentPacker::unpackZone defenses:
- absurd fileCount header rejected
- absurd infoSize header rejected (16MB cap)
- relative path traversal ('../../etc/passwd_clone') rejected
- absolute paths ('/tmp/...') rejected
- malicious zone name slugified instead of escaping destDir
- bad magic rejected

Each test confirms the defense fires AND that no escape file
landed outside the test dir.
2026-05-06 08:03:02 -07:00
Kelsi
98f2a6c3bf fix(gizmo): hide on NaN target instead of building NaN geometry
setTarget previously stored the position raw, then updateBuffers
ran glm::normalize on axis offsets. NaN target → NaN normalized
axes → NaN gizmo vertices → Vulkan validation drops the whole
draw and the gizmo is invisible regardless of target value.

Hide the gizmo upfront so the user sees no gizmo (which is the
intent of the NaN handling) without leaking garbage into the
vertex buffer.
2026-05-06 08:01:23 -07:00
Kelsi
cdc9bb94ee fix(npc): NaN guards in NpcSpawner::selectAt distance test
Same NaN-comparison short-circuit pattern: NaN worldPos or NaN
spawn position would short-circuit dist < bestDist (NaN < x is
false), so it never updates bestIdx — but if every entry had NaN
the bestIdx stays -1 (correct) only because the first comparison
also fails. Belt and braces: skip NaN entries explicitly.
2026-05-06 08:00:36 -07:00
Kelsi
94469592f2 fix(objects): NaN guards in selectAt ray-sphere test
Without these, NaN ray or NaN object position would short-circuit
the disc < 0 early-out (NaN comparisons return false) and select
the object at a garbage t — silently 'picking' arbitrary objects.
2026-05-06 07:59:33 -07:00
Kelsi
0f15d0f3a0 fix(terrain): reject NaN rays in raycastTerrain
Without this, the AABB tests divide by ray.direction components and
NaN propagates through tmin/tmax into the triangle intersection,
returning undefined behavior at the hit position.
2026-05-06 07:58:56 -07:00
Kelsi
493cb68ddc fix(viewport): defensive NaN checks in patrol path ribbon
len < 0.001f returns false for NaN — same short-circuit class of
bug as elsewhere. Reject non-finite endpoints upfront and double-
check the computed length before dividing.
2026-05-06 07:57:54 -07:00
Kelsi
ba017193db fix(viewport): skip NPCs with NaN position in marker update
Same defensive pattern as updateObjectMarkers — non-finite NPC
position would produce NaN vertex positions and Vulkan would drop
the entire NPC marker batch, hiding every NPC marker in the zone.
2026-05-06 07:57:19 -07:00
Kelsi
b1cfef8264 fix(markers): skip objects with NaN position/scale on update
A non-finite object transform would produce NaN vertex positions
in the marker mesh — Vulkan validation flags it and dropping the
entire batch leaves all markers invisible. Skip the bad object
instead so the rest of the markers still render.
2026-05-06 07:56:26 -07:00
Kelsi
45dabaff44 fix(wcp): normalize separators before traversal check on unpack
Older WCP files packed on Windows (before pack-side normalization
was added) carry backslash separators. Normalize to '/' first so
the unpack works on any platform — and so the traversal check sees
a consistent canonical form (no more '\' special case).
2026-05-06 07:54:54 -07:00
Kelsi
439d1381f0 fix(editor): catch NaN-from-normalize when camera flies to a target
When the camera looks straight up/down, projecting forward onto XY
gives a zero vector — glm::normalize then returns NaN. The original
length<0.001 fallback ran AFTER the divide-by-zero, and NaN length
< 0.001 is false (NaN comparisons return false), so the fallback
never fired. Length-check the source before normalizing.
2026-05-06 07:53:41 -07:00
Kelsi
130aa34d73 fix(viewport): hide path preview on zero-length input
start == end would call glm::normalize on a zero vector, producing
NaN dir/perp and NaN ribbon vertex positions. Vulkan would either
drop the draw silently or trip a validation error. Hide the preview
when the segment is degenerate.
2026-05-06 07:53:07 -07:00
Kelsi
4babaebf86 fix(stamp): scrub NaN samples at save time
Symmetric with the load-side scrub. Without this, a stamp captured
on terrain that had a NaN mid-edit would throw on serialize and
abort the whole save.
2026-05-06 07:51:56 -07:00
Kelsi
ebb7e0f831 fix(wcp): normalize path separators on pack for cross-platform reads
WCP packs created on Windows would store paths with backslashes;
unpack on Linux/macOS would either fail the path-traversal check
('\' treated as absolute prefix) or land each file as a single
opaque filename rather than a directory tree. Normalize to '/' on
write so the format is portable in both directions.
2026-05-06 07:50:10 -07:00
Kelsi
ad65b2ad36 test(editor): add 4 quest validateChains tests + rename to test_editor_units
Tests cover:
- non-existent nextQuestId is flagged
- orphan quests (no questgiver, no turn-in) are flagged
- turn-in only quest is accepted (auto-completed quest pattern)
- circular chain is detected

Renamed test_sql_escape → test_editor_units since the file now
houses both SQL escape and quest validation tests.
2026-05-06 07:49:26 -07:00
Kelsi
ea713ae994 test(sql): add escape unit tests
5 tests covering: doubled single quotes (King's Land case),
backslash escaping, ordinary text passthrough, control characters
(NUL drop, CR/LF/tab/Ctrl-Z escape sequences), and combined
escapes. Locks in the recent escape expansion that fixed the
multi-line INSERT bug.
2026-05-06 07:47:58 -07:00
Kelsi
5366c53734 fix(objects): NaN guards on transform deltas
A NaN move/rotate/scale delta would poison every selected object's
transform permanently and produce NaN model matrices in the
renderer. Reject upfront.
2026-05-06 07:45:26 -07:00
Kelsi
2c5710b910 fix(terrain): NaN guards + zero-length checks on river/road/ridge generators
Same defensive pattern as paintAlongPath. carveRiver, flattenRoad,
and createRidge all called glm::normalize on a possibly-zero
direction vector, then divided by lineLen later. NaN endpoints
short-circuited dist comparisons and applied the height delta
to every vertex on every chunk.
2026-05-06 07:43:39 -07:00
Kelsi
869cee70b1 fix(painter): reject NaN endpoints and zero-length lines in paintAlongPath
Two bugs:
1. NaN start/end produced NaN distances that the chunk-skip check
   (dist > width + 40) treated as 'always within range', so every
   chunk got painted.
2. Zero-length line caused glm::normalize to return NaN; same
   downstream effect.

Compute lineDir manually after the length check so we never hit
the divide-by-zero path.
2026-05-06 07:41:58 -07:00
Kelsi
7e48658ab1 fix(terrain): NaN guards on createCrater and createMesa
Same defensive pattern as createHill — NaN center/radius would
short-circuit dist comparisons and apply the height delta to every
vertex on every chunk. Reject upfront.
2026-05-06 07:40:29 -07:00
Kelsi
f484b742db fix(editor): NaN guards on flattenAroundSelected and createHill
Same NaN-comparison short-circuit pattern: dist >= radius is false
when dist is NaN, so the loop body would run for every vertex and
write garbage heights / bend the terrain unbounded.
2026-05-06 07:39:35 -07:00
Kelsi
12bcd0ef8c fix(brush): defensive guards in EditorBrush::getInfluence
Three issues:
1. NaN distance returned 1.0 (full influence) because distance >=
   radius is false for NaN; the inner-radius check then returned 1.
2. Non-positive radius would divide by zero in the t computation.
3. falloff = 0 produces division by zero in the outer falloff path.

Also clamps falloff to [0,1] so a slider extreme can't break the math.
2026-05-06 07:38:50 -07:00
Kelsi
bd6e5fe3de fix(brush): reject NaN/non-positive brush settings before sculpt apply
Same NaN-comparison short-circuit bug as the texture painter — a
brush with a NaN cursor position would mark every vertex in every
'affected' chunk as full influence and silently rewrite huge
swaths of terrain. Reject upfront in applyBrush.
2026-05-06 07:37:54 -07:00
Kelsi
9207d54f20 fix(painter): reject NaN brush center / non-positive radius
NaN comparisons return false, so the dist >= radius early-out
would never fire and the falloff path would skip its inner check
too — the brush would paint full strength on every texel in the
chunk. Reject upfront.
2026-05-06 07:37:11 -07:00
Kelsi
891bb711a0 fix(editor): bound tile coords + NaN-guard baseHeight in createNewTerrain
Same defensive validation as loadADT — out-of-range tile coords
would generate broken save paths. Also guards against a NaN
baseHeight slider (would propagate into every terrain vertex).
2026-05-06 07:36:32 -07:00
Kelsi
d9d0797b7f fix(wcp): cap info JSON string lengths at pack time
A stray gigantic name/description/author field would inflate the
info JSON past the 16MB unpack cap and make the pack unreadable
via readInfo/unpackZone. Caps mirror the zone manifest limits.
2026-05-06 07:35:44 -07:00
Kelsi
efd0a6de29 fix(editor): reject out-of-range tile coords in loadADT
A tileX/tileY outside 0..63 would generate ADT paths the asset
manager refuses, then poison the manifest.tiles entries on save.
Reject upfront with a log message.
2026-05-06 07:34:58 -07:00
Kelsi
237cc67b24 fix(wcp): sanitize zone name before using it as a directory path
The unpacker used info.name verbatim as the destination subdirectory.
A malicious WCP could carry a name like '../etc' or '/usr/bin' to
write extracted files outside destDir. Now slugified to alphanumeric
+ underscore/dash, matching the server module slug rule.
2026-05-06 07:34:09 -07:00
Kelsi
6b06bd07f9 feat(quest): detect orphan quests + speed up chain validation
validateChains now also flags quests with no questgiver and no
turn-in NPC — those are unreachable in-game and a common authoring
mistake. Also replaced the O(n²) inner lookup with an O(1)
unordered_map of id → nextId so circular detection scales.
2026-05-06 07:33:31 -07:00
Kelsi
778f4aca3e fix(wob): warn + clamp uint32 indices on WMO conversion
WoB allows uint32 indices but WMO format is uint16. The previous
static_cast would silently wrap a >65k index into a wrong-but-
valid value — producing visible mis-stitched triangles in the
renderer. Now log a warning once per group and clamp to 0
(degenerate triangle) so the bug is visible.
2026-05-06 07:32:07 -07:00