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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
A NaN move/rotate/scale delta would poison every selected object's
transform permanently and produce NaN model matrices in the
renderer. Reject upfront.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
--info-wcp gives counts and totals; --list-wcp gives the full file
listing sorted by path. Useful for spotting missing texture/model
entries before unpacking and shipping a zone.
The previous escape only doubled quotes and backslashes. A quest
description containing a literal newline would emit a multi-line
INSERT that breaks per-line execution scripts; a NUL byte could
prematurely terminate the string in non-length-prefixed clients;
Ctrl-Z is the historical MySQL string terminator on Windows.
Now full MySQL/MariaDB string-literal escape: NUL drops, CR/LF/tab
become \r/\n/\t, Ctrl-Z becomes \Z.
Completes the --info-* family. Reports total/chained quest counts,
reward/item counts, total XP awarded, objective-type breakdown
(kill/collect/talk), and any quest-chain validation errors. Lets
zone authors spot broken chains, missing rewards, and lopsided XP
balance from the command line.
Mirrors --info-creatures and the other format inspectors. Reports
total placement count, M2/WMO breakdown, unique source paths, and
scale range. Useful for spotting empty zones, accidental scale
extremes, or duplicated placements before packing.
Mirrors the existing --info-wom/wob/woc/wot/wcp inspectors. Reports
total spawn count, hostile/questgiver/vendor/trainer flag counts,
behavior breakdown (stationary/wander/patrol), and unique displayId
count. Useful for triaging zone NPC content from the command line.
The previous --validate output told you whether *some* file of each
type existed, which was hard to act on for partially-valid zones.
Now reports the per-format file count and how many failed magic
validation, e.g. 'WOM (12 invalid: 2)' so a zone author can spot
missing or corrupted models without grepping through file listings.
readInfo previously trusted fileCount/infoSize blindly, so a malicious
or corrupted WCP could allocate a 4GB string just to print metadata
via --info-wcp. Same 1M file / 16MB info caps now applied. Also
categorizes .woc collision files (was bucketed under 'other').
Mirrors the load-side sanitize. nlohmann throws on NaN/inf, so
a corrupted in-memory baseHeight or volume would abort the manifest
save and lose all zone-level settings (flags, audio config, etc.).
Same json-serialization-safety fix as for NPC and WOT saves.
Object position/rotation NaN would abort the entire objects.json
save and lose all placement work in the session.
nlohmann::json throws on non-finite serialization, which would
abort the entire creatures.json save and lose every spawn change
in the session. Scrub position/orientation/scale/radii/patrol
floats on the way out so a single corrupt NPC can't kill the
batch save.
nlohmann::json serialization throws on NaN/inf floats, which would
abort the entire WOT save and leave the user with an unsaved zone
state. Scrub on the way out so a single corrupted placement can't
take down the whole save.
map_dbc.MapName / area_table_dbc.AreaName are varchar(100). Edited
zone.json could carry longer strings that would either fail the
INSERT or silently truncate. Cap mapName/displayName at 100,
biome at 64, description at 4096.
creature_template.name is varchar(100) in AzerothCore. Edited
creature JSON could carry longer names that would either fail
the INSERT or silently truncate. Cap name at 100 and modelPath
at 1024 on load.
quest_template.LogTitle is varchar(200) in AzerothCore. Edited
quest JSON could carry longer strings that would either fail the
INSERT or silently truncate at the server. Cap title at 200 chars
and the longer text fields at 8KB on load.
Mirrors the existing --info-wom/--info-wob/--info-woc/--info-wcp
inspectors. Reports the tile coord, populated chunks, layer count,
water count, texture/doodad/WMO counts, and computed height range
across all chunks. Useful for triaging zones from the command line
without opening the GUI.
Same defensive guards as scatter: cap per-asset count at 50k to
prevent editor freeze under a high-density biome, and ensure scale
range preconditions (a<b, both positive) so distScale construction
doesn't undermine validity.
Same defensive guards now applied to NPC scatter: reject NaN/inf
center/radius, cap count at 100k (prevents editor freeze on huge
inputs), and ensure minScale < maxScale so uniform_real_distribution
preconditions hold.
A radius of 0 would either throw from the uniform distribution
constructor (uniform_real_distribution requires a < b) or divide
by zero in the sqrt-based area-uniform sampler. Also reject NaN
center, non-positive radius, and absurdly large counts (>10k)
which would freeze the editor on placement.
A creature_template row with modelid1=0 spawns an invisible NPC
in-game. Fall back to 11707 (a generic humanoid) on export so
"Creature"-named placeholder spawns are at least usable; the user
can edit the displayId after.
ADTWriter::writeFloat now coerces non-finite values to 0 before
emitting bits, so a stray NaN in a chunk position, MDDF rotation,
or MODF extent can't leak into the saved ADT and produce invisible
terrain or off-map placements after reload.