Commit graph

793 commits

Author SHA1 Message Date
Kelsi
8e1addf7a6 fix(rendering): increase ImGui descriptor pool from 100 to 2048
The pool was exhausted by cached spell/item/talent icon textures,
causing vkAllocateDescriptorSets to fail inside ImGui_ImplVulkan_AddTexture.
The NVIDIA driver crashed on the subsequent invalid descriptor write.

Also add a null-check on the returned descriptor set so pool exhaustion
gracefully returns VK_NULL_HANDLE instead of crashing.
2026-04-03 18:14:46 -07:00
Kelsi
2096e67bf9 fix(rendering): prevent shutdown crash from deferred cleanup use-after-free
During shutdown, VkContext::runDeferredCleanup() was executing lambdas
that called vkFreeDescriptorSets on descriptor pools already destroyed
by Renderer::shutdown(). This corrupted the validation layer's internal
state, causing a SIGSEGV during process exit on AMD RADV.

Clear the deferred queues without executing them — vkDestroyDevice
reclaims all device-child resources anyway. Also guard against the
double shutdown() call (explicit + destructor).
2026-04-03 18:02:24 -07:00
Kelsi
b2cb98e969 fix(rendering): per-image semaphores and depth-format shadow placeholder
Avoid semaphore reuse while the presentation engine still holds a
reference by switching from per-frame-slot to per-swapchain-image
semaphores with a rotating free semaphore for acquire.

Replace the R8G8B8A8_UNORM dummy white texture in CharacterPreview
with a proper D16_UNORM depth texture cleared to 1.0, matching the
sampler2DShadow expectation in shaders. AMD RADV enforces strict
format/sampler type compatibility.
2026-04-03 17:52:48 -07:00
Kelsi
4f7912cf45 fix(rendering): water reflection render pass compat, anisotropy feature, shadow pool race
Three bugs found via AMD RADV crash log:

1. Water reflection render pass used BOTTOM_OF_PIPE as srcStageMask but
   pipelines were created against the main pass (EARLY_FRAGMENT_TESTS |
   COLOR_ATTACHMENT_OUTPUT). AMD enforces strict render pass compatibility
   → SIGSEGV when scene renders into reflection texture.

2. samplerAnisotropy was never enabled during device creation despite being
   used in sampler creation — now requested via PhysicalDeviceSelector.

3. Shadow texture descriptor pool was reset each frame while prior frame's
   command buffers might still reference it. Split into per-frame-slot pools
   so each reset is fence-guarded.
2026-04-03 17:41:14 -07:00
Kelsi
62b8a757a3 fix(rendering): skip TRANSIENT_ATTACHMENT for MSAA on GPUs without lazily allocated memory
AMD RDNA4 (9070XT) crashes with SIGSEGV when MSAA is enabled because the
driver optimizes TRANSIENT images for tile-only storage. Without lazily
allocated memory backing, the MSAA resolve reads unbacked memory. Now we
only set TRANSIENT+LAZILY_ALLOCATED when the device actually exposes that
memory type.
2026-04-03 17:23:52 -07:00
Kelsi
81a9970c91 fix(rendering): add warnings for silent texture fallbacks
M2 particle/ribbon/batch, terrain layer, and WMO material texture
resolution paths were silently falling back to white textures when
indices were out of range — making missing texture issues hard to
diagnose. Add LOG_WARNING at each silent failure point with model
name, index details, and array sizes.
2026-04-03 16:11:45 -07:00
Paul
2cb47bf126 chore(testing): add unit tests and update core render/network pipelines
- add new tests:
  - test_blp_loader.cpp
  - test_dbc_loader.cpp
  - test_entity.cpp
  - test_frustum.cpp
  - test_m2_structs.cpp
  - test_opcode_table.cpp
  - test_packet.cpp
  - test_srp.cpp
  - CMakeLists.txt
- add docs and progress tracking:
  - TESTING.md
  - perf_baseline.md
- update project config/build:
  - .gitignore
  - CMakeLists.txt
  - test.sh
