From 100394a743007712db5ba2aa35e99294bb540966 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 3 Apr 2026 23:02:04 -0700 Subject: [PATCH] fix(rendering,game): init bone SSBO to identity; stop movement before cast Bone SSBO buffers were allocated for MAX_BONES (240) entries but only the first numBones were written. Uninitialized GPU memory in the remaining slots caused vertex spikes when any bone index exceeded the model's actual bone count. Also send MSG_MOVE_STOP before spell casts so the server doesn't reject cast-time spells (e.g. hearthstone) with "can't do that while moving". --- src/game/spell_handler.cpp | 8 ++++++++ src/rendering/character_renderer.cpp | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/game/spell_handler.cpp b/src/game/spell_handler.cpp index 59e0d29f..47346c39 100644 --- a/src/game/spell_handler.cpp +++ b/src/game/spell_handler.cpp @@ -222,6 +222,14 @@ void SpellHandler::castSpell(uint32_t spellId, uint64_t targetGuid) { return; } + // Stop movement before casting — servers reject cast-time spells while moving + const uint32_t moveFlags = owner_.movementInfo.flags; + const bool isMoving = (moveFlags & 0x0Fu) != 0; // FORWARD|BACKWARD|STRAFE_LEFT|STRAFE_RIGHT + if (isMoving) { + owner_.movementInfo.flags &= ~0x0Fu; + owner_.sendMovement(Opcode::MSG_MOVE_STOP); + } + uint64_t target = targetGuid != 0 ? targetGuid : owner_.targetGuid; // Self-targeted spells like hearthstone should not send a target if (spellId == 8690) target = 0; diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 79412156..2b347ac7 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -2024,6 +2024,13 @@ void CharacterRenderer::prepareRender(uint32_t frameIndex) { &instance.boneBuffer[frameIndex], &instance.boneAlloc[frameIndex], &allocInfo); instance.boneMapped[frameIndex] = allocInfo.pMappedData; + // Initialize all bone slots to identity so out-of-range indices + // produce correct (neutral) transforms instead of GPU garbage + if (instance.boneMapped[frameIndex]) { + auto* dst = static_cast(instance.boneMapped[frameIndex]); + for (int j = 0; j < MAX_BONES; j++) dst[j] = glm::mat4(1.0f); + } + VkDescriptorSetAllocateInfo ai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; ai.descriptorPool = boneDescPool_; ai.descriptorSetCount = 1; @@ -2147,6 +2154,13 @@ void CharacterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, &instance.boneBuffer[frameIndex], &instance.boneAlloc[frameIndex], &allocInfo); instance.boneMapped[frameIndex] = allocInfo.pMappedData; + // Initialize all bone slots to identity so out-of-range indices + // produce correct (neutral) transforms instead of GPU garbage + if (instance.boneMapped[frameIndex]) { + auto* dst = static_cast(instance.boneMapped[frameIndex]); + for (int j = 0; j < MAX_BONES; j++) dst[j] = glm::mat4(1.0f); + } + // Allocate descriptor set for bone SSBO VkDescriptorSetAllocateInfo ai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; ai.descriptorPool = boneDescPool_; @@ -2787,6 +2801,13 @@ void CharacterRenderer::renderShadow(VkCommandBuffer cmd, const glm::mat4& light &inst.boneBuffer[frameIndex], &inst.boneAlloc[frameIndex], &ai); inst.boneMapped[frameIndex] = ai.pMappedData; + // Initialize all bone slots to identity so out-of-range indices + // produce correct (neutral) transforms instead of GPU garbage + if (inst.boneMapped[frameIndex]) { + auto* dst = static_cast(inst.boneMapped[frameIndex]); + for (int j = 0; j < MAX_BONES; j++) dst[j] = glm::mat4(1.0f); + } + VkDescriptorSetAllocateInfo dsAI{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; dsAI.descriptorPool = boneDescPool_; dsAI.descriptorSetCount = 1;