The area trigger system was temporarily moving the player to the trigger
center and sending a heartbeat before firing CMSG_AREATRIGGER. This told
the server the player was at a different location, causing unexpected
teleports (e.g. Stormwind to Hillsbrad). Just send the area trigger
packet directly — the player is already inside the trigger radius.
Was reading field 2 (InstanceType) which fell back to field 1 (internal
name like "Azeroth"). Field 4 has the localized display name ("Eastern
Kingdoms").
getMapName() returned empty for mapId 0 (Eastern Kingdoms) due to an
early return guard. Remove it since 0 is a valid map ID.
queryGuildInfo() required IN_WORLD state but the character screen is at
CHAR_LIST_RECEIVED. The server accepts CMSG_GUILD_QUERY before login,
so just check for a connected socket.
Load area names from AreaTable.dbc (canonical zone names) in addition
to WorldMapArea.dbc so zone IDs from SMSG_CHAR_ENUM resolve to names.
Use lookupGuildName() to query and display guild names instead of raw
guild IDs.
The decomposition PRs moved mail state to InventoryHandler but the GO
interaction code still set stale GameHandler fields. Add openMailbox()
on InventoryHandler and forward from GameHandler so the correct
mailboxGuid_/mailboxOpen_ are set and refreshMailList() works.
Calculate repair costs client-side using DurabilityCosts.dbc and
DurabilityQuality.dbc. Block repair when player can't afford it and
only apply optimistic durability/gold updates when cost is verified.
Show repair cost next to the Repair All button in the vendor window.
MAX_CULL_INSTANCES was 16384 but game object instances were allocated
at indices 20000+, beyond the cull buffer. Increased to 65536.
Also compute fallback boundRadius from vertex extents when M2 header
reports 0, and seed bones for global-sequence-only animated models.
handleValuesUpdate silently dropped VALUES updates for item GUIDs not in
entityManager, causing repair-all durability changes to be lost. Fall
through to updateItemOnValuesUpdate for items tracked in onlineItems_.
The minimum floor (3.0 for sphere radius, 4.0 for box dimensions) was
inflating narrow triggers like AT 5711 (boxWidth 1.06 → 4.0), causing
false area trigger fires near the Stormwind AH and unexpected teleports.
Add update=none and shallow=true to extern/FidelityFX-FSR2 and
extern/FidelityFX-SDK in .gitmodules.
The 'Checkout' step runs git submodule update --init --force --depth=1
on all submodules, including the Kelsidavis FSR2/SDK forks, which fails
because it tries to resolve a specific commit SHA via shallow fetch.
The 'Fetch AMD FSR2 SDK' step already rm -rf's and re-clones both
directories from the correct upstream repos, so the submodule checkout
step is redundant and harmful.
update=none causes git submodule update (and actions/checkout submodules:true)
to skip these two entries. Local developers who want the FSR2/SDK must
run the manual fetch step or use 'git submodule update --init <path>'
explicitly.
The .gitignore commit was never pushed to the FSR2 fork, breaking CI
shallow clones. Revert to 3d22aef and add ignore=dirty to .gitmodules
to suppress generated shader header noise.
- VkTexture::isValid() now checks both image AND sampler handles. Previously
it only checked the image, so a texture with a valid image but NULL sampler
would pass validation and get bound to a descriptor set. On MoltenVK (macOS)
this renders as pink/magenta boxes; the fallback white texture is now
correctly used instead.
- Fix fs::path to std::string implicit conversion in asset extractor that
broke the Windows (MSYS2/clang) CI build.
- mpq and locale finding is now case insensitive
- improve extraction order and support more patches
- unified much of the mpq logic for all expansions
- return a list of ordered paths for loading
M2 GPU instancing
- M2InstanceGPU SSBO (96 B/entry, double-buffered, 16384 max)
- Group opaque instances by (modelId, LOD); single vkCmdDrawIndexed per group
- boneBase field indexes into mega bone SSBO via gl_InstanceIndex
Indirect terrain drawing
- 24 MB mega index buffer (6M uint32) + 64 MB mega vertex buffer
- CPU builds VkDrawIndexedIndirectCommand per visible chunk
- Single VB/IB bind per frame; shadow pass reuses mega buffers
- Replaced vkCmdDrawIndexedIndirect with direct vkCmdDrawIndexed to fix
host-mapped buffer race condition that caused terrain flickering
GPU frustum culling (compute shader)
- m2_cull.comp.glsl: 64-thread workgroups, sphere-vs-6-planes + distance cull
- CullInstanceGPU SSBO input, uint visibility[] output, double-buffered
- dispatchCullCompute() runs before main pass via render graph node
Consolidated bone matrix SSBOs
- 16 MB double-buffered mega bone SSBO (2048 instances × 128 bones)
- Eliminated per-instance descriptor sets; one megaBoneSet_ per frame
- prepareRender() packs bone matrices consecutively into current frame slot
Render graph / frame graph
- RenderGraph: RGResource handles, RGPass nodes, Kahn topological sort
- Automatic VkImageMemoryBarrier/VkBufferMemoryBarrier between passes
- Passes: minimap_composite, worldmap_composite, preview_composite,
shadow_pass, reflection_pass, compute_cull
- beginFrame() uses buildFrameGraph() + renderGraph_->execute(cmd)
Pipeline derivatives
- PipelineBuilder::setFlags/setBasePipeline for VK_PIPELINE_CREATE_DERIVATIVE_BIT
- M2 opaque = base; alphaTest/alpha/additive are derivatives
- Applied to terrain (wireframe) and WMO (alpha-test) renderers
Rendering bug fixes:
- fix(shadow): compute lightSpaceMatrix before updatePerFrameUBO to eliminate
one-frame lag that caused shadow trails and flicker on moving objects
- fix(shadow): scale depth bias with shadowDistance_ instead of hardcoded 0.8f
to prevent acne at close range and gaps at far range
- fix(visibility): WMO group distance threshold 500u → 1200u to match terrain
view distance; buildings were disappearing on the horizon
- fix(precision): camera near plane 0.05 → 0.5 (ratio 600K:1 → 60K:1),
eliminating Z-fighting and improving frustum plane extraction stability
- fix(streaming): terrain load radius 4 → 6 tiles (~2133u → ~3200u) to exceed
M2 render distance (2800u) and eliminate pop-in when camera turns;
unload radius 7 → 9; spawn radius 3 → 4
- fix(visibility): ground-detail M2 distance multiplier 0.75 → 0.9 to reduce
early pop of grass and debris
Drop PROT_EXEC from warden module mmap when using Unicorn emulation
(not needed — module image is copied into emulator address space). Use
MAP_JIT on macOS for the native fallback path.
Add --listfile option to asset_extract and SFileAddListFileEntries
support for resolving unnamed MPQ hash table entries from external
listfiles.
- Enable alpha-to-coverage on alphaTestPipeline for smooth hair edges
when MSAA is active (both init and recreatePipelines paths)
- Shader uses fwidth()-based alpha rescaling for clean coverage
- Skip group 17/18 geosets (DK/NE eye glow) when no geoset filter is
set — prevents blue eye glow on all NPCs
- Add missing <libgen.h> include for dirname() on Linux
Whisper sender name may not be in the player name cache when the packet
arrives. Store the sender GUID and lazily resolve the name from the
cache in getLastWhisperSender(). Also backfill lastWhisperSender_ when
the SMSG_NAME_QUERY_RESPONSE arrives.
- alphaTestPipeline_ uses blendDisabled() so surviving pixels are fully
opaque (was blendAlpha, causing hair to blend with background)
- Remove alphaCutout from alphaTest condition — opaque materials like
capes no longer alpha-test just because their texture has an alpha
channel
- Two-pass batch rendering: opaque (blendMode 0) draws first to
establish depth, then alpha-key/blend draws on top
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".
Global sequence bones (hair, cape, physics) need time values spanning
their full duration (up to ~968733ms), but animationTime wraps at the
current animation's sequence duration (~2000ms for walk). This caused
vertex spikes projecting from fingers/neck/ponytail as bones got stuck
in the first ~2s of their loop. Add a separate globalSequenceTime
accumulator that is not wrapped at the animation duration.
Drop PROT_EXEC from warden module mmap when using Unicorn emulation
(not needed — module image is copied into emulator address space). Use
MAP_JIT on macOS for the native fallback path.
Add --listfile option to asset_extract and SFileAddListFileEntries
support for resolving unnamed MPQ hash table entries from external
listfiles.
- Enable alpha-to-coverage on alphaTestPipeline for smooth hair edges
when MSAA is active (both init and recreatePipelines paths)
- Shader uses fwidth()-based alpha rescaling for clean coverage
- Skip group 17/18 geosets (DK/NE eye glow) when no geoset filter is
set — prevents blue eye glow on all NPCs
- Add missing <libgen.h> include for dirname() on Linux
Whisper sender name may not be in the player name cache when the packet
arrives. Store the sender GUID and lazily resolve the name from the
cache in getLastWhisperSender(). Also backfill lastWhisperSender_ when
the SMSG_NAME_QUERY_RESPONSE arrives.
- alphaTestPipeline_ uses blendDisabled() so surviving pixels are fully
opaque (was blendAlpha, causing hair to blend with background)
- Remove alphaCutout from alphaTest condition — opaque materials like
capes no longer alpha-test just because their texture has an alpha
channel
- Two-pass batch rendering: opaque (blendMode 0) draws first to
establish depth, then alpha-key/blend draws on top
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".
Global sequence bones (hair, cape, physics) need time values spanning
their full duration (up to ~968733ms), but animationTime wraps at the
current animation's sequence duration (~2000ms for walk). This caused
vertex spikes projecting from fingers/neck/ponytail as bones got stuck
in the first ~2s of their loop. Add a separate globalSequenceTime
accumulator that is not wrapped at the animation duration.
buildDefaultPlayerGeosets() was inserting all submeshIds 0-99 into
activeGeosets, showing every hair variation simultaneously. Now uses
the hairGeosetMap_ (from CharHairGeosets.dbc) to select only the
correct hair scalp geoset for the player's race/sex/style, matching
the existing NPC geoset filtering logic in EntitySpawner.
Hair, cape, and other physics bones use global sequences (continuously
looping timers independent of the character's current animation). The
character renderer was ignoring globalSequence entirely, causing these
bones to fall back to identity transforms and produce deformed/spiked
hair geometry. Added resolveTrackTime() to wrap global sequence time
correctly, matching the M2 renderer's existing behavior.