- core engine updates:
  - application.cpp
  - game_handler.cpp
  - world_socket.cpp
  - adt_loader.cpp
  - asset_manager.cpp
  - m2_renderer.cpp
  - post_process_pipeline.cpp
  - renderer.cpp
  - terrain_manager.cpp
  - game_screen.cpp
- add profiler header:
  - profiler.hpp
2026-04-03 09:41:34 +03:00
Paul
5af9f7aa4b chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.

AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
  emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()

Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
  directly, grouped by access pattern:
  - UIServices: settings_panel, game_screen, toast_manager, chat_panel,
    combat_ui, window_manager
  - GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
    social_handler, combat_handler
  - Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
  MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
Paul
5ef600098a chore(renderer): refactor renderer and add post-process + spell visuals systems
- Updated core render pipeline and renderer integration in CMakeLists.txt, renderer.cpp, renderer.hpp
- Added post-process pipeline module:
  - post_process_pipeline.hpp
  - post_process_pipeline.cpp
- Added spell visual system module:
  - spell_visual_system.hpp
  - spell_visual_system.cpp
- Adjusted application/audio integration:
  - application.cpp
  - audio_coordinator.cpp
2026-04-02 00:21:21 +03:00
Kelsi
7cfaf2c7e9 refactor: complete OpenGL→Vulkan migration (Phase 7)
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Remove all OpenGL/GLEW code and dependencies. The Vulkan renderer has
been the sole active backend for months; these files were dead code.

Deleted (8 files, 641 lines):
- rendering/mesh.cpp+hpp: OpenGL VAO/VBO/EBO wrapper (never instantiated)
- rendering/shader.cpp+hpp: OpenGL GLSL compiler (replaced by VkShaderModule)
- rendering/scene.cpp+hpp: Scene graph holding Mesh objects (created but
  never populated — all rendering uses Vulkan sub-renderers directly)
- rendering/video_player.cpp+hpp: FFmpeg+GL texture uploader (never
  included by any other file — login video feature can be re-implemented
  with VkTexture when needed)

Cleaned up:
- renderer.hpp: remove Scene forward-decl, getScene() accessor, scene member
- renderer.cpp: remove scene.hpp/shader.hpp includes, Scene create/destroy
- application.cpp: remove stale "GL/glew.h removed" comment
- CMakeLists.txt: remove find_package(OpenGL/GLEW), source/header entries,
  and target_link_libraries for OpenGL::GL and GLEW::GLEW
- PKGBUILD: remove glew dependency
- BUILD_INSTRUCTIONS.md: remove glew from all platform install commands
2026-03-30 19:22:36 -07:00
Kelsi
1ab254273e docs: add M2 format why-comments to character preview
- Explain M2 version 264 threshold (WotLK stores submesh/bone data
  in external .skin files; Classic/TBC embed it in the M2)
- Explain M2 texture types 1 and 6 (skin and hair/scalp; empty
  filenames resolved via CharSections.dbc at runtime)
- Explain 0x20 anim flag (embedded data; when clear, keyframes live
  in external {Model}{SeqID}-{Var}.anim files)
- Explain geoset ID encoding (group × 100 + variant from
  ItemDisplayInfo.dbc; e.g. 801 = sleeves variant 1)
2026-03-30 17:28:47 -07:00
Kelsi
92369c1cec docs: add why-comments to rendering, packets, and UI code
- charge_effect: explain inversesqrt guard (prevents NaN on stationary
  character) and dust accumulator rate (30 particles/sec * 16ms)
- swim_effects: explain why insect pipeline disables depth test
  (screen-space sprites must render above water geometry)
- packet_parsers_classic: explain spline waypoint cap (DoS prevention)
  and packed GUID compression format (non-zero bytes only, mask byte)
- talent_screen: explain class ID to bitmask conversion (1-indexed
  WoW class IDs → power-of-2 mask for TalentTab.classMask matching)
- auth_screen: explain login music volume reduction (80% so UI sounds
  remain audible over background track)
