hw.usermem is a 32-bit kernel sysctl; on systems with ≥16 GB RAM the value
overflows signed int32, wraps to ~2 GB, and permanently triggers
isSevereMemoryPressure() — blocking all terrain worker threads from ever
calling prepareTile() (fixes#68).
Replace with host_statistics64(HOST_VM_INFO64) which returns 64-bit free +
inactive page counts, matching what Activity Monitor and vm_stat report.
Users building with the LunarG Vulkan SDK who haven't sourced setup-env.sh
have VK_ICD_FILENAMES unset, so the Vulkan loader can't find MoltenVK and
SDL's pre-instance VK_KHR_surface check fails with the misleading
"windowing_extensions_not_present" error.
Before calling SDL_Vulkan_LoadLibrary, probe $VULKAN_SDK/share/vulkan/icd.d/
then the two common Homebrew prefixes (arm64 and x86) and auto-set
VK_ICD_FILENAMES if a MoltenVK_icd.json is found. Also tighten the macOS
failure message to suggest brew install vulkan-loader or sourcing setup-env.sh
instead of the Windows-centric vulkan-1.dll hint.
Issue #65: add explicit libunicorn copy fallback in the macOS bundle step,
mirroring the existing Vulkan/OpenSSL/FFmpeg fallbacks that handle dylibbundler
misses on Homebrew-installed libraries.
Issue #66: loadSettings() was storing camera values (mouse sensitivity,
invert mouse, zoom, stiffness, pivot height) only into pending fields without
applying them to the runtime CameraController, so the camera always started
with defaults. settingsInit then read those defaults back, overwriting the
loaded values. Fix: apply all five camera settings to the controller in
loadSettings() and push the loaded values into the controller in settingsInit
instead of reading back from it. Also adds the requested Save Settings button
alongside Back to Game.
The Warden code currently always falls through to GameHandler fake
responses (the emulator native execution path is intentionally disabled
for safety). Each fallback was emitting:
- 1 LOG_WARNING ('processCheckRequest NOT IMPLEMENTED')
- 1 LOG_INFO ('Would call module->PacketHandler() here')
Warden checks fire every 10-30 seconds during gameplay, so this
produced roughly 2-12 line/min in logs even on stable sessions. The
warning was also misleading: the path IS implemented (as a documented
fallback), it's just not the native-execution path.
Changes:
- Replace the per-call WARNING/INFO with a sticky once-per-session
LOG_WARNING + LOG_DEBUG for subsequent calls.
- Demote the per-check 'Processing check request via emulator…',
'Calling emulated PacketHandler', and 'PacketHandler wrote N
byte response' lines from INFO to DEBUG — these describe normal
periodic behavior, not events worth surfacing.
- Consolidate the 7-line module-loading limitations banner into a
single INFO + one DEBUG with the same content.
No behavior change — only log levels and wording.
Commit 3a09f4b9 promoted these two log lines to INFO to debug item
queries (which led to the fix in 94d32688 sizing the backpack window
to actual keyring contents). With the diagnostic resolved, demoting
them back to DEBUG so they don't spam INFO output:
- queryItemInfo: SENT — fires every time a new item entry is
encountered (loot drops, vendor browsing, mail attachments).
- handleItemQueryResponse: RECV — fires per response from server.
In normal play a player can hit dozens of these per minute when
opening vendors or interacting with new items. They stay available
via WOWEE_LOG_LEVEL=DEBUG when needed for future diagnostics.
Data/expansions/tbc/dbc_layouts.json had DurationIndex=40 — but that
column is RangeIndex in TBC 2.4.3. DurationIndex is at column 34.
Per cmangos mangos-tbc src/game/Server/DBCStructure.h:
uint32 CastingTimeIndex; // 22
uint32 DurationIndex; // 34
uint32 rangeIndex; // 40
Visible impact prior to this fix on TBC servers: every spell-duration
lookup read the spell's RangeIndex value (an index into SpellRange.dbc)
instead of its actual DurationIndex (an index into SpellDuration.dbc).
Cast durations and aura durations would have come back as nonsense
values — most likely zero or wildly incorrect for spells whose range
index happens to be a valid duration row.
Found by scanning all dbc_layouts.json files for duplicate field
indices within a single DBC entry — the (DurationIndex, RangeIndex,
40) collision was the only one across all four expansions.
Classic (Duration=40 Range=33) and WotLK (Duration=40 Range=49)
remained intact; only TBC had the alias.
include/game/protocol_constants.hpp declared SESSION_KEY_HEX_LENGTH=40
but the constant was never consumed anywhere in src/ or include/.
The name was also wrong: WoW SRP session keys are 40 *bytes*
(320 bits → 80 hex chars), so neither interpretation of the constant
matched its name.
Removed rather than renamed since no one references it. If a length
constant is added back later, it should be SESSION_KEY_BYTE_LENGTH=40
to match how src/auth/auth_handler.cpp:390 logs the size.
Two comments in src/rendering/wmo_renderer.cpp (lines 2782 and 3393)
claimed the wall classification threshold 'matches MAX_WALK_SLOPE
(cos 50° ≈ 0.6428)', but the actual code uses 0.65 — which is
cos(~49.46°), not cos(50°).
Per MEMORY.md and the WoW slope conventions, there are two distinct
thresholds in this codebase:
- Wall vs walkable: absNz < 0.65 (≈ cos 49.46°)
- Slope-sliding lockout: normalZ < 0.6428 (= cos 50°)
The comment conflated them. Reworded both occurrences to make the
distinction explicit. Comment-only — no code changes.
item_tooltip_renderer.cpp:378 queries Faction.dbc Name field to
resolve faction IDs to localized names for the 'Requires X Reputation'
line in item tooltips. None of the four dbc_layouts.json defined a
Name entry under Faction; the code's ternary fallback (20u) only
fired when the entire Faction layout was null. With the layout
present, (*layout)['Name'] returned DBC_FIELD_INVALID (0xFFFFFFFF)
and dbc->getString(r, 0xFFFFFFFF) read out of bounds.
Result: s_factionNames never populated → every item with a reputation
requirement displayed 'Unknown Faction'.
Canonical Name column index per Faction.dbc DBC format strings
(string offset to the English-locale entry):
Vanilla 1.12: 19 (cmangos-classic DBCfmt.h FactionEntryfmt:
"niiiiiiiiiiiiiiiiiissssssssxxxxxxxxxx")
TBC 2.4.3: 23 (cmangos-tbc SQLStorages.cpp FactionEntryfmt:
"iiiiiiiiiiiiiiiiiiiffiissssssssssssssss")
WotLK 3.3.5a: 23 (TrinityCore 3.3.5 DBCfmt.h FactionEntryfmt:
"niiiiiiiiiiiiiiiiiiffiissssssssssssssssxxxxxxxxxxxxxxxxxx")
Note: in 1.12 the row layout omits ReputationFlags[4] that TBC/WotLK
added, so the Name string offset moves from 19 → 23 across the
TBC bump.
Same root-cause pattern as bd5e9aa2 and 50e31ab0 (DBC field referenced
by code but missing from layout JSON → operator[] sentinel beats
the ternary fallback).
entity_spawner.cpp:1832-1835 queries these for shoulder-piece M2
attachment (shoulders have two models — left at attachment point 5
and right at attachment point 6):
const uint32_t leftModelField = idiL ? (*idiL)["LeftModel"] : 1u;
const uint32_t rightModelField = idiL ? (*idiL)["RightModel"] : 2u;
const uint32_t leftTexFieldS = idiL ? (*idiL)["LeftModelTexture"] : 3u;
const uint32_t rightTexFieldS = idiL ? (*idiL)["RightModelTexture"]: 4u;
None of the four dbc_layouts.json files defined RightModel or
RightModelTexture. The ternary returns the layout lookup result when
idiL is non-null — and DBCFieldMap::operator[] on a missing field
returns DBC_FIELD_INVALID (0xFFFFFFFF), NOT the intended fallback.
So shoulder right-side M2 model and texture loaded from field
0xFFFFFFFF (out of bounds, getUInt32 returns 0) and the right
shoulder pad never rendered on any character.
Added the standard pairing values (RightModel=2, RightModelTexture=4)
to all four expansion layouts — these match the canonical
ItemDisplayInfo.dbc field order and the code's intended fallbacks.
Same root cause as bd5e9aa2 (SpellRange.MinRange) — the
'tnL ? (*tnL)["Field"] : N' pattern silently breaks when the field
is missing from the JSON but the DBC is present, because the
sentinel from operator[] beats the fallback N.
application.cpp:438 queries SpellRange.MinRange to populate the
spell-range map used by minimum-distance casting checks. None of the
four dbc_layouts.json files defined MinRange, so the lookup returned
the DBC_FIELD_INVALID sentinel (0xFFFFFFFF); rDbc->getFloat(i, 0xFFFFFFFF)
then read out of bounds and returned 0. Minimum-range checks were
silently dead on every expansion.
Canonical SpellRange.dbc layouts (per spellbook_screen.cpp:71-74):
Classic 1.12: 0=ID, 1=MinRange, 2=MaxRange
TBC / WotLK: 0=ID, 1=MinRangeFriendly, 2=MinRangeHostile,
3=MaxRangeFriendly, 4=MaxRangeHostile
The existing MaxRange values (classic 2, TBC/WotLK 4) follow the
'Hostile' convention; MinRange is wired the same way (1 for Vanilla,
2 for TBC/WotLK).
Visible effect: spells with a non-zero minimum range (hunter ranged
abilities, Pyroblast, etc.) will now correctly trigger 'You are too
close' errors and target-validity checks at the client level.
Per cmangos / vmangos Vanilla 1.12 opcode tables:
SMSG_INSPECT = 0x115 (cmangos Opcodes.h:315, vmangos Opcodes_1_12_1.h:280)
That wire slot was REPURPOSED in TBC to SMSG_INSPECT_RESULTS_UPDATE
(with a different packet body). The Classic opcode table inherited the
post-TBC name, which meant:
- Server sends 0x115 with the Vanilla SMSG_INSPECT body
(PackedGUID + 19 uint32 itemEntries — EQUIPMENT_SLOT_END=19).
- fromWire(0x115) returned LogicalOpcode::SMSG_INSPECT_RESULTS_UPDATE
on Classic.
- social_handler.cpp:128 routed the bytes to handleInspectResults(),
which parses the WotLK-style format and misread the packet.
- The handler at game_handler_packets.cpp:1902 — which has the
correct Classic 1.12 parser and an explicit comment 'only reachable
on Classic servers' — was dead code on Classic.
Fixing the JSON name routes wire 0x115 to the Classic-aware handler.
TBC and WotLK JSONs are untouched (SMSG_INSPECT_RESULTS_UPDATE = 0x115
is correct for those expansions).
Turtle inherits classic via _extends, so it gets the fix automatically.
Not patched in this commit:
- SMSG_SET_REST_START / SMSG_QUEST_FORCE_REMOVE both legitimately
exist at 0x21E across forks (vmangos uses one name, AzerothCore/TC
uses the other). quest_handler.cpp:685-700 already disambiguates
by expansion at runtime, so the existing mapping is defensible.
- SMSG_LEVELUP_INFO_ALT — appears to be a project-invented name with
no upstream wire value. Left alone.
UF::UNIT_FIELD_BYTES_1 is declared in include/game/update_field_table.hpp:23
and queried at entity_controller.cpp:553 (used to read the shapeshift
form ID, which lives in byte3 of the BYTES_1 update field). All four
expansion JSONs define a value for it.
But kUFNames in update_field_table.cpp had no entry mapping the JSON
string 'UNIT_FIELD_BYTES_1' to UF::UNIT_FIELD_BYTES_1, so the loader
silently dropped the JSON entry at load time. fieldIndex(UF::UNIT_FIELD_BYTES_1)
always returned 0xFFFF on every expansion, and shapeshift-form detection
in entity_controller.cpp could never trigger.
Found while cross-checking 'declared in enum vs. provided in JSON':
- UNIT_FIELD_BYTES_1 was the only enum field with JSON entries but no
kUFNames mapping.
- The remaining two JSON-only names (PLAYER_END, UNIT_FIELD_NATIVEDISPLAYID
on classic/turtle) are informational entries — they aren't declared
in the UF enum at all, so silently dropping them is fine.
- PLAYER_FIELD_KEYRING_SLOT_1 is declared in code but not in any JSON;
inventory_handler.cpp:2631 falls back to deriving its base from
bankBagBase, so its absence is intentional.
Combined with the recent UNIT_FIELD_BYTES_1 index corrections (644cfcc8
classic/turtle 133->138), this is what actually enables shapeshift
form detection at runtime — both fixes were needed.
UNIT_NPC_EMOTESTATE is read in entity_controller.cpp:551 for stationary
NPC looping animations (sleeping, talking, working, etc.) and in
animation_callback_handler.cpp:323.
- classic, turtle, tbc: field was missing entirely — fieldIndex() was
returning the 0xFFFF sentinel, so NPC idle emote animations never
played on those expansions.
- wotlk: field was set to 164, which is past UNIT_END (148) — that
index falls inside the PLAYER_ range, so reading it would have
pulled an unrelated player-fields value rather than the unit emote
state.
Authoritative sources:
Vanilla 1.12: 148 (vmangos UpdateFields_1_12_1.h:93,
OBJECT_END + 0x8E)
TBC 2.4.3: 169 (cmangos mangos-tbc UpdateFields.h:118,
OBJECT_END + 0xA3)
WotLK 3.3.5a: 83 (TrinityCore 3.3.5 UpdateFields.h:140,
OBJECT_END + 0x4D — adjacent to UNIT_NPC_FLAGS=82)
Python collision check after patch: no UNIT_* aliasing in any file.
Adds a short 'Expansion Config Files' section noting:
- opcodes.json supports _extends/_remove (used by turtle to inherit
from classic)
- update_fields.json does NOT support _extends; classic and turtle
copies must be hand-synced
- authoritative sources for canonical field indices per expansion
Context: the previous commit (644cfcc8) fixed a Vanilla 1.12 update-
field bug that affected both classic and turtle JSONs. Without the
guidance above, future contributors are likely to update one and
forget the other.
The classic and turtle update_fields.json files had several wrong
indices that aliased fields together and misread the unit record.
Cross-referenced vmangos UpdateFields_1_12_1.h
(github.com/vmangos/core, OBJECT_END=6) and corrected:
- UNIT_FIELD_BYTES_1: 133 -> 138 (was colliding with MOUNTDISPLAYID)
- UNIT_FIELD_STAT0..STAT4: 138..142 -> 150..154 (the old slot held
pet fields PETNUMBER/PET_NAME_TIMESTAMP/
PETEXPERIENCE/PETNEXTLEVELEXP, not stats)
- UNIT_FIELD_RESISTANCES: 154 -> 155 (off by one — STAT4 lives at 154)
- UNIT_FIELD_NATIVEDISPLAYID: added (was missing) -> 132
The verified other indices (DISPLAYID=131, MOUNTDISPLAYID=133,
DYNAMIC_FLAGS=143, NPC_FLAGS=147, BYTES_0=36) all matched vmangos
and were left alone.
Practical impact on Classic/Turtle prior to this fix:
- entity_controller.cpp:548-553 read MOUNTDISPLAYID and BYTES_1 from
the same slot — mount/dismount detection and shapeshift form
detection would race and clobber each other.
- All STAT* readers were reading pet-related fields instead of stats,
so character-sheet stats and stat-derived UI on Classic/Turtle
would be wrong/zero.
- UNIT_FIELD_RESISTANCES readers got STAT4 instead of armor resist.
Python collision check after patch: no UNIT_* collisions in either
file.
While auditing docs found that classic/update_fields.json and
turtle/update_fields.json both assign index 133 to two distinct
fields:
UNIT_FIELD_BYTES_1 = 133
UNIT_FIELD_MOUNTDISPLAYID = 133
These are read separately at entity_controller.cpp:548-553 for
display-change detection vs mount/dismount vs shapeshift, so on
Classic/Turtle the two events alias and one will be misattributed
(visible as mount stutter or wrong shapeshift transitions).
TBC and WotLK assign distinct values (BYTES_1=137/137,
MOUNTDISPLAYID=154/69), so the issue is specific to the two
classic-family JSONs. Adding to status.md as a known gap rather
than patching the index blindly — the correct vanilla 1.12 value
should be sourced from CMaNGOS-Zero before changing.
- Intro said 'supports three World of Warcraft expansions' but the
'Supported Expansions' bullet list immediately below has four
entries (Vanilla, TBC, WotLK, Turtle), and Data/expansions/ has
four subdirectories matching them. Updated count.
- Turtle WoW 1.17 → 1.18 — the rest of the doc set was synced in
dd552fa3 but this one line was missed. Data/expansions/turtle/
expansion.json declares major=1 minor=18.
Follow-up to 6c972fb7 (removed GLEW from container/Dockerfiles +
ATTRIBUTION.md) and fd78479a (removed from README's macOS brew line).
The CI workflows still installed libglew-dev / brew glew /
mingw-w64-*-glew across Ubuntu, macOS, and MSYS2 jobs, but nothing
in src/ or include/ references GLEW (this is a Vulkan project; the
loader only) and CMakeLists.txt has no find_package(GLEW). Dead
weight in every CI run.
- CMakeLists.txt:29: WOWEE_ENABLE_ASAN description said 'AddressSanitizer
(Debug builds)', but the implementation at lines 1206-1219 applies
-fsanitize=address,undefined to ALL configs (no $<$<CONFIG:Debug>:>
guard) and prints 'AddressSanitizer + UBSan: ENABLED'. Updated to
match reality.
- CHANGELOG.md / docs/architecture.md: TransportManager decomposition
claimed 1,200→500 LOC, but transport_manager.cpp is now 367 lines
(wc -l). Updated to ~370.
- CHANGELOG.md: world editor 'World Editor (tools/editor/)' header
claimed '14.7k+ lines, 59 files'. Actual: ~132k LOC across ~500
files in tools/editor/ (find + wc). Updated. The understatement
most likely reflects an early-2026 snapshot.
Three README stragglers missed by earlier passes:
- macOS brew install line still had 'glew' — removed (matches the
container/Dockerfile + ATTRIBUTION.md cleanup in 6c972fb7).
- 'Container build' block told users to run a non-existent script
container/build-in-container.sh. The real entrypoints are
container/run-{linux,macos,windows}.sh (per container/README.md);
updated the README to point at the real scripts and reference
container/README.md for details.
- 'extract_assets.sh / extract_assets.ps1 support classic, tbc, wotlk'
was missing 'turtle' — script usage strings list all four targets,
and an earlier commit (617fe0e9) fixed BUILD_INSTRUCTIONS.md but
missed README.
- src/game/game_handler.cpp:1589: SMSG_ACHIEVEMENT_EARNED is 0x468 on
WotLK 3.3.5a per Data/expansions/wotlk/opcodes.json; the comment
said 0x4AB, which is actually SMSG_CLIENTCACHE_VERSION.
- include/game/protocol_constants.hpp:18: header declares this file
is WotLK-only, but the comment claimed UNIT_FIELD_FLAGS lives at
index 46 (Classic/TBC/Turtle value). WotLK 3.3.5a is 59
(Data/expansions/wotlk/update_fields.json:44, MEMORY.md). Comment
rewritten to give both indices and note the bitmask is stable.
- include/game/protocol_constants.hpp:23: same problem for
UNIT_NPC_FLAGS — comment said offset 147 (Classic/Turtle), real
WotLK index is 82. Comment fixed the same way.
No code change — these were comment-only and the runtime indices are
read from the update-field tables, not these comments. But the file
is supposed to be the canonical reference for WotLK constants, so
the comments materially mislead readers.
- src/game/spell_handler.cpp:1795-1799: comment said handleSpellGo
clears lastInteractedGoGuid_ 'at line 958'. Verified: handleSpellGo
(line 1019) never clears it; the only clears are in
inventory_handler.cpp:716 (on SMSG_LOOT_RESPONSE) and
game_handler_callbacks.cpp:479. Reworded to point at the real owner.
- include/ui/chat_panel.hpp:147-148: 'will migrate to ChatInput in
Phase 6' — Phase 6.2/6.6/6.7 shipped without that migration; the
fields are still used directly in chat_panel*.cpp (27 references).
Reworded so the comment reflects what actually happened.
- include/game/game_handler.hpp:1047: KnockBackCallback parameter
comment called vcos/vsin 'render-space direction', but the source
(camera_controller.cpp:2367-2377) is explicit that they arrive in
server/wire space and only happen to also be valid render-space
because two coordinate swaps cancel. Updated to match the truth
and to call out the vspeed sign convention.
- docs/AMD_FSR2_INTEGRATION.md: removed 'Startup safety behavior'
bullets — WOWEE_ALLOW_STARTUP_FSR2 has zero references in src/,
include/, CMakeLists.txt, or any build script; the IN_WORLD-deferred
FSR2 logic does not exist. Documenting a knob that nothing reads
misleads users.
- README.md / docs/status.md / docs/server-setup.md / GETTING_STARTED.md:
Turtle WoW version was 1.17 in four places, but
Data/expansions/turtle/expansion.json declares major=1 minor=18
(build 7234). Sync docs to the canonical config.
- docs/status.md: 'Last updated' bumped from 2026-04-14 to 2026-05-14
to reflect the v1.9.1-preview-era body that has already replaced the
v1.8.9 snapshot.
- extern/VERSIONS.md: vk-bootstrap was 'latest' — pinned to v1.3.302
(from git submodule status). Added FidelityFX-FSR2 (v2.2.1-1-g3d22aef)
and FidelityFX-SDK (tracks main) — both real submodules. Added
catch2 (vendored amalgamated) and nlohmann/json 3.11.3 (vendored
single header) which were missing from the catalog.
- BUILD_INSTRUCTIONS.md: extract_assets.sh accepts classic|turtle|tbc|wotlk
(see script usage line 31), but the doc only listed three. Added
'turtle' in both macOS and Linux sections.
- ATTRIBUTION.md: removed GLEW (not used — no glew.h includes, no
find_package(GLEW) in CMakeLists.txt); added the libraries that
ARE actually used (Vulkan, vk-bootstrap, zlib, FFmpeg, Lua, Catch2,
nlohmann/json, stb, FidelityFX-FSR2). OpenSSL row clarified for 3.x.
- container/{builder-linux,builder-macos,builder-windows}.Dockerfile:
drop libglew-dev / vcpkg 'glew' — nothing links against GLEW, so
these were dead weight in the images.
- container/FLOW.md: matched the Dockerfile changes.
- docs/perf_baseline.md: Tracy is NOT vendored — building with
WOWEE_ENABLE_TRACY=ON references extern/tracy/ which doesn't exist.
Documented the required 'git clone' step. Also corrected the
M2::computeBoneMatrices / M2Renderer::update file paths
(m2_renderer_internal.h and m2_renderer_render.cpp, not
m2_renderer.cpp).
- WARDEN_IMPLEMENTATION.md: status said 'Complete' but
src/game/warden_module.cpp still has placeholder import binding
(line 1023), TODO for WardenFuncList extraction (line 1155), and
falls back to fake responses (line 234). Documented honestly.
- WARDEN_IMPLEMENTATION.md: removed the WoW.exe offset 0x005e3a03
claim — that exact offset appears nowhere in the codebase; the
modulus is a hardcoded placeholder (per include header).
- SKY_SYSTEM.md: example code used glEnable/glBlendFunc/glDisable in
a Vulkan-only project. Replaced with a Vulkan-blend description.
The [Unreleased] block contained items already shipped in v1.9.0/1.9.1
(per git tags and README). Retitled so readers can find the changes
under the version that actually contains them.
- docs/server-setup.md: realmlist column is 'flag', not 'realmflags'
(TrinityCore 3.3.5 schema). Add gamebuild for clarity. Bumped stale
v1.8.9-preview reference to v1.9.1-preview to match README.
- docs/packet-framing.md: status line claimed the doc was 'tested
against AzerothCore/TC/Mangos/Turtle' but the doc only describes
auth framing; world framing (with encryption) lives in WorldSocket.
Reworded so the scope is unambiguous.
- README.md / docs/architecture.md: remove the '664 opcode handlers' /
'664+ opcodes' figure (unverifiable — actual register*Handler() call
count is 33, but most opcodes are bulk-registered via array loops).
Reword to describe coverage qualitatively.
- docs/threading.md: WorldSocket::connectAsync(), startWatchdog(), and
TerrainManager::startWorkers() do not exist. Reference the actual
symbols: connect() / asyncPumpLoop(), the inline lambda in run(),
and TerrainManager::initialize() spawning workerLoop().
- GETTING_STARTED.md / EXPANSION_GUIDE.md: WOWEE_EXPANSION is not read
anywhere in src/. Expansion is selected via the auth screen, which
calls ExpansionRegistry::setActive() (src/ui/auth_screen.cpp:104).
- README.md install snippets: trailing '# comment' on backslash-
continued lines silently breaks the apt/dnf/pacman command. Moved
comments above each block.
- README.md Arch snippet: replace vulkan-devel (not a real Arch
package, per BUILD_INSTRUCTIONS.md:36-38) with vulkan-headers +
vulkan-icd-loader.
- TESTING.md: WOWEE_BUILD_TESTS defaults to ON in CMakeLists.txt:28,
not OFF as the doc claimed.
- TROUBLESHOOTING.md: log path is logs/wowee.log in CWD (per
src/core/logger.cpp:61), not ~/.wowee/logs/.
- CONTRIBUTING.md / docs/status.md: align test count with actual
31 test_*.cpp files in tests/ (was 27 in both docs, 31 in README).
- docs/quickstart.md: section numbering jumped from 2 to 4 to 5;
renumbered to 1-4.
Clang treats narrowing in brace-initialization as an error; macOS arm64
and windows-arm64 builds failed compiling m2_renderer_particles.cpp
because cachedBlendType (uint16_t) was passed to a uint8_t field.
renderBagWindow reserved height for ALL 32 keyring slots at 40px in 6
columns, but the keyring renders at 24px in 8 columns and only draws
rows that contain occupied slots. Result: a huge empty grey area below
the items in the Backpack window. Mirror the render-side calculation
(lastOccupied → visibleKeySlots → keyringRows at 24px) in the height
math; skip the keyring height entirely when nothing is keyed.
To see whether item queries are being sent and how the server responds,
upgrade queryItemInfo and handleItemQueryResponse log lines to INFO so
they show up without enabling verbose debug output. Includes the
resolved displayInfoId in the response log so we can see which items
are coming back as displayInfoId=0 versus a real value.
To narrow down why most inventory items render blank icons, surface a
warning at each early-return inside getItemIcon: ItemDisplayInfo.dbc
unloadable, displayInfoId missing from DBC, iconName empty, BLP file
not found, BLP decode failed. Pure logging — no behavior change.
Some AzerothCore announcer modules drop the "[Arena Queue Announcer]:"
prefix and just blast colored lines like "<player> joined : |cff..2x2|r"
or "<player> exited |cff..3x3|r" straight to chat. Add a second-tier
filter: when a message contains a color code AND an arena/BG bracket
token (2x2|r, 3x3|r, 5x5|r, 2v2|r, 3v3|r, 5v5|r — case-cased to match
the literal server output) AND a verb ("joined", "exited", "left",
"entered", "queue"), drop it. Player chat that happens to mention a
bracket won't carry color codes, so legitimate messages still pass.
The existing filter only matched the exact strings "BG Queue Announcer"
and "Queue status", so the Arena Queue Announcer (and any other case
variation the server happens to send) leaked straight through into
chat. Switch to a case-insensitive substring scan for "queue announcer"
and "queue status" so all known announcer prefixes are suppressed.
Chat tab bar render did `std::string tabLabel = getTabName(i)` (one
copy) then `tabLabel += " (" + std::to_string(unread) + ")"` (more
allocations) every frame for every tab. Format the label into a
fixed char[96] only when there's an unread count to append; pass
the underlying string's c_str() through otherwise.
The vast majority of chat messages don't contain $g/$G/$N tokens at
all, but replaceGenderPlaceholders unconditionally looked up the
active character, built the Pronouns struct, and copied the input
into a working std::string before scanning. Bail out before any of
that when the input has no '$' — fires per visible chat message per
frame.
WMORenderer::render did loadedModels.count(modelId) per instance just
to pre-filter the visibleInstances_ vector, then cullInstance did a
full loadedModels.find() on the same modelId anyway and bailed on
miss. Skip the pre-filter; let cullInstance handle the unloaded case.
Reserves visibleInstances_ to instances.size() so no reallocations.
Renderer::update was calling camera->getForward() and camera->getUp()
to feed AudioEngine::setListenerOrientation. getUp() internally
calls getRight() which calls getForward() again, so the per-frame
line ended up running three independent trig sequences. Compute the
forward vector once, derive right and up from it inline.
Weather::update walked the particle vector twice (update, then copy
position into the GPU upload buffer) and called camera.getPosition()
once per particle inside updateParticle. Fold both passes into one
loop and hoist the camera read once. For storms with thousands of
particles, that's one pass instead of two plus N fewer member reads.
ChatBubbleManager built a fresh std::string ("##ChatBubble" + guid)
for every bubble every frame. Format into a small char[40] with
snprintf instead — no per-bubble alloc.
The opaque draw path resolves textureTransformLookup → textureTransforms
inside the per-instance UV-offset loop. Those table indirections are
constant for every instance in a batch (only the interpolated translation
varies, since animTime is per-instance). Hoist the pointer above the
loop and let the inner body do just the interpVec3 + glm::vec2 pack.
startMoveAlongPath was computing fraction = cumDist[i] / totalDist
followed by fraction * durationMs for every waypoint, replacing
a per-iteration division with a hoisted reciprocal multiply.
handleMonsterMove rebuilt a fresh std::vector<std::array<float, 3>>
on every SMSG_MONSTER_MOVE with waypoints — and the push_back loop
doubled its capacity 2–3 times along the way. Reserve to the final
size (waypoints + start + dest) up front. Fires many times per
second on busy servers.
The animated-instance bone-update loop was recomputing the same
worldRadius / cullRadius / effectiveMaxDistSq / paddedRadius the
SSBO upload now reads from instance.cachedEffectiveMaxDistSqFactor
and instance.cachedPaddedRadius. Re-use the cached values here too
so the only per-frame work is the dot product and frustum sphere
test.
findLightVolumes was emitting a LOG_INFO for the first three active
volumes on every call (i.e. every frame), turning what was clearly a
one-time diagnostic into a continuous stream of allocations, format
calls, and log writes during normal play. Limit it to the first run.
After switching block.fields to a flat vector, the parser's
append_sorted loop could still trigger one or two reallocations as
the vector doubled. Popcount the update-mask blocks first to learn
exactly how many fields are about to be appended, then reserve()
once before the read loop. The vector grows to its final size in
one allocation per block.