- White crosshair on minimap shows camera position in real-time
- Bulk Operations section in Object panel:
- "Delete All in Radius": removes all objects within brush radius
- "Snap All to Ground": raycasts every object downward to terrain
(fixes all floating objects in one click)
- Minimap legend updated with camera indicator
- Useful for cleaning up scattered objects or fixing placement height
- NPC default scale changed from 1.0 to 3.0 so creatures are visible
from typical editing altitude (WoW creature models are very small at
scale 1.0)
- Properties/Info panel uses ImGuiCond_Always for position so it stays
pinned to the right edge when the window is resized (was getting lost
off-screen before)
- Clear All now actually removes all objects and NPCs (was only clearing
selections before). Uses new ObjectPlacer::clearAll() method.
- New Terrain clears all objects/NPCs and resets viewport before creating
fresh terrain. Fixes stale state from previous session.
- Right-click context menu works on both objects AND NPCs with
appropriate options for each (Move/Rotate/Scale for objects,
Fly To/Duplicate for NPCs)
- Gizmo drag: left-click now confirms the transform (ends drag) instead
of requiring mouse-up. Right-click cancels. Camera no longer steals
mouse events while gizmo is active.
- Right-click on unselected area passes through to camera correctly
- "Fly To" button on selected objects and NPCs: moves camera 30 units
above the selected item for quick navigation on large zones
- Export now generates README.txt with zone summary: map name, tile
coords, object/NPC counts, and file listing
- Complete export package: zone.json + WDT + ADT + objects.json +
creatures.json + README.txt
- Object placer save/load: objects.json persists placed M2/WMO objects
across sessions (path, position, rotation, scale, type)
- Fixed Duplicate button in Object panel: now actually creates a copy
with correct path/type/scale instead of being a no-op stub
- Export Zone now saves objects.json alongside ADT/WDT/creatures/manifest
- Object JSON loader parses all fields for full round-trip
- Minimap now shows placed objects (yellow dots) and NPCs (red=hostile,
green=friendly) at their world positions on the height grid
- NPC Duplicate button: copies selected creature with 10-unit offset
for quick population of similar spawns
- Minimap legend: colored dots showing Object/Hostile/Friendly markers
- All positions correctly mapped from world coords to minimap UV space
- Zone manifest (zone.json): generated on export with map name, map ID,
tile list, biome, creature flag, and file paths. This is what the
wowee client will read to discover and load custom zones.
- Export workflow now produces a complete loadable zone package:
zone.json + MapName.wdt + MapName_X_Y.adt + creatures.json
- ZoneManifest class with save/load (JSON format)
- Custom map IDs start at 9000+ to avoid conflicting with retail maps
- New Terrain dialog shows helper text for map name format
- Remove markerRenderer_ initialization, shutdown, update, and clear
calls from EditorViewport (markers replaced by actual M2 rendering)
- Remove unused saveAdtRequested_/saveWdtRequested_ fields and their
void casts (replaced by unified exportZone workflow)
- Zero warnings across both wowee and wowee_editor targets
- Smooth Entire Tile: global smoothing pass with configurable iterations
(1-10). Smooths across chunk boundaries for seamless results. Updates
inner vertices from smoothed outer grid. Great after noise generation.
- Snap to Ground checkbox: on by default, objects placed at terrain
surface. Disable for floating/airborne objects.
- Random Rotation + Snap Ground checkboxes side-by-side for fast setup
- Toast notifications on noise apply and smooth operations
- Smooth pass uses cross-chunk neighbor averaging for edge continuity
- Random Rotation checkbox: each placed object gets a random Y rotation
(great for natural-looking tree/rock placement without manual tweaking)
- Placed Object List: collapsible list in Object panel showing all placed
objects with name and position, click to select
- Both features reduce repetitive manual work when building dense zones
- Export Heightmap: File > Export Heightmap saves terrain as 16-bit
RAW grayscale (129x129) for use in external terrain editors or
as a backup. Configurable max height scale.
- Help overlay (F1 or Help menu): lists all keyboard shortcuts
organized by category (navigation, editing, object transform, view)
- Round-trip heightmap workflow: import → edit → export
- Import Heightmap: File > Import Heightmap loads RAW 8/16-bit grayscale
files (129x129 or 257x257) and maps to terrain heights with configurable
scale. Supports standard terrain editor heightmap formats.
- Toast notifications: non-intrusive green popup at bottom center for
user feedback (save confirmations, import results, errors)
- Toasts fade out after 3 seconds with alpha animation
- Auto-save now shows toast on save
- Quick-save (Ctrl+S) shows toast confirmation
- Erode brush mode: simulates water erosion by moving height downhill
based on slope, creating natural drainage patterns and gullies
- NPC JSON loader: File > Load NPCs parses saved creatures.json back
into the spawn list (round-trip save/load now works)
- Auto-save: every 5 minutes when unsaved changes exist, exports the
full zone (ADT + WDT + creatures) to the output directory
- Sculpt mode now has 6 brush types: Raise/Lower/Smooth/Flatten/Level/Erode
- Noise Generator in Sculpt panel: applies procedural value noise
with configurable frequency, amplitude, octaves, and seed
to create hills/valleys across entire tile instantly
- Sky/Lighting presets: View > Sky menu with Day (blue sky, high sun),
Dusk (orange, low sun), Night (dark blue, moonlight)
- Viewport clear color and light direction now configurable at runtime
- Noise uses smoothstep interpolation with octave fractal layering
- Punch Hole / Fill Hole buttons in Sculpt panel: creates terrain
holes (4x4 bitmask) for cave entrances, mine shafts, etc.
Uses brush radius to determine affected area.
- Recent Textures: paint panel shows last 6 used textures as quick-
select buttons (no need to re-search the full list)
- Holes saved in ADT format (MCNK holes field) and respected by
the mesh generator (triangles skipped at hole positions)
- Minimap window: 16x16 chunk grid colored by average height
(blue=low, green=mid, brown=high, blue overlay=water)
- NPC patrol path UI: add waypoints at cursor, view path list,
delete individual points or clear entire path
- Sculpt flatten "Pick" button: click to set target height from
cursor position instead of typing manually
- Height range displayed in minimap footer
- Terrain normals recalculated after height changes (smooth lighting
on sculpted terrain instead of flat-shaded appearance)
- Ghost preview and brush indicator cleared when switching modes
(prevents stale model instances or circles persisting)
- File > Clear All resets undo history and selections
- Normal computation uses finite differences from neighbor heights,
handles both outer (9x9) and inner (8x8) vertex grid positions
- Object scatter tool: place N copies of selected M2/WMO in a radius
with random rotation and scale range (Min/Max Scale slider)
- Camera bookmarks: F5 saves current position, View > Load Bookmark
to jump back — useful for working on different parts of a large zone
- Shortcut hints shown at bottom of Object panel
(G=move, R=rotate, T=scale, Del=remove)
- DragFloatRange2 for min/max scale in scatter UI
- Scatter tool: place N creatures in a radius around cursor position
with random rotation and uniform disk distribution
- File > Add Adjacent Tile: creates and exports a blank tile N/S/E/W
of current (foundation for multi-tile zone editing)
- Scatter UI: count slider (1-30), radius slider (10-200)
- Scatter places all copies with same stats/behavior as template
- Yellow circle renders at cursor position showing brush radius
- Visible in Sculpt, Paint, and Water modes
- Built from 48-segment quad strip slightly above terrain surface
- Renders through the water pipeline (alpha-blended, depth-tested)
- Disappears when cursor leaves terrain or in Object/NPC modes
- Brush VB cleaned up properly on shutdown
- Ctrl+S quick-saves entire zone (ADT + WDT + creatures.json) to output/
- File > Export Zone dialog saves all data to chosen directory
- exportZone() bundles ADT, WDT, and NPC spawns in one operation
- Info panel shows cursor world coordinates for placement debugging
- Info panel shows NPC count alongside object count
- Save state indicator: "Saved" (green) vs "* Unsaved (Ctrl+S)" (yellow)
- File menu reorganized: Quick Save + Export Zone replaces separate ADT/WDT
- Ctrl+Z in Object/NPC mode undoes last placement (50-deep stack)
- "Snap Ground" button raycasts straight down to place object on terrain
- Useful for objects placed too high or moved off terrain surface
- Undo stack adjusts indices when objects are removed mid-stack
- Raycast AABB now uses actual min/max vertex heights per chunk
instead of fixed ±200 padding (fixes misses on sculpted terrain)
- Right-click context menu opens correctly (deferred popup via flag
since ImGui::OpenPopup must be called within ImGui frame)
- Keyboard shortcuts: G=Move, R=Rotate, T=Scale, X/Y=axis lock,
Escape=deselect, Delete works in any mode for objects/NPCs
- Delete key now removes selected NPC in NPC mode too
- Ghost preview now shows in NPC mode (follows cursor with creature model)
- Added scale field to CreatureSpawn (default 1.0, slider 0.5-10x)
- NPC instances render at their configured scale
- Scale included in JSON save format
- M2Renderer::update() now runs AFTER beginFrame() so getCurrentFrame()
returns the correct frame index — fixes instance SSBO mismatch that
caused draws=0 despite loaded models
Standalone wowee_editor tool for creating custom WoW zones.
This is a rough initial implementation — many features work but
M2/WMO rendering still has issues (frame sync, texture layout
transitions) and needs further polish.
Terrain:
- Create new blank terrain with 10 biome types (Grassland, Forest,
Jungle, Desert, Barrens, Snow, Swamp, Rocky, Beach, Volcanic)
- Load existing ADT tiles from extracted game data
- Sculpt brushes: Raise, Lower, Smooth, Flatten, Level
- Chunk edge stitching prevents seams between tiles
- Undo/redo (100-deep stack, Ctrl+Z/Ctrl+Shift+Z)
- Save to WoW ADT/WDT format
Texture Painting:
- Paint/Erase/Replace Base modes
- Full tileset texture browser (1285 textures from manifest)
- Per-zone directory filtering and search
- Alpha map editing with 4-layer limit (auto-replaces weakest)
Object Placement:
- M2 and WMO model placement with full manifest browser (11k M2s, 2k WMOs)
- M2Renderer + WMORenderer integrated (loads .skin files for WotLK)
- Ghost preview follows cursor before placing
- Ctrl+click selection, right-click context menu
- Transform gizmo (Move/Rotate/Scale with axis constraints)
- Position/rotation/scale editing in properties panel
NPC/Monster System:
- 631 creature presets scanned from manifest, categorized
(Critters, Beasts, Humanoids, Undead, Demons, etc.)
- Stats editor: level, health, mana, damage, armor, faction
- Behavior: Stationary, Patrol, Wander, Scripted
- Aggro/leash radius, respawn time, flags (hostile/vendor/etc.)
- Save creature spawns to JSON
Water:
- Place water at configurable height per chunk
- Liquid types: Water, Ocean, Magma, Slime
- Rendered as translucent colored quads
- Saved in ADT MH2O format
Infrastructure:
- Free-fly camera (WASD/QE, right-drag look, scroll speed)
- 5-mode toolbar: Sculpt | Paint | Objects | Water | NPCs
- Asset browser indexes full manifest on startup
- Editor water/marker shaders (pos+color vertex format)
- forceNoCull added to M2Renderer for editor use
- AssetManifest::getEntries() and AssetManager::getManifest() exposed
Known issues:
- M2/WMO rendering may not display on first placement (frame index
sync between update/render was misaligned — now fixed but untested
end-to-end)
- Validation layer errors on shutdown (resource cleanup ordering)
- Object placement on steep terrain can miss raycast
- No undo for texture painting or object placement yet
The server can persist a corrupted near-origin position on map 0 (from a
faulty area-trigger destination) across sessions. On re-login it sends the
bad position via LOGIN_VERIFY_WORLD; if the player walks into the offending
trigger again the server re-teleports there, and our heartbeats reinforce
the bad save — creating a permanent teleport loop.
Defenses added:
- handleTeleportAck rejects MSG_MOVE_TELEPORT to near-origin on map 0
(no position update, no ACK, no world reload)
- applyPlayerTransportState rejects player UPDATE_OBJECT MOVEMENT blocks
pushing the same bad position
- sendMovement blocks heartbeats originating from near-origin so the
server cannot persist the bad save
- 10-second area-trigger cooldown after teleport / world entry / login
(replaces the one-shot suppress flag that re-fired on jitter)
- Immediate STOP+HEARTBEAT after teleport ACK / WORLDPORT ACK / login
to sync the real position with the server promptly
- CMSG_AREATRIGGER firing now logged at WARNING level for diagnosis
Re-allocate megaBoneSet_[0..1] in M2Renderer::clear() after vkResetDescriptorPool invalidates all
sets from boneDescPool_. Stale handles were bound to command buffers during rendering, causing
cascading validation errors. Also add ImGui::Dummy() after SetCursorScreenPos in the shaman totem
bar to satisfy ImGui's window boundary extension assertion.
Reject displayId values >100k (corrupted update-field data) to avoid
pointless DBC lookups and log spam. Add fileExists() guard before
using base texture path in 4 equipment compositing code paths that
were falling through without checking, causing excessive "Texture not
found" warnings when users have incomplete MPQ extractions.
- architecture.md: add chat system modules (src/ui/chat/), world map
modules (src/rendering/world_map/), CatmullRomSpline (src/math/),
transport decomposition, and updated namespace list
- status.md: update timestamp to 2026-04-14, add recent refactors
section and world map known gaps
- CHANGELOG.md: add detailed entries for PRs #58-63 covering
architecture, features, bug fixes, and 19 new test files
- TESTING.md: expand test suite layout from 8 to 27 files organized
by category (core, animation, transport, world map, chat)
- CONTRIBUTING.md: update namespace table, testing section, and key
files list to reflect new module directories
- README.md: update status timestamp to 2026-04-14
- Remove stale kVOffset (-0.15) from zone_highlight_layer hover detection;
the offset was removed from rendering but left in the hit-test path,
shifting hover ~15% vertically
- Add null guard for cachedGameHandler_ in ChatPanel::inputTextCallback
to prevent dereference before first render frame
- Zero WindowBorderSize in world map ImGui window to eliminate gap
between window edge and map content
- Replace hardcoded cosmic highlight multipliers with displayH×displayH
square rendering, preserving 1:1 aspect ratio at any resolution
- Skip transport waypoints where serverToCanonical zeroes nonzero input
instead of silently building paths with broken (0,0,0) coordinates
- Use length-squared check (posLenSq > 1.0) for spline endpoint
validation instead of per-component != 0 comparison, so entities
near the world origin are no longer skipped
- Fix off-by-one in ChatPanel::insertChatLink buffer capacity check
- Remove the -0.15 vertical offset (kVOffset) from coordinate_projection,
coordinate_display, and zone_highlight_layer; continent UV math is now
identical to zone UV math
- Switch world_map_facade aspect ratio to MAP_W/MAP_H (1002×668) and crop
the FBO image with MAP_U_MAX/MAP_V_MAX instead of stretching the full
1024×768 FBO
- Account for ImGui title bar height (GetFrameHeight) in window sizing and
zone highlight screen-space rect coordinates
- Add ZMP 128×128 grid pixel-accurate hover detection in zone_highlight_layer;
falls back to AABB when ZMP data is unavailable
- Upgrade PlayerMarkerLayer with full Vulkan lifecycle (initialize,
clearTexture, destructor); loads MinimapArrow.blp and renders a rotated
32×32 textured quad via AddImageQuad; red triangle retained as fallback
- Expose arrowRotation_ / arrowDS_ accessors on Minimap; clean up arrow DS
and texture in Minimap::shutdown()
- Wire PlayerMarkerLayer::initialize() into WorldMapFacade::initialize()
- Update coordinate-projection test: continent and zone UV are now equal
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Break the monolithic 1360-line world_map.cpp into 16 focused modules
under src/rendering/world_map/:
Architecture:
- world_map_facade: public API composing all components (PIMPL)
- world_map_types: Vulkan-free domain types (Zone, ViewLevel, etc.)
- data_repository: DBC zone loading, ZMP pixel map, POI/overlay storage
- coordinate_projection: UV projection, zone/continent lookups
- composite_renderer: Vulkan tile pipeline + off-screen compositing
- exploration_state: server mask + local exploration tracking
- view_state_machine: COSMIC→WORLD→CONTINENT→ZONE navigation
- input_handler: keyboard/mouse input → InputAction mapping
- overlay_renderer: layer-based ImGui overlay system (OCP)
- map_resolver: cross-map navigation (Outland, Northrend, etc.)
- zone_metadata: level ranges and faction data
Overlay layers (each an IOverlayLayer):
- player_marker, party_dot, taxi_node, poi_marker, quest_poi,
corpse_marker, zone_highlight, coordinate_display, subzone_tooltip
Fixes:
- Player marker no longer bleeds across continents (only shown when
player is in a zone belonging to the displayed continent)
- Zone hover uses DBC-projected AABB rectangles (restored from
original working behavior)
- Exploration overlay rendering for zone view subzones
Tests:
- 6 new test files covering coordinate projection, exploration state,
map resolver, view state machine, zone metadata, and integration
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
TransportManager decomposition:
- Extract TransportClockSync: server clock offset, yaw flip detection,
velocity bootstrap, client/server mode switching
- Extract TransportAnimator: spline evaluation, Z clamping, orientation
from server yaw or spline tangent
- Slim TransportManager to thin orchestrator delegating to ClockSync and
Animator; add pushTransform() helper to deduplicate WMO/M2 renderer calls
- Remove legacy orientationFromSplineTangent (now uses
CatmullRomSpline::orientationFromTangent)
Entity path following upgrade:
- Replace pathPoints_/pathSegDists_ linear lerp with
std::optional<CatmullRomSpline> activeSpline_
- startMoveAlongPath builds SplineKeys with distance-proportional timing
- updateMovement evaluates CatmullRomSpline for smooth Catmull-Rom
interpolation matching server-side creature movement
- Reset activeSpline_ on setPosition/startMoveTo to prevent stale state
Tests:
- Add test_transport_components (9 cases): ClockSync client/server/reverse
modes, yaw flip detection, Animator position eval, server yaw, Z clamping
- Link spline.cpp into test_entity for CatmullRomSpline dependency
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Extract CatmullRomSpline (include/math/spline.hpp, src/math/spline.cpp) as a
standalone, immutable, thread-safe spline module with O(log n) binary segment
search and fused position+tangent evaluation — replacing the duplicated O(n)
evalTimedCatmullRom/orientationFromTangent pair in TransportManager.
Consolidate 7 copies of spline packet parsing into shared functions in
game/spline_packet.{hpp,cpp}: parseMonsterMoveSplineBody (WotLK/TBC),
parseMonsterMoveSplineBodyVanilla, parseClassicMoveUpdateSpline,
parseWotlkMoveUpdateSpline, and decodePackedDelta. Named SplineFlag constants
replace magic hex literals throughout.
Extract TransportPathRepository (game/transport_path_repository.{hpp,cpp}) from
TransportManager — owns path data, DBC loading, and path inference. Paths stored
as PathEntry wrapping CatmullRomSpline + metadata (zOnly, fromDBC, worldCoords).
TransportManager reduced from ~1200 to ~500 lines, focused on transport lifecycle
and server sync.
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Add proper waypoint support to entity movement:
- Parse intermediate waypoints from MonsterMove packets in both WotLK
and Vanilla paths. Uncompressed paths store absolute float3 waypoints;
compressed paths decode TrinityCore's packed uint32 deltas (11-bit
signed x/y, 10-bit signed z, ×0.25 scale, waypoint = midpoint − delta)
with correct 2's-complement sign extension.
- Entity::startMoveAlongPath() interpolates along cumulative-distance-
proportional segments instead of a single straight line.
- MovementHandler builds the full path (start → waypoints → destination)
in canonical coords and dispatches to startMoveAlongPath() when
waypoints are present.
- Snap entity x/y/z to moveEnd in the dead-reckoning overrun phase
before starting a new movement, preventing visible teleports when the
renderer was showing the entity at its destination.
- Clamp creature and player entity Z to the terrain surface via
TerrainManager::getHeightAt() during active movement. Idle entities
keep their server-authoritative Z to avoid breaking flight masters,
elevator riders, etc.
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
- Mark swapchain dirty in Application's SDL resize handler (was only done
in Window::pollEvents which is never called)
- Skip swapchain recreation when window is minimized (0×0 extent violates
Vulkan spec and crashes vmaCreateImage)
- Guard aspect ratio division by zero when height is 0
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
- Delegate GameHandler::getQuestGiverStatus() to QuestHandler instead of
reading from GameHandler's own empty npcQuestStatus_ map
- Immediately add quest to local log in acceptQuest() instead of waiting
for field updates, fixing quests not appearing after accept
- Handle duplicate accept path (server already has quest) by also adding
to local log
- Remove early return on empty questLog_ in applyQuestStateFromFields()
- Re-query nearby quest giver NPC statuses on abandon so markers refresh
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Add Up/Down arrow keys to cycle through character list and Enter to
select. Claim arrow key ownership via SetKeyOwner to prevent ImGui nav
from moving focus to other widgets. Lock Enter key until release to
prevent the keypress from activating chat on the game screen.
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>