2026-03-30 17:23:07 -07:00
Kelsi
4215950dcd refactor: extract class/race restriction helpers, add DBC fallback comment
- inventory_screen: extract renderClassRestriction() and
  renderRaceRestriction() from two identical 40-line blocks in quest
  info and item info tooltips. Both used identical bitmask logic,
  strncat formatting, and player-class/race validation (-49 lines net)
- world_map: add why-comment on AreaTable.dbc fallback field indices —
  explains that incorrect indices silently return wrong data and why
  the WotLK stock layout (ID=0, Parent=2, ExploreFlag=3) is chosen
  as the safest default
2026-03-30 15:45:48 -07:00
Kelsi
ff72d23db9 refactor: name lighting time constant, replace PI literal with glm
- Name kHalfMinutesPerDay (2880) replacing 8 bare literals across
  time conversion, modulo clamping, and midnight wrap arithmetic.
  Add why-comment: Light.dbc stores time-of-day as half-minutes
  (24h × 60m × 2 = 2880 ticks per day cycle)
- Replace hardcoded 3.14159f with glm::two_pi<float>() in sun
  direction angle calculations (2 occurrences)
2026-03-30 15:23:58 -07:00
Kelsi
55cac39541 refactor: name random/camera constants, add alpha map static_assert
- terrain_manager: extract kRand16Max (65535.0f) from 8 duplicated
  random normalization expressions — 16-bit mask to [0..1] float
- terrain_manager: add static_assert verifying packed alpha unpacks
  to full alpha map size (ALPHA_MAP_PACKED * 2 == ALPHA_MAP_SIZE)
- camera_controller: name kCameraClipEpsilon (0.1f) with why-comment
  preventing character model clipping at near-minimum distance
2026-03-30 15:17:37 -07:00
Kelsi
7b4fdaa277 refactor: name memory/taxi constants, add camera jitter why-comment
- memory_monitor: extract kOneGB and kFallbackRAM constants from 6
  duplicated 1024*1024*1024 expressions; name kFieldPrefixLen for
  /proc/meminfo "MemAvailable:" offset (was bare 13)
- camera: add why-comment on projection matrix jitter — column 2 holds
  NDC x/y offset for TAA/FSR2 sub-pixel sampling
- movement_handler: name kMaxTaxiNodeId (384) with why-comment —
  WotLK TaxiNodes.dbc has 384 entries, bitmask is 12 × uint32
2026-03-30 15:07:55 -07:00
Kelsi
548828f2ee refactor: extract color write mask, name frustum epsilon, add comments
- vk_pipeline: extract kColorWriteAll constant from 4 duplicated RGBA
  bitmask expressions across blend mode functions, with why-comment
- frustum: name kMinNormalLenSq epsilon (1e-8) with why-comment —
  prevents division by zero on degenerate planes
- dbc_loader: add why-comment on DBC field width validation — all
  fields are fixed 4-byte uint32 per format spec
- pin_auth: replace 0x30 hex literal with '0' char constant, add
  why-comment on ASCII encoding for server HMAC compatibility
2026-03-30 15:02:47 -07:00
Kelsi
8c7db3e6c8 refactor: name FNV-1a/transport constants, fix dead code, add comments
- vk_context: name FNV-1a hash constants (kFnv1aOffsetBasis/kFnv1aPrime)
  with why-comment on algorithm choice for sampler cache
- transport_manager: collapse redundant if/else that both set
  looping=false into single unconditional assignment, add why-comment
  explaining the time-closed path design
- transport_manager: hoist duplicate kMinFallbackZOffset constants out
  of separate if-blocks, add why-comment on icebreaker Z clamping
- entity: expand velocity smoothing comment — explain 65/35 EMA ratio
  and its tradeoff (jitter suppression vs direction change lag)
2026-03-30 14:48:06 -07:00
Kelsi
74f0ba010a fix: remove duplicate zone weather, consolidate RNG, name star constants
- weather: remove duplicate setZoneWeather(15) for Dustwallow Marsh —
  second call silently overwrote the first with different parameters
