- Split M2 instances into fast-path index lists (animated, particle-only,
particle-all, smoke) to avoid iterating all 46K instances per frame
- Cache model flags (hasAnimation, disableAnimation, isSmoke, etc.) on
M2Instance struct to eliminate per-frame hash lookups
- Replace full rebuildSpatialIndex on position/transform updates with
incremental grid cell remove+add, preventing 8.5ms/frame rebuild cost
- Advance animTime for all instances (texture UV animation) but only
compute bones and particles for the ~3K that need it
M2_UPDATE: 10.7ms → 2.0ms, FPS: 35 → 55-59
The single sceneColorImage races between frames with MAX_FRAMES_IN_FLIGHT=2:
frame N-1's water shader reads it while frame N's captureSceneHistory writes
it via vkCmdCopyImage. Pipeline barriers only sync within a single command
buffer, not across submissions on the same queue.
This caused VK_ERROR_DEVICE_LOST after ~700 frames on any map with water.
Disable the capture entirely for now — water renders without refraction.
TODO: allocate per-frame scene history images to eliminate the race.
The previous commit changed std::move to copy for terrain/mesh data to fix
the empty-cache bug. But copying ~8 MB per tile × 81 tiles caused a 60s
streaming timeout.
The tile cache was already broken before — putCachedTile stored a shared_ptr
to the same PendingTile whose data was moved out, so cached tiles always had
empty meshes. Remove the putCachedTile call entirely; tiles re-parse from
ADT files (asset manager file cache hit) when they re-enter streaming range.
The softReset cache clear from the previous commit remains as safety for
map transitions.
Three fixes:
1. Water captureSceneHistory gated on hasSurfaces() — the image layout
transitions (PRESENT_SRC→TRANSFER_SRC→PRESENT_SRC) were running every
frame even on WMO-only maps with no water, causing VK_ERROR_DEVICE_LOST.
2. Tile cache invalidation: softReset() now clears tileCache_ since cache
keys are (x,y) without map name — prevents stale cross-map cache hits.
3. Copy terrain/mesh into TerrainTile instead of std::move — the moved-from
PendingTile was cached with empty data, so subsequent map loads returned
tiles with 0 valid chunks from cache.
Also adds diagnostic skip env vars (WOWEE_SKIP_TERRAIN, WOWEE_SKIP_SKY,
WOWEE_SKIP_PREPASSES) and a 0-chunk warning in loadTerrain.
Add null checks for vertex/index buffers, pipelines, and zero-count
draws in WMO render path. The shadow pass already had buffer validation
but the main render() was missing it, which could cause GPU crashes
on WMO-only maps like Stockades (26 groups).
After VK_ERROR_DEVICE_LOST, beginFrame returns VK_NULL_HANDLE but
renderWorld() and renderHUD() were still called, passing the null
handle to vkCmdBindPipeline which triggered a validation abort.
When the GPU device is lost (unrecoverable Vulkan error), the app now
closes cleanly instead of looping with a black screen. Also adds
vk_context.hpp include for the isDeviceLost() check.
Two bugs in SMSG_MESSAGECHAT parser for MONSTER_SAY/YELL/EMOTE:
1. Sender name included trailing null byte from server (nameLen includes
null terminator). The embedded null in std::string caused ImGui to
truncate the concatenated display string at the NPC name, hiding
" says: <message>" entirely.
2. Missing NamedGuid receiver name for non-player/non-pet targets. When
the receiver GUID is a creature, the server writes an additional
SizedCString (target name) that we weren't reading, shifting all
subsequent field reads.
Also adds MONSTER_WHISPER, MONSTER_PARTY, RAID_BOSS_EMOTE, RAID_BOSS_WHISPER
chat types with proper parsing and display formatting (says/yells/whispers).
Root cause: LOGIN_VERIFY_WORLD path did not set areaTriggerCheckTimer_ or
areaTriggerSuppressFirst_, so the Stockades exit portal (AT 503) fired
immediately on login, teleporting the player back to Stormwind and crashing
the GPU during the unexpected map transition.
Fixes:
- Set 5s area trigger cooldown + suppress-first in handleLoginVerifyWorld
(same as SMSG_NEW_WORLD handler already did for teleports)
- Add deviceLost_ flag to VkContext so beginFrame returns immediately once
VK_ERROR_DEVICE_LOST is detected, preventing infinite retry loops
- Track device lost from both fence wait and queue submit paths
- Extract initializeRenderers() from loadTestTerrain() so WMO-only maps
(dungeons/raids) initialize renderers directly without a dummy ADT path
- Defer setState(IN_GAME) until after processing any pending deferred world
entry, preventing brief IN_GAME flicker on the wrong map
- Remove verbose area trigger debug logging (every-second position spam)
Reset descriptor pools in CharacterRenderer/M2Renderer/WMORenderer on map
change to prevent VK_ERROR_DEVICE_LOST from pool exhaustion. Defer re-entrant
SMSG_NEW_WORLD during active world load to avoid recursive cleanup crashes.
Gate swim bubbles on swimming state, skip redundant shadow pipeline re-init,
add WOWEE_SKIP_* env vars for render isolation debugging.
- Prefer realm.build over profile worldBuild when non-zero in CMSG_AUTH_SESSION
- Fixes vanilla (1.12.1 build 5875) servers rejecting connection due to wrong build
- Suppress v0.0.0 display in realm list when version info is all zeros
- Fix WDT chunk magic constants to big-endian ASCII (matching ADTLoader)
- Add minimum effective size for box area triggers (90 units, like sphere 45-unit radius)
- Add areaTriggerSuppressFirst_ flag to prevent portal ping-pong on map transfer
- Add WMORenderer::clearAll() to clear models/textures on map change (prevents GPU crash)
- Increase WMO texture cache default to 8GB
- Fix setMapName called after loadTestTerrain so WMO renderer exists
- Save/restore player position around CMSG_AREATRIGGER to prevent bad DB persistence
- 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
Parse SMSG_PARTY_MEMBER_STATS and SMSG_PARTY_MEMBER_STATS_FULL packets
so party frames display health, power, level, and online status even
when group members are not nearby. Expansion-aware field sizes: uint16
health for Classic/TBC, uint32 for WotLK, plus per-expansion aura and
vehicle seat handling.
Load radius was reduced to 3 during taxi (from 4 normal), causing
tiles to not load fast enough at 32 u/s flight speed. Increased to 6
and restored update interval to 0.033s for responsive tile detection.
Implement generic handlers for force speed changes (walk, run back,
swim, swim back, flight, flight back, turn rate, pitch rate),
movement flag toggles (CAN_FLY, HOVER, feather fall, water walk),
and knockback ACKs. Fix SMSG_TIME_SYNC_REQ to respond with
CMSG_TIME_SYNC_RESP instead of silently dropping.
Water strafe used opposite sign convention from land movement.
Land: strafeLeft += right, strafeRight -= right.
Water was: strafeLeft -= right, strafeRight += right.
Now matches land.
playOneShot() spawns an external ffplay process that bypasses the
miniaudio engine master volume. Now checks getMasterVolume() before
spawning the process.
- Force binary loading for SkillLine.dbc (CSV exports are garbled)
- Handle quoted numeric values in DBC CSV parser
- Fix bank slot drag-and-drop to use mouse-release detection instead
of click detection, preventing item drops on wrong slots
- Fix action bar item drop to use hoveredOnRelease for consistency
Water splash and swimming sounds were bypassing mute because
ma_engine_set_volume(0) didn't fully prevent sound creation.
Now playSound2D and playSound3D early-return when masterVolume
is zero.
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.
lookupSpellName() (called by action bar tooltips) was triggering
loadSpellDBC() early, setting dbcLoadAttempted=true before the
spellbook opened. This caused loadSkillLineDBCs() to be skipped,
so all spells were categorized into "General". Fixed by checking
each DBC's own loaded flag independently and re-categorizing when
skill line data becomes available.
- Fix SMSG_ITEM_QUERY_SINGLE_RESPONSE parsing: read statsCount stat
pairs instead of always 10, use 2 damage entries (MAX_ITEM_PROTO_DAMAGES),
and parse item spell data (spellId + spellTrigger per slot)
- Pass item spell ID in CMSG_USE_ITEM packet so server processes item
use requests (spellId=0 caused silent server rejection)
- Add spellId parameter to buildUseItem interface across all expansions
- Fix spellbook mount tab to use SkillLine 777 (Mounts) instead of 762
(Riding), so known mount summon spells appear correctly
- Fix inventory right-click: use IsItemHovered+IsMouseClicked instead
of IsItemClicked for InvisibleButton (which only tracks left-clicks)
- Fix SlotKind enum declaration order in inventory_screen.hpp
- Companion pet spells (SkillLine 778) get their own "Companions" tab
- Mounts (SkillLine 762) and Companions are now split out from secondary
skills instead of being lumped into profession tabs
- Refactored tab grouping into addGroupedTabs helper to reduce duplication
- Tab order: Specs > General > Professions > Mounts > Companions
- Double-click to summon companions/mounts works via existing castSpell
- Class spec abilities now get individual tabs (e.g. Fire, Frost, Arcane)
- Primary professions each get their own tab (Alchemy, Blacksmithing, etc.)
- Secondary professions get tabs (Cooking, First Aid, Fishing)
- Mounts tab for all spells linked to Riding skill line (762)
- General tab for everything else (racials, weapon skills, etc.)
- Tab order: specs first, then General, then professions, then Mounts
Talent screen:
- Remove all debug text and per-frame LOG_INFO spam
- Show class name in window title (e.g. "Warrior Talents")
- Display point distribution in header (0/31/20) and per-tab counts
- Highlighted active spec button with styled spec switcher
- Load and render tree background textures from TalentTab.dbc
- Draw prerequisite arrows with arrowheads (green=met, gray=unmet)
- Fix rank display (was showing rank+1, now correct 1-indexed values)
- Rank counter with dark background pill for readability
- Hover glow effect, rounded corners, centered grid layout
- Wider window (680x600) for 4-column WoW talent grid
Spellbook:
- Add search/filter bar for finding spells by name
- Add spell descriptions from Spell.dbc tooltip field
- Rich tooltips with name, rank, passive indicator, cooldown, description
- Visual icon borders: yellow=passive, red=cooldown, default=active
- Cooldown overlay on icon with countdown number
- Hover highlight on spell rows
- Tab counts update to reflect search filter results
- Rounded corners on icons and hover states
- Extracted renderSpellTooltip helper for consistent tooltip rendering
- Add item class/subclass category filters (Weapon, Armor, etc.) with correct WoW 3.3.5a IDs
- Add sell item picker dropdown with icons and Create Auction button
- Add pagination (Prev/Next) for browse results with filter preservation
- Add Buy/Bid action buttons to Bids tab
- Add item icons and stat tooltips on hover across all three tabs
- Add Enter-to-search from name field and search delay countdown
- Parse SMSG_AUCTION_OWNER/BIDDER_NOTIFICATION into chat messages
- Auto-refresh browse results after bid/buyout using saved search params
- Clamp level range inputs to 0-80
- CMSG_SEND_MAIL now includes item GUIDs (up to 12 per WotLK)
- Right-click items in bags to attach when mail compose is open
- Compose window shows 12-slot attachment grid with item icons
- Click attached items to remove them
- Classic/Vanilla falls back to single item GUID format
Right-clicking equippable items (inventoryType > 0) while the bank is
open now equips them as normal instead of depositing. Only materials,
quest items, and other non-equippable items auto-deposit to bank.
Bank window slots now act as drop targets when holding an item from
inventory. Empty bank slots highlight green, and clicking drops the
held item via CMSG_SWAP_ITEM. Occupied bank slots accept swaps too.
Works for both main bank slots (39-66) and bank bag slots (67+).
When the bank is open, right-clicking a backpack or bag item now
deposits it into the bank via CMSG_AUTOBANK_ITEM instead of trying
to equip/use it. Bank deposit takes priority over vendor sell and
auto-equip actions.
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
Two bugs in loadPatchArchives():
1. isLetterPatch detection was inverted (rfind != 0 is false for all
"patch-*" entries), making the disable flags non-functional
2. Patch file lookup used exact std::filesystem::exists() which is
case-sensitive on Linux — Patch-A.MPQ wouldn't match patch-a.mpq
Now scans the data directory once and builds a case-insensitive lookup
map, so any case variant (Patch-A.MPQ, patch-a.mpq, PATCH-A.MPQ) is
found correctly.
logoutToLogin() was only clearing a handful of flags, leaving stale
entity instance maps, pending spawn queues, transport state, mount
state, and charge state from the previous session. On second login,
these stale GUIDs and instance IDs caused invalid renderer operations
and crashes.
Now clears: creature/player/gameObject instance maps, all pending
spawn queues, transport doodad batches, mount/charge state, player
identity, and renderer world geometry (WMO instances, M2 models,
quest markers). Also disconnects TransportManager from WMORenderer
before teardown to prevent dangling pointer access.
Camera collision with WMO walls and M2 doodads caused erratic zoom
and pull-through at doorway transitions. Terrain-only camera floor
clamping is retained.