Replace processAllReadyTiles() with bounded processReadyTiles() in the
same-map teleport and reconnect paths. processAllReadyTiles finalizes
every pending tile synchronously with a GPU sync wait, which caused
8+ second main-thread stalls when many tiles were queued. The bounded
version processes 1-4 tiles per call with async GPU upload — remaining
tiles finalize incrementally over subsequent frames.
Mirror the Turtle parser hardening: check remaining bytes before every
conditional read in ClassicPacketParsers::parseMovementBlock. Prevents
silent garbage reads (readUInt8 returns 0 past EOF) that corrupt
subsequent update fields and lose NPC data in multi-block packets.
The Turtle parseMovementBlock had no bounds checking on any reads.
Since Packet::readUInt8() returns 0 past the end without failing, the
parser could "succeed" with all-zero garbage data, then subsequent
parseUpdateFields would read from wrong positions, producing
"truncated field value" and "truncated update mask" errors.
Added bounds checks before every conditional read section (transport,
swimming pitch, fall time, jumping, spline elevation, speeds, spline
data, tail flags). Also removed the WotLK movement block fallback from
the Turtle parser chain — WotLK format is fundamentally incompatible
(uint16 flags, 9 speeds) and false-positive parses corrupt NPC data.
Also changed spline pointCount > 256 from cap-to-zero to return false
so the parser correctly fails instead of silently dropping waypoints.
The spline parser assumed WotLK format (durationMod, durationModNext,
conditional PARABOLIC fields) for all expansions. Classic/Turtle has a
simpler layout: timePassed+duration+splineId+pointCount directly.
Reading WotLK-specific fields from Classic data consumed wrong bytes,
causing pointCount to read garbage and the entire update block to fail
— losing dozens of NPC spawns in multi-block packets.
Now tries Classic format first (pointCount at offset 12), then WotLK
(offset 20+), then compact fallback. Also fixes WotLK SMSG_SPELL_GO
hit/miss targets to use full uint64 GUIDs instead of PackedGuid, which
was the root cause of garbage missCount values (46, 64, 241).
The legacy UPDATE_OBJECT spline path was reading verticalAccel (float)
and effectStartTime (uint32) unconditionally, but these 8 bytes are
only present when SPLINEFLAG_PARABOLIC (0x00000800) is set. Without
the flag, the extra reads shifted the stream by 8 bytes, causing
pointCount to read garbage (e.g. 3323328650) and failing the entire
update block parse.
Models that lose all instances are no longer immediately evicted from
GPU memory. Instead they get a 60-second grace period, preventing the
thrash cycle where GO models (barrels, chests, herbs) were evicted
every 5 seconds and re-loaded when the same object type respawned.
When SMSG_SPELL_GO reads a suspiciously high missCount (>20), log
the surrounding packet bytes, castFlags, and position for debugging
the persistent offset error causing garbage miss counts (46, 48, 241).
Servers may reject CMSG_GAMEOBJ_USE or cancel the resulting pickup
spell cast if movement flags are still active. Now sends MSG_MOVE_STOP
to clear directional movement before the interaction packet. Also adds
diagnostic logging for GO interactions to help trace collection issues.
Quest dialogs were showing literal "$C" instead of the player's class
name. Added support for $c/$C (class) and $r/$R (race) placeholders
in both game_screen and quest_log_screen substitution functions.
SMSG_SPELL_GO packets with unreasonably high miss counts (48, 118, 241)
were causing the entire packet to be discarded, losing all combat hit
data. Now salvage the successfully-parsed hit targets (needed for combat
text, health bars, animations) instead of discarding everything. Also
add spellId/hitCount to truncation warnings for easier diagnosis.
M2Renderer::removeInstance() was calling rebuildSpatialIndex() for every
single removal, causing 25-90ms frame hitches during entity despawns.
Now uses O(1) lookup via instanceIndexById, incremental spatial grid
cell removal, and swap-remove from the instance vector. The auxiliary
index vectors are rebuilt cheaply since they're small.
WotLK and TBC parsers were reading uint32+uint8 (5 bytes) for
SPELL_MISS_REFLECT entries, but the server only sends uint8
reflectResult (1 byte). This caused a 4-byte misalignment after every
reflected spell, corrupting subsequent miss entries and SpellCastTargets
parsing. Classic parser was already correct.
Right-clicking a locked container (e.g. Dead-Tooth's Strong Box) was
sending CMSG_USE_ITEM with spellId=0, which the server rejects. Locked
containers (itemClass==1, inventoryType==0) now send CMSG_OPEN_ITEM
instead, letting the server auto-check the keyring for the required key.
The VK_ERROR_DEVICE_LOST crash on AMD/Mali GPUs (barrier srcAccessMask)
was fixed in 2026-03-18. Enable refraction for new sessions so players
get the improved water visuals without needing to touch Settings.
Existing saved configs that explicitly disabled it are preserved.
'Release in X:XX' implied a client-enforced forced release; renamed to
'Auto-release in X:XX' (server-driven) and added 'Or wait for a player
to resurrect you.' hint so players know they can stay dead without
clicking Release Spirit.
Setting releasedSpirit_=true immediately on CMSG_REPOP_REQUEST raced
with PLAYER_FLAGS field updates that arrive from the server before it
processes the repop: the PLAYER_FLAGS handler saw wasGhost=true /
nowGhost=false and fired the 'ghost cleared' path, wiping corpseMapId_
and corpseGuid_ — so the minimap skull marker and the Resurrect from
Corpse dialog never appeared.
Ghost state is now driven entirely by the server-confirmed PLAYER_FLAGS
GHOST bit (and the login-as-ghost path), eliminating the race.
Macro conditions now support checking aura presence:
[buff:Power Word: Fortitude] — player has the named buff
[nobuff:Frost Armor] — player does NOT have the named buff
[debuff:Faerie Fire] — target has the named debuff
[nodebuff:Hunter's Mark] — target does NOT have the named debuff
Name matching is case-insensitive. When a target override (@target etc.)
is active the check uses that unit's aura list instead of the player's.
Adds /mark [icon], /marktarget, and /raidtarget slash commands that
set a raid mark on the current target. Accepts icon names (star,
circle, diamond, triangle, moon, square, cross, skull), numbers 1-8,
or "clear"/"none" to remove the mark. Defaults to skull when no
argument is given.
Right-clicking a castable pet ability (actionId > 6) in the pet action
bar now sends CMSG_PET_SPELL_AUTOCAST to toggle the spell's autocast
state. The local petAutocastSpells_ set is updated optimistically and
the tooltip shows the current state with a right-click hint.
setOnlinePlayerEquipment used wrong geoset ID ranges for boots (402+ instead
of 501+), gloves (301+ instead of 401+), and chest/sleeves (501+ instead of
801+), and was missing bare-shin (502), bare-wrist (801), and bare-leg (1301)
defaults. This caused other players to render with missing shin/wrist geometry
and wrong geosets when wearing equipment (the "shin mesh" gap in status.md).
Now mirrors the CharacterPreview::applyEquipment logic exactly:
- Group 4 (4xx) forearms/gloves: default 401, equipment 401+gg
- Group 5 (5xx) shins/boots: default 502, equipment 501+gg
- Group 8 (8xx) wrists/sleeves: default 801, equipment 801+gg
- Group 13 (13xx) legs/pants: default 1301, equipment 1301+gg
When a spell is queued in the 400ms window before the current cast ends,
render its icon dimmed (0.8 alpha) to the right of the cast bar progress,
with a "Queued: <name>" tooltip. The progress bar shrinks to accommodate
the icon when one is present.
Also exposes getQueuedSpellId() as a public const accessor on GameHandler
so the UI can observe the spell queue state without friend access.
Add SpellCastFailedCallback to GameHandler, fired from SMSG_CAST_RESULT
when result != 0. GameScreen registers the callback and records each failed
spellId in actionFlashEndTimes_ (keyed by spell ID, value = expiry time).
During action bar rendering, if a slot's spell has an active flash entry,
an AddRectFilled overlay is drawn over the button with alpha proportional
to remaining time (1.0→0.0 over 0.5 s), giving the same error-red flash
visual feedback as the original WoW client.
When SMSG_ITEM_PUSH_RESULT arrives for an item not yet in the cache, store
a PendingItemPushNotif and fire the 'Received: [item]' chat message only
after SMSG_ITEM_QUERY_SINGLE_RESPONSE resolves the name and quality, so the
notification always shows a proper item link instead of 'item #12345'.
Notifications that are already cached emit immediately as before; multiple
pending notifs for the same item are all flushed on the single response.
- /stopmacro [conditions] halts remaining macro commands; supports all existing
macro conditionals ([combat], [nocombat], [mod:shift], etc.) via the sentinel
action trick on evaluateMacroConditionals
- macroStopped_ flag in GameScreen; executeMacroText resets and checks it after
each command so /stopmacro mid-macro skips all subsequent lines
- Emit a "X is about to break!" UI error + system chat when an equipped item's
durability drops below 20% via SMSG_UPDATE_OBJECT field delta; warning fires
once per threshold crossing (prevDur >= maxDur/5, newDur < maxDur/5)
Tracks ITEM_ENCHANTMENT_SLOT 0 (permanent) and 1 (temporary) from item update
fields in OnlineItemInfo, then looks up names from SpellItemEnchantment.dbc and
renders them in both ItemDef and ItemQueryResponseData tooltip variants.
Supports: /castsequence [conds] [reset=N/target/combat] Spell1, Spell2, ...
Cycles through the spell list on successive button presses. State is keyed
by spell list so the same sequence shared across macros stays in sync.
- getMacroShowtooltipArg() parses the #showtooltip [SpellName] directive
- Action bar macro buttons now display the named spell's icon when
#showtooltip SpellName is present at the top of the macro body
- For bare #showtooltip (no argument), derives the icon from the first
/cast line in the macro (stripping conditionals and rank suffixes)
- Falls back to "Macro" text label only when no spell can be resolved
- Adds mouseoverGuid_ to GameHandler (set/cleared each frame by UI)
- renderNameplates() sets mouseoverGuid when the cursor is inside a
nameplate's hit region; resets to 0 at frame start
- Raid frame cells set mouseoverGuid while hovered (IsItemHovered)
- evaluateMacroConditionals() resolves @mouseover / target=mouseover to
the hover GUID; returns false (skip alternative) when no unit is hovered
This enables common healer macros like:
/cast [target=mouseover,help,nodead] Renew; Renew
Adds evaluateMacroConditionals() which parses the [cond1,cond2] Spell;
[cond3] Spell2; Default syntax and returns the first matching
alternative. Supported conditions:
- mod:shift/ctrl/alt, nomod — keyboard modifier state
- target=player/focus/target, @player/@focus/@target — target override
- help / harm (noharm / nohelp) — target faction check
- dead / nodead — target health check
- exists / noexists — target presence check
- combat / nocombat — player combat state
- noform / nostance / form:0 — shapeshift/stance state
- Unknown conditions are permissive (true) to avoid false negatives.
/cast now resolves conditionals before spell lookup and routes
castSpell() to the [target=X] override GUID when specified.
isHostileFaction() exposed as isHostileFactionPublic() for UI use.
- Macro text is now escaped (\\n, \\\\) on save and unescaped on load,
fixing multiline macros silently truncating after the first line in
the character config file.
- executeMacroText() runs every non-comment line of a macro body in
sequence (WoW behaviour), replacing the firstMacroCommand() approach
that only fired the first actionable line. The server still enforces
one spell-cast per click; non-cast commands (target, equip, pet, etc.)
now all execute correctly in the same macro activation.
Adds the standard WoW pet control slash commands used in macros:
- /petattack — attack current target
- /petfollow — follow player
- /petstay / /pethalt — stop and hold position
- /petpassive — set passive react mode
- /petdefensive — set defensive react mode
- /petaggressive — set aggressive react mode
- /petdismiss — dismiss the pet
All commands also appear in Tab-autocomplete.
These are standard WoW macro commands:
- /cancelform / /cancelshapeshift: exits current shapeshift form by
cancelling the first permanent aura (flag 0x20) on the player
- /cancelaura <name|#id>: cancels a specific player buff by spell name
or numeric ID (e.g. /cancelaura Stealth, /cancelaura #1784)
Also expand the Tab-autocomplete command list to include /cancelaura,
/cancelform, /cancelshapeshift, /dismount, /sit, /stand, /startattack,
/stopcasting, /target, and other commands that were previously missing.
Macros often start with a #showtooltip or #show directive line; these
should not be executed as chat commands. The firstMacroCommand() helper
now scans forward through the macro text, skipping blank lines and any
line starting with '#', and executes the first actual command line.
Applies to all three execution paths: left-click, keyboard shortcut,
and right-click Execute menu item.
Adds WoW macro-standard /use argument forms alongside the existing
item-name search:
- /use 0 <slot> — backpack slot N (1-based, bag 0)
- /use 1-4 <slot> — equipped bag slot N (1-based bag index)
- /use <N> — equip slot N (1-based, e.g. /use 16 = main hand)
These are the standard forms used in macros like:
#showtooltip
/use 13 (trinket 1)
/cast Arcane Blast
The captureSceneHistory barrier was using srcAccessMask=0 with
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT when transitioning the swapchain
image from PRESENT_SRC_KHR to TRANSFER_SRC_OPTIMAL. This does not
flush the GPU's color attachment write caches, causing VK_ERROR_DEVICE_LOST
on strict drivers (AMD, Mali) that require explicit cache invalidation
before transfer reads.
Fix: use VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT + COLOR_ATTACHMENT_OUTPUT
as the source mask so color writes are properly made visible to the
transfer unit before the image copy begins.
Also remove the now-unnecessary "requires FSR" restriction in the
settings UI — water refraction can be enabled independently of FSR.
Two companion improvements for the macro system:
- Keyboard shortcut handler now executes MACRO slots (1-0 keys) by running
the first line of their text as a command, same as left-click
- /cast now accepts a numeric spell ID or #ID prefix (e.g. /cast 133,
/cast #133) in addition to spell names — enables standard WoW macro
syntax and direct spell ID testing
Macros in WoW are client-side — the server sends only a macro index via
SMSG_ACTION_BUTTONS, never the text. This commit adds local storage and
a UI so macro slots are actually usable.
- GameHandler: getMacroText/setMacroText accessors backed by macros_ map;
text is persisted to the character .cfg file as macro_N_text= entries
- Action bar left-click: MACRO slot executes first line of macro text as
a chat/slash command (same path as /cast, /use, etc.)
- Context menu: "Execute" and "Edit" items for MACRO slots; "Edit" opens
a multiline modal editor (320×80 px, up to 255 chars) with Save/Cancel
- Tooltip: shows macro text body below the index; hints "right-click to
Edit" when no text is set yet
Both handlers silently cleared state with no visible message, leaving the
player unsure why their attack failed. Split the shared case block:
- NOTSTANDING: show "You need to stand up to fight." (rate-limited to 1.25s
via the existing autoAttackRangeWarnCooldown_ guard), keep auto-attack
active so it fires once the player stands.
- CANT_ATTACK: call stopAutoAttack() to end the attack loop (target is a
critter, civilian, or already dead — no point retrying), then show "You
can't attack that." with the same rate limiter.
Macro slots stored from SMSG_ACTION_BUTTONS had no tooltip and no context
menu header — hovering or right-clicking gave a blank result. Add an
"else if MACRO" branch to both the tooltip and the popup-context-item so
that "Macro #N" is displayed in both places. Clearing via right-click
still works via the existing "Clear Slot" item which was already outside
the type branches.