- weather: replace duplicate static RNG in getRandomPosition() with
  shared weatherRng() to avoid redundant generator state
- starfield: extract day/night cycle thresholds into named constants
  (kDuskStart/kNightStart/kDawnStart/kDawnEnd/kFadeDuration)
- skybox: replace while-loop time wrapping with std::fmod — avoids
  O(n) iterations on large time jumps
2026-03-30 14:38:30 -07:00
Kelsi
683e171fd1 fix: VkTexture move/destroy ownsSampler_ flag, extract finalizeSampler
- Fix move constructor and move assignment: set other.ownsSampler_ to
  false after transfer (was incorrectly set to true, leaving moved-from
  object claiming ownership of a null sampler)
- Fix destroy(): reset ownsSampler_ to false after clearing sampler
  handle (was set to true, inconsistent with null handle state)
- Extract finalizeSampler() from 3 duplicated cache-or-create blocks
  in createSampler() overloads and createShadowSampler() (-24 lines)
- Add SPIR-V alignment why-comment in vk_shader.cpp
2026-03-30 14:24:41 -07:00
Kelsi
4574d203b5 refactor: name M2 renderer magic constants, add why-comments
- Name portal spin wrap value as kTwoPi constant
- Name particle animTime wrap as kParticleWrapMs (3333ms) with
  why-comment: covers longest known emission cycle (~3s torch/campfire)
  while preventing float precision loss over hours of runtime
- Add FBlock interpolation documentation: explain what FBlocks are
  (particle lifetime curves) and note that float/vec3 variants share
  identical logic and must be updated together
2026-03-30 14:14:27 -07:00
Kelsi
6dfac314ee fix: remove dead code, name constants, add why-comments
- renderer: remove no-op assignment (mountAnims_.stand = 0 when already 0)
- renderer: add why-comments on blacksmith WMO ID 96048 (ambient forge
  sounds) with TODO for other smithy buildings
- terrain_renderer: replace 1e30f sentinel with numeric_limits::max(),
  name terrain view distance constant (1200 units ≈ 9 ADT tiles)
- social_handler: add missing LFG case 15, document case 0 nullptr
  return (success = no error message), add enum name comments
2026-03-30 14:10:32 -07:00
Kelsi
4acebff65c refactor: extract fallback textures, add why-comments, name WMO constant
- character_renderer: extract duplicated fallback texture creation
  (white/transparent/flat-normal) into createFallbackTextures() — was
  copy-pasted between initialize() and clear()
- wmo_renderer: replace magic 8192 with kMaxRetryTracked constant,
  add why-comment explaining the fallback-retry set cap (Dalaran has
  2000+ unique WMO groups)
- quest_handler: add why-comment on reqCount=0 fallback — escort/event
  quests can report kill credit without objective counts in query response
2026-03-30 14:06:30 -07:00
Kelsi
76f493f7d9 refactor: replace goto with structured control flow
- spell_handler.cpp: replace goto-done with do/while(false) for pet
  spell packet parsing — bail on truncated data while always firing
  events afterward
- water_renderer.cpp: replace goto-found_neighbor with immediately
  invoked lambda to break out of nested neighbor search loops
