- Fix shutdown hang: skip vmaDestroyAllocator (walked thousands of allocations),
replace unsafe pthread_timedjoin_np with plain join + early-exit checks in workers
- Bank window: full icon rendering, click-and-hold pickup (0.10s), drag-drop for
all bank slots including bank bag equip slots, same-slot drop detection
- Loading screen: process one tile per frame for live progress updates
- Camera reset: trust server position in online mode to avoid spawning under WMOs
- Fix PLAYER_BYTES/PLAYER_BYTES_2 field indices, preserve purchasedBankBagSlots
across inventory rebuilds, fix bank slot purchase result codes
Water strafe used opposite sign convention from land movement.
Land: strafeLeft += right, strafeRight -= right.
Water was: strafeLeft -= right, strafeRight += right.
Now matches land.
Coastal areas like Booty Bay had gaps in ocean water where MH2O
data didn't cover harbor chunks (only 34/256 chunks had ocean
entries). For sea-level ocean surfaces (type=2, height~0), the
mask is now initialized to fully visible instead of empty, letting
depth testing against terrain handle land occlusion naturally.
unloadAll() now uses a 500ms deadline with pthread_timedjoin_np to
avoid blocking indefinitely when worker threads are mid-prepareTile
(reading MPQ archives / parsing ADT files). Threads that don't finish
within the deadline are detached so the app can exit promptly.
unloadAll() joins worker threads which blocks if they're mid-tile
(prepareTile can take seconds for heavy ADTs). Replace with softReset()
which clears tile data, queues, and water surfaces without stopping
worker threads — workers find empty queues and idle naturally.
- Add VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT to water material
descriptor pool so individual sets can be freed when tiles are unloaded
- Free descriptor sets in destroyWaterMesh() instead of leaking them
- Add terrain manager unloadAll() during logout to properly clear stale
tiles, water surfaces, and queues between sessions
- Add diagnostic logging for water surface loading, material allocation
failures, and render skip reasons to investigate missing water
Camera collision with WMO walls and M2 doodads caused erratic zoom
and pull-through at doorway transitions. Terrain-only camera floor
clamping is retained.
- Add build.ps1/bat, rebuild.ps1/bat, debug_texture.ps1/bat (Windows equivalents
of existing bash scripts, using directory junctions for Data link)
- Fix asset extractor: StormLib is not thread-safe even with separate handles per
thread. Serialize all MPQ reads behind a mutex while keeping CRC computation and
disk writes parallel. Previously caused 99.8% extraction failures with >1 thread.
- Add SFileHasFile() check during enumeration to skip listfile-only entries
- Add diagnostic logging for extraction failures (first 5 per thread + summary)
- Use std::filesystem::temp_directory_path() instead of hardcoded /tmp/ in
character_renderer.cpp debug dumps
- Update debug_texture.sh to use $TMPDIR fallback and glob for actual dump filenames
Recreate Vulkan swapchain in LoadingScreen::render() when the dirty flag
is set, so resizing the window during world loading no longer renders
into a stale swapchain with mismatched framebuffers.
- Windows: SetThreadAffinityMask to pin main thread to core 0 and
exclude workers from core 0
- macOS: thread_policy_set with THREAD_AFFINITY_POLICY tags to hint
scheduler separation (tag 1 for main, tag 2 for workers)
Water deduplication: merge per-chunk water surfaces into per-tile surfaces
to reduce Vulkan descriptor set usage from ~8900 to ~100-200. Uses hybrid
approach — groups with ≤4 chunks stay per-chunk (preserving shore detail),
larger groups merge into 128×128 tile-wide surfaces.
Re-add incremental tile finalization state machine (reverted in 9b90ab0)
to spread GPU uploads across frames and prevent city stuttering.
Pin main thread to CPU core 0 and exclude worker threads from core 0
to reduce scheduling jitter on the render/game loop.
StormLib's bundled libtomcrypt uses x86 inline assembly (bswapl/movl)
gated by __MINGW32__, which is defined on CLANGARM64 too. Pass
-DLTC_NO_BSWAP to force portable C byte-swap fallback.
The incremental advanceFinalization state machine broke water rendering
in ways that couldn't be resolved. Reverted to the original monolithic
finalizeTile approach. The other performance optimizations (bone SSBO
pre-allocation, WMO distance culling, M2 adaptive distance tiers)
are kept.
Phase-splitting across frames caused water surfaces to not render
correctly. Changed processReadyTiles to run all phases for each tile
before moving to the next, with time budget checked between tiles.
Replace monolithic finalizeTile() with a phased state machine that spreads
GPU upload work across multiple frames (TERRAIN→M2→WMO→WATER→AMBIENT→DONE).
Each advanceFinalization() call does one bounded unit of work within the
per-frame time budget, eliminating 50-300ms frame hitches when entering cities.
Additional performance improvements:
- Pre-allocate bone SSBOs at M2 instance creation instead of lazily during
first render frame, preventing hitches when many skinned characters appear
- Enable WMO distance culling (800 units) with active-group exemption so
the player's current floor/neighbors are never culled
- Add 4-tier adaptive M2 render distance (250/400/600/1000 based on count)
- Remove dead PendingM2Upload queue code superseded by incremental system
Fix tile re-enqueueing bug: keep tiles in pendingTiles until committed to
loadedTiles (not when moved to finalizingTiles_) so streamTiles() doesn't
re-enqueue tiles mid-finalization. Also handle unloadTile() for tiles in
the finalizingTiles_ deque to prevent orphaned water/M2/WMO resources.
- Fix StormLib package name: libstormlib-dev → libstorm-dev (correct
Ubuntu package name) across all CI workflows and extract_assets.sh
- Build StormLib from source on Windows CI (no MSYS2 package exists),
ensuring asset_extract.exe is included in release archives
- Update extract_assets.sh/.ps1 to prefer pre-built asset_extract
binary next to the script (release archives) before trying build dir
- Move ADTTerrain allocations from stack to heap in prepareTile() to
fix stack overflow on macOS (worker threads default to 512 KB stack,
two ADTTerrain structs ≈ 560 KB exceeded that)
When M2Renderer's descriptor pool was exhausted, batch.materialSet
would be VK_NULL_HANDLE and the bind was skipped, but the draw call
still executed using the previously bound descriptor set from
CharacterRenderer — causing game objects to render with the player's
skin/armor textures. Skip the entire batch instead.
Character was facing stage-left (yaw 180) instead of toward camera;
corrected default yaw to 90. Added MSAA support to VkRenderTarget with
automatic resolve attachment, and enabled 4x MSAA for the character
preview off-screen pass.
Remove freeze-while-moving and idle smoothing logic from shadow
center computation. Texel snapping already prevents shimmer, so
the shadow projection can track the player directly each frame.
Remove isInterior restriction from WMO shadow sampling so city
buildings (flagged as interior groups) correctly receive shadows.
Apply shadow to interior-lit surfaces. Enable shadows by default
and persist the setting across sessions.
Zoom speed now scales with distance for fine control near the character.
Default max zoom out reduced from 50 to 33 units. New "Extended Camera
Zoom" toggle in Gameplay settings restores the full 50-unit range.
- Tighten celestial glow falloff and edge fade to eliminate visible quad boundary
- Add opacity support to minimap display shader, synced with UI opacity setting
- Fix audio double/triple-scaling by removing redundant master volume multiplications
- Rename "Original Soundtrack" toggle to "Enable WoWee Music"
- Add About tab with developer info and GitHub link
Alpha blending caused faint quad edges to be visible against the sky
gradient. Additive blending correctly adds glow light without the
outline artifact. Edge fade starts earlier and discard threshold raised.
Ripples were cleared every frame the player wasn't swimming, which
destroyed foot splash particles the same frame they were spawned.
Now particles expire naturally via their lifetime.
- Replace dark edge RGB on alpha-tested foliage with mip-4 average color
proportional to alpha (low alpha = low trust in original RGB)
- Raise foliage alpha cutoff to 0.4 and remove dither band for cleaner edges
- Disable depth test on insect particles so they don't vanish behind foliage
- Exempt dragonflies/butterflies/moths from foliage animation freeze
Fireflies, dragonflies, butterflies, and moths no longer get
disableAnimation/foliage classification so their bone animation
and particles run normally.
- Spawn dark point-sprite insects buzzing around cattails/reeds/kelp/seaweed
- Fix firefly M2 particles: exempt from alpha dampening and forced gravity
- Make water shoreline/crest foam more irregular with UV warping and bluer tint
- Play WaterFootstep splash sounds when wading in shallow water
- Spawn foot splash particles at water surface on each water footstep
- Reduce inland water wave amplitude from 0.18 to 0.08 for calmer lakes
Remove negative cache for transient load failures in M2, terrain, and
character renderers. Failed textures return white and will be retried
on next model/tile load when assets may have finished streaming.
- Use authoritative playerRace/playerGender at spawn for voice profiles
instead of unreliable model name parsing
- Support nonbinary gender with useFemaleModel body type fallback
- Move voice setup into spawnPlayerCharacter() for all spawn paths
- Remove legacy single-player default Human Male clip preloading
- Make loading screen text black and move progress bar to top
- Cache material UBO mapped pointers at creation time, eliminating
per-batch vmaGetAllocationInfo() calls in the hot render path
- Precompute foliage/elven/lantern/kobold model name classifications
at load time instead of per-instance string operations every frame
- Remove redundant descriptor set and push constant rebinds on WMO
pipeline switches (preserved across compatible layouts)
- Pre-allocate glow sprite descriptor set once at init instead of
allocating from the pool every frame
Shadow casting: foliage batches now bind their actual texture in the shadow
pass with alpha testing, producing leaf-shaped shadows instead of solid cards.
Uses a per-frame resettable descriptor pool for texture sets.
Shadow receiving: foliage fragments now sample the shadow map with PCF
instead of using a flat constant darkening.
Gameobject M2 instances (books, crates, chests) were continuously
cycling their animations because M2Renderer unconditionally loops
all sequences. Added setInstanceAnimationFrozen() and freeze all
gameobject instances at creation time so they stay in their bind pose.
removeInstance() and removeInstances() were erasing M2Instances without
calling destroyInstanceBones(), leaking VMA bone buffers permanently.
This caused framerate to drop and never recover after NPC encounters.
All three shadow renderers (WMO, M2, Character) were iterating every
loaded instance with zero culling. Now skip instances outside the
180-unit shadow frustum radius via squared-distance check.
Raise all texture cache defaults from 1GB to 4GB to reduce rejections.
Cap cache-full warnings (texture + model) to 3 messages per renderer,
and cap update block parse errors to 5 messages.
Strip 26 chrono::now() timing calls per frame from renderer update loop,
periodic LOG_INFO/LOG_DEBUG from terrain/character/quest/heartbeat paths,
and dead m2ProfileCounter variable.
Strip per-frame/periodic logging from CharacterRenderer (batch dump,
instance count) and M2Renderer (frame timing profiler) to avoid
unnecessary string formatting and I/O in render loops.