Commit graph

753 commits

Author SHA1 Message Date
Kelsi
9c4e61a227 fix(diagnostics): instrument applyMsaaChange to find NULL deref
AMD crash is caused by msaaChangePending_ flipping true from saved settings.
applyMsaaChange() then crashes with faultAddr=(nil). Add LOG_WARNING markers
between pipeline recreation groups to identify the failing call.
2026-04-03 20:42:08 -07:00
Kelsi
45ac7e4d8e fix(diagnostics): log renderer state on each beginFrame for AMD crash
Add per-frame LOG_WARNING with this/vkCtx/camera/postProcess pointers and
msaaChangePending state. If the log prints before crash, the pointer values
tell us what's corrupt. If it doesn't print, crash is in the log itself
(meaning this or vkCtx is corrupt).
2026-04-03 20:37:21 -07:00
Kelsi
cd07e23485 fix(diagnostics): finer beginFrame sub-phase markers for AMD crash
Previous markers showed crash before bf:ubo. Add markers at bf:msaa,
bf:pp, bf:swap, bf:acquire, bf:jitter to isolate which early beginFrame
call does the NULL deref.
2026-04-03 20:32:18 -07:00
Kelsi
5778ba230d fix(diagnostics): add sub-phase markers inside Renderer::beginFrame
AMD RADV crash is renderPhase=beginFrame with faultAddr=(nil) — a NULL
pointer dereference somewhere in the pre-pass chain. Add granular markers
(bf:ubo, bf:minimap, bf:worldmap, bf:preview, bf:shadow, bf:reflection,
bf:renderpass) to pinpoint the exact call.
2026-04-03 20:28:37 -07:00
Kelsi
82267320b0 fix(diagnostics): add render-phase crash markers and improve signal handling
Add signal-safe render-phase markers throughout GameScreen::render() and
Application::render() so the crash handler can report which render call was
active when a SIGSEGV occurs. The AMD RADV crash backtrace only shows 2
frames due to missing frame pointers, making it impossible to identify the
actual crash site.

Changes:
- Add volatile g_crashRenderPhase marker updated before each major render call
- Upgrade Linux signal handler to sigaction with SA_SIGINFO for faulting address
- Set ImGui CheckVkResultFn to log silent Vulkan errors in ImGui backend
- Enable -fno-omit-frame-pointer in all build configs (not just Debug/RelWithDebInfo)
2026-04-03 20:19:33 -07:00
Kelsi
b092bc2e90 fix(rendering): use deferAfterAllFrameFences for bone destruction
destroyInstanceBones loops over both frame slots (i=0,1), deferring
both boneSet[0] and boneSet[1] to the current frame's fence. When
currentFrame=0, boneSet[1] is freed after only slot 0's fence completes
while slot 1's command buffer may still be using it.

Switch both M2Renderer and CharacterRenderer bone destruction from
deferAfterFrameFence to deferAfterAllFrameFences to ensure all
in-flight frames have completed before freeing cross-slot resources.
2026-04-03 19:54:54 -07:00
Kelsi
3ac8c4d95f fix(rendering): wait all frame fences before freeing shared descriptor sets
deferAfterFrameFence only waits for one frame slot's fence, but shared
resources (material descriptor sets, vertex/index buffers) are bound by
both in-flight frames' command buffers. On AMD RADV this caused
vkFreeDescriptorSets errors and eventual SIGSEGV.

Add deferAfterAllFrameFences: queues to every frame slot with a shared
counter so cleanup runs exactly once, after the last slot is fenced.
Use it for WMO, terrain, water, and character model shared resources.
Per-frame bone sets keep using deferAfterFrameFence (already correct).

Also fix character renderer vertex format: R8G8B8A8_UINT -> _SINT to
match shader's ivec4 input (RADV validation rejects the mismatch).
2026-04-03 19:48:43 -07:00
Kelsi
40e72d535e fix(rendering): defer model buffer destruction and per-frame FXAA descriptors
CharacterRenderer::destroyModelGPU now defers vertex/index buffer
destruction when replacing models mid-stream, preventing use-after-free
on AMD RADV. FXAA descriptor sets are now per-frame to eliminate
write-read races between in-flight command buffers. Water reflection
descriptor update narrowed to current frame only.
2026-04-03 19:17:55 -07:00
Kelsi
e19bf76d88 fix(rendering): defer character renderer bone descriptor destruction
CharacterRenderer::destroyInstanceBones had the same immediate-free bug
as M2Renderer — freeing bone descriptor sets and buffers while in-flight
command buffers still reference them. Applies the same deferred pattern
via deferAfterFrameFence for the removeInstance streaming path.
2026-04-03 18:53:06 -07:00
Kelsi
ac5c61203d fix(rendering): defer descriptor set destruction during streaming unload
M2 destroyInstanceBones and WMO destroyGroupGPU freed descriptor sets
and buffers immediately during tile streaming, while in-flight command
buffers still referenced them — causing DEVICE_LOST on AMD RADV.

Now defers GPU resource destruction via deferAfterFrameFence in streaming
paths (removeInstance, removeInstances, unloadModel). Immediate
destruction preserved for shutdown/clear paths that vkDeviceWaitIdle
first.

Also: vkDeviceWaitIdle before WMO backfillNormalMaps descriptor rebinds,
and fillModeNonSolid added to required device features for wireframe
pipelines on AMD.
2026-04-03 18:30:52 -07:00
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