2026-03-30 13:47:14 -07:00
Kelsi
161b7981f9 fix: video player decode loop could spin indefinitely on corrupt files
The while(true) loop retried av_read_frame after seeking to the start
on error. A corrupt file where read fails but seek succeeds would loop
forever, blocking the main thread. Bounded to 500 attempts with a
warning log on exhaustion.
2026-03-29 21:46:10 -07:00
Kelsi
c8dde0985f fix: detached normal-map thread could deadlock shutdown on exception
If generateNormalHeightMapCPU threw (e.g., bad_alloc), the pending
counter was never decremented, causing shutdown() to block forever
waiting for a count that would never reach zero. Added try-catch to
guarantee the decrement. Also strengthened the increment from relaxed
to acq_rel so shutdown()'s acquire load sees the count before the
thread body begins executing.
2026-03-29 21:36:06 -07:00
Kelsi
3dd1128ecf fix: unguarded future::get() crashed on render/floor-query worker exceptions
std::future::get() re-throws any exception from the async task. The 6
call sites in the render pipeline (terrain/WMO/M2 workers + animation
worker) and 2 floor-query sites in camera_controller were unguarded,
so a single bad_alloc in any worker would terminate the process with
no recovery. Now wrapped in try-catch with error logging.
2026-03-29 21:26:01 -07:00
Kelsi
a55eacfe70 fix: weather particles and cycle durations deterministic due to unseeded rand()
8 rand() calls in weather.cpp used C rand() which defaults to seed 1.
Weather intensity rolls, cycle durations, and particle Y positions were
identical on every launch. Replaced with a file-local mt19937 seeded
from random_device, matching the RNG already present in getRandomPosition.
2026-03-29 20:53:38 -07:00
Kelsi
849542d01d fix: doodad/mount animations synchronized due to unseeded rand()
All 8 rand() calls for animation time offsets and variation timers in
m2_renderer.cpp used C rand() which defaults to seed 1 without srand(),
producing identical sequences every launch. Trees, torches, and grass
all swayed in sync. Replaced with std::mt19937 seeded from
random_device. Same fix for 4 mount idle fidget/sound timer sites in
renderer.cpp which mixed rand() with the mt19937 already present.
2026-03-29 20:42:10 -07:00
Kelsi
f02be1ffac fix: tolower/toupper UB on signed char at 10 remaining call sites
Final sweep across mpq_manager, application, auth_screen, wmo_renderer,
character_renderer, and terrain_manager. All now use the unsigned char
cast pattern. No remaining bare ::tolower/::toupper or std::tolower(c)
calls on signed char in the codebase.
2026-03-29 20:27:16 -07:00
Kelsi
34e384e1b2 fix: tavern music always played first track — index never incremented
tavernTrackIndex was initialized to 0 but never modified, so the player
always heard TavernAlliance01.mp3. Added post-increment to rotate
through the 3 available tracks on each tavern entry.
2026-03-29 20:27:08 -07:00
Kelsi
ef25785404 fix: terrain chunk UBO allocation failure crashed GPU via null descriptor
vmaCreateBuffer return value was silently discarded in both loadTerrain
and loadTerrainIncremental. If allocation failed (OOM/fragmentation),
the chunk proceeded with a VK_NULL_HANDLE UBO, causing the GPU to read
from an invalid descriptor on the next draw call. Now checks the return
value and skips the chunk on failure.
2026-03-29 20:14:35 -07:00
Kelsi
91c6eef967 fix: suspend gravity for 10s after world entry to prevent WMO fall-through
Stormwind WMO collision takes 25+ seconds to fully load. The warmup
ground check couldn't detect the WMO floor because collision data
wasn't finalized yet. Player spawned and immediately fell through
the unloaded WMO floor into the terrain below (Dun Morogh).

New approach: suspendGravityFor(10s) after world entry. Gravity is
disabled (Z position frozen) until either:
1. A floor is detected by the collision system (gravity resumes instantly)
2. The 10-second timer expires (gravity resumes as fallback)

This handles the case where WMO collision loads during the first few
seconds of gameplay — the player hovers at spawn Z until the floor
appears, then lands normally.

Also fixes faction language for chat (ORCISH for Horde, COMMON for
Alliance) and adds SMSG_MESSAGECHAT diagnostic logging.
2026-03-28 15:50:13 -07:00
Kelsi
416e091498 feat: add Camera Stiffness and Pivot Height settings for motion comfort
Camera Stiffness (default 20, range 5-100): controls how tightly the
camera follows the player. Higher values = less sway/lag. Users who
experience motion sickness can increase this to reduce floaty camera.

Camera Pivot Height (default 1.8, range 0-3): height of the camera
orbit point above the player's feet. Lower values reduce the
"detached/floating" feel that can cause nausea. Setting to 0 puts the
pivot at foot level (ground-locked camera).

Both settings saved to settings file and applied via sliders in the
Gameplay tab of the Settings window.
2026-03-28 11:39:37 -07:00
Paul
888a78d775 fixin critical bugs, non critical bugs, sendmail implementation 2026-03-28 11:35:10 +03:00
Kelsi
2af3594ce8 perf: eliminate per-frame heap allocs in M2 renderer; UI polish and report
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
M2 renderer: move 3 per-frame local containers to member variables:
- particleGroups_ (unordered_map): reuse bucket structure across frames
- ribbonDraws_ (vector): reuse draw call buffer
- shadowTexSetCache_ (unordered_map): reuse descriptor cache
Eliminates ~3 heap allocations per frame in particle/ribbon/shadow passes.

UI polish:
- Nameplate hover tooltip showing level, class (players), guild name
- Bag window titles show slot counts: "Backpack (12/16)"

Player report: CMSG_COMPLAIN packet builder and reportPlayer() method.
"Report Player" option in target frame right-click menu for other players.
Server response handler (SMSG_COMPLAIN_RESULT) was already implemented.
2026-03-27 18:21:47 -07:00
Kelsi
dce11a0d3f perf: skip bone animation for LOD3 models, frustum-cull water surfaces
M2 renderer: skip bone matrix computation for instances beyond 150 units
(LOD 3 threshold). These models use minimal static geometry with no visible
skeletal animation. Last-computed bone matrices are retained for GPU upload.
Removes unnecessary float matrix operations for hundreds of distant NPCs
in crowded zones.

Water renderer: add per-surface AABB frustum culling before draw calls.
Computes tight AABB from surface corners and height range, tests against
camera frustum. Skips descriptor binding and vkCmdDrawIndexed for surfaces
outside the view. Handles both ADT and WMO water (rotated step vectors).
2026-03-27 18:11:20 -07:00
Kelsi
cccd52b32f fix: equipment visibility (remove layout verification gate), follow uses run speed
Equipment: removed the visibleItemLayoutVerified_ gate from
updateOtherPlayerVisibleItems(). The default WotLK field layout (base=284,
stride=2) is correct and should be used immediately. The verification
heuristic was silently blocking ALL other-player equipment rendering by
queuing for auto-inspect (which doesn't return items in WotLK anyway).

Follow: auto-follow now uses run speed (autoRunning) instead of walk speed.
Also uses squared distance for the distance checks.

Commands: /quit, /exit aliases for /logout; /difficulty normal/heroic/25/25heroic
sends CMSG_CHANGEPLAYER_DIFFICULTY.
2026-03-27 18:05:42 -07:00
Kelsi
b366773f29 fix: inspect (packed GUID), follow (client-side auto-walk); add loot/raid commands
Inspect: CMSG_INSPECT was writing full uint64 GUID instead of packed GUID.
Server silently rejected the malformed packet. Fixed both InspectPacket and
QueryInspectAchievementsPacket to use writePackedGuid().

Follow: was a no-op (only stored GUID). Added client-side auto-follow system:
camera controller walks toward followed entity, faces target, cancels on
WASD/mouse input, stops within 3 units, cancels at 40+ units distance.

Party commands:
- /lootmethod (ffa/roundrobin/master/group/nbg) sends CMSG_LOOT_METHOD
- /lootthreshold (0-5 or quality name) sets minimum loot quality
- /raidconvert converts party to raid (leader only)

Equipment diagnostic logging still active for debugging naked players.
2026-03-27 17:54:56 -07:00
Kelsi
0396a42beb feat: render equipment on other players (helmets, weapons, belts, wrists)
Other players previously appeared partially naked — only chest, legs, feet,
hands, cape, and tabard rendered. Now renders full equipment:

- Helmet M2 model: loads from ItemDisplayInfo.dbc with race/gender suffix,
  attaches at head bone (point 0/11), hides hair geoset under helm
- Weapons: mainhand (attachment 1) and offhand (attachment 2) M2 models
  loaded from ItemDisplayInfo, with Weapon/Shield path fallback
- Wrist/bracer geoset (group 8): applies when no chest sleeve overrides
- Belt/waist geoset (group 18): reads GeosetGroup1 from ItemDisplayInfo
- Shoulder M2 attachments deferred (separate bone attachment system)

Also applied same wrist/waist geosets to NPC and character preview paths.

Minimap: batch 9 individual vkUpdateDescriptorSets into single call.
2026-03-27 17:30:35 -07:00
Kelsi
50a3eb7f07 fix: mail money uint64, other-player cape textures, zone toast dedup, TCP_NODELAY
Mail: change money/COD fields from uint32 to uint64 in CMSG_SEND_MAIL and
SMSG_MAIL_LIST_RESULT for WotLK 3.3.5a. Classic keeps uint32 on the wire.
Fixes money truncation and packet misalignment causing mail failures.

Other-player capes: add cape texture loading to setOnlinePlayerEquipment().
The cape geoset was enabled but no texture was loaded, leaving capes blank.
Now mirrors the local-player path: looks up ItemDisplayInfo.dbc, finds cape
texture candidates, applies via setGroupTextureOverride/setTextureSlotOverride.

Zone toasts: suppress duplicate zone toast when the zone text overlay is
already showing the same zone name. Fixes double "Entering: Stormwind City".

Network: enable TCP_NODELAY on both auth and world sockets after connect(),
disabling Nagle's algorithm to eliminate up to 200ms buffering delay on
small packets (movement, spell casts, chat).

Rendering: track material and bone descriptor sets in M2 renderer to skip
redundant vkCmdBindDescriptorSets calls between batches sharing same textures.
2026-03-27 17:20:31 -07:00
Kelsi
6b1c728377 perf: eliminate double map lookups, dynamic_cast in render loops, div by 255
- Replace count()+operator[] double lookups with find() or try_emplace()
  in gameObjectInstances_, playerTextureSlotsByModelId_, onlinePlayerAppearance_
- Add Entity::isUnit() helper; replace 5 dynamic_cast<Unit*> in per-frame
  UI rendering (nameplates, combat text, pet frame) with isUnit()+static_cast
- Add constexpr kInv255 reciprocal for per-pixel normal map generation loops
  in character_renderer and wmo_renderer
2026-03-27 17:04:13 -07:00
Kelsi
6f2c8962e5 fix: use expansion context for spline parsing; preload DBC caches at world entry
Spline parsing: remove Classic format fallback from the WotLK parser. The
PacketParsers hierarchy already dispatches to expansion-specific parsers
(Classic/TBC/WotLK/Turtle), so the WotLK parseMovementBlock should only
attempt WotLK spline format. The Classic fallback could false-positive when
durationMod bytes resembled a valid point count, corrupting downstream parsing.

Preload DBC caches: call loadSpellNameCache() and 5 other lazy DBC caches
during handleLoginVerifyWorld() on initial world entry. This moves the ~170ms
Spell.csv load from the first SMSG_SPELL_GO handler to the loading screen,
eliminating the mid-gameplay stall.

WMO portal culling: move per-instance portalVisibleGroups vector and
portalVisibleGroupSet to reusable member variables, eliminating heap
allocations per WMO instance per frame.
2026-03-27 16:58:39 -07:00
Kelsi
a795239e77 fix: spline parse order (WotLK-first) fixes missing NPCs; bound WMO liquid loading
Spline auto-detection: try WotLK format before Classic to prevent false-positive
matches where durationMod float bytes resemble a valid Classic pointCount. This
caused the movement block to consume wrong byte count, corrupting the update mask
read (maskBlockCount=57/129/203 instead of ~5) and silently dropping NPC spawns.

Terrain latency: bound WMO liquid group loading to 4 groups per advanceFinalization
call. Large WMOs (e.g., Stormwind canals with 40+ liquid groups) previously loaded
all groups in one unbounded loop, blowing past the 8ms frame budget and causing
stalls up to 1300ms. Now yields back to processReadyTiles() after 4 groups so the
time budget check can break out.
2026-03-27 16:51:13 -07:00
Kelsi
d26eed1e7c perf: constexpr reciprocals, cache redundant lookups, consolidate texture maps
- Hoist DBC field index lookups before loops in game_handler (7 DBC iteration loops)
- Cache getSkybox()/getPosition() calls instead of redundant per-frame queries
- Merge textureHasAlphaByPtr_ + textureColorKeyBlackByPtr_ into single map
- Add constexpr for DEG_TO_RAD, reciprocal constants, physics delta
- Add reserve() for WMO/M2 collision grid queries and portal BFS
- Frustum plane normalize: inversesqrt instead of length+divide
- M2 particle emission: inversesqrt for direction normalization
- Parse creature display IDs from query response
- UI: show spell names/IDs as fallback instead of "Unknown"
2026-03-27 16:47:30 -07:00
Kelsi
b0466e9029 perf: eliminate ~70 unnecessary sqrt ops per frame, optimize caches and threading
Squared distance optimizations across 30 files:
- Convert glm::length() comparisons to glm::dot() (no sqrt)
- Use glm::inversesqrt() for check-then-normalize patterns (1 rsqrt vs 2 sqrt)
- Defer sqrt to after early-out checks in collision/movement code
- Hottest paths: camera_controller (21), weather particles, WMO collision,
  transport movement, creature interpolation, nameplate culling

Container and algorithm improvements:
- std::map<string> → std::unordered_map for asset/DBC/MPQ/warden caches
- std::mutex → std::shared_mutex for asset_manager and mpq_manager caches
- std::sort → std::partial_sort in lighting_manager (top-2 of N volumes)
- Double-lookup find()+operator[] → insert_or_assign in game_handler
- Add reserve() for per-frame vectors: weather, swim_effects, WMO/M2 collision

Threading and synchronization:
- Replace 1ms busy-wait polling with condition_variable in character_renderer
- Move timestamp capture before mutex in logger
- Use memory_order_acquire/release for normal map completion signaling

API additions:
- DBC getStringView()/getStringViewByOffset() for zero-copy string access
- Parse creature display IDs from SMSG_CREATURE_QUERY_SINGLE_RESPONSE
2026-03-27 16:33:16 -07:00
Kelsi
cf0e2aa240 refactor: deduplicate pomSampleTable in wmo_renderer, last static const array
Move duplicate pomSampleTable (2 copies → 1 constexpr) to file-scope
anonymous namespace. All static const primitive arrays outside src/game/
are now constexpr.
2026-03-27 15:34:48 -07:00
Kelsi
c762688202 refactor: promote static const arrays to constexpr across audio/core/rendering
audio: birdPaths, cricketPaths, races
core/application: componentDirs (4 instances), compDirs
rendering/character_preview: componentDirs
rendering/character_renderer: regionCoords256, regionSizes256
2026-03-27 15:31:21 -07:00
Kelsi
e805eae33c refactor: add [[nodiscard]] to shader/asset load functions, suppress warnings
Add [[nodiscard]] to VkShaderModule::loadFromFile, Shader::loadFromFile/
loadFromSource, AssetManifest::load, DbcLoader::load — all return bool
indicating success/failure that callers should check.

Suppress with (void) at 17 call sites where validity is checked via
isValid() after loading rather than the return value (m2_renderer
recreatePipelines, swim_effects recreatePipelines).
2026-03-27 15:17:19 -07:00
Kelsi
53a4377ed7 refactor: extract magic numbers in terrain alpha map and texture compositing
terrain_manager: replace bare 4096/2048/0x80/0x7F with named constants
ALPHA_MAP_SIZE, ALPHA_MAP_PACKED, ALPHA_FILL_FLAG, ALPHA_COUNT_MASK
— documents the WoW alpha map RLE format.

character_renderer: replace bare 256/512 texture sizes with
kBaseTexSize/kUpscaleTexSize for NPC skin upscaling logic.
2026-03-27 14:57:20 -07:00