Commit graph

2918 commits

Author SHA1 Message Date
Paul
f5757aca83 refactor(game): extract EntityController from GameHandler (step 1.3)
Moves entity lifecycle, name/creature/game-object caches, transport GUID
tracking, and the entire update-object pipeline out of GameHandler into a
new EntityController class (friend-class pattern, same as CombatHandler
et al.).

What moved:
- applyUpdateObjectBlock() — 1,520-line core of all entity creation,
  field updates, and movement application
- processOutOfRangeObjects() / finalizeUpdateObjectBatch()
- handleUpdateObject() / handleCompressedUpdateObject() / handleDestroyObject()
- handleNameQueryResponse() / handleCreatureQueryResponse()
- handleGameObjectQueryResponse() / handleGameObjectPageText()
- handlePageTextQueryResponse()
- enqueueUpdateObjectWork() / processPendingUpdateObjectWork()
- playerNameCache, playerClassRaceCache_, pendingNameQueries
- creatureInfoCache, pendingCreatureQueries
- gameObjectInfoCache_, pendingGameObjectQueries_
- transportGuids_, serverUpdatedTransportGuids_
- EntityManager (accessed by other handlers via getEntityManager())

8 opcodes re-registered by EntityController::registerOpcodes():
  SMSG_UPDATE_OBJECT, SMSG_COMPRESSED_UPDATE_OBJECT, SMSG_DESTROY_OBJECT,
  SMSG_NAME_QUERY_RESPONSE, SMSG_CREATURE_QUERY_RESPONSE,
  SMSG_GAMEOBJECT_QUERY_RESPONSE, SMSG_GAMEOBJECT_PAGETEXT,
  SMSG_PAGE_TEXT_QUERY_RESPONSE

Other handler files (combat, movement, social, spell, inventory, quest,
chat) updated to access EntityManager via getEntityManager() and the
name cache via getPlayerNameCache() — no logic changes.

Also included:
- .clang-tidy: add modernize-use-nodiscard,
  modernize-use-designated-initializers; set -std=c++20 in ExtraArgs
- test.sh: prepend clang's own resource include dir before GCC's to
  silence xmmintrin.h / ia32intrin.h conflicts during clang-tidy runs

Line counts:
  entity_controller.hpp  147 lines  (new)
  entity_controller.cpp  2172 lines (new)
  game_handler.cpp       8095 lines (was 10143, −2048)

Build: 0 errors, 0 warnings.
2026-03-29 08:21:27 +03:00
Kelsi
4f2a4e5520 debug: log SMSG_SPELL_START to diagnose missing cast bar
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
2026-03-28 17:00:24 -07:00
Kelsi
71cf3ab737 debug: log CMSG_CAST_SPELL packet size to verify format 2026-03-28 16:56:15 -07:00
Kelsi
d68bb5a831 fix: spell facing used atan2(dy,dx) but canonical convention is atan2(-dy,dx)
The canonical yaw convention (documented in coordinates.hpp) is
atan2(-dy, dx) where X=north, Y=west. North=0, East=+PI/2.

The spell facing code used atan2(dy, dx) (no negation on dy), producing
a yaw ~77° off from the correct server orientation. The server rejected
every cast with "unit not in front" because the sent orientation pointed
in the wrong direction.

Fixed in all 3 locations: charge facing, melee facing, and general
pre-cast facing.
2026-03-28 16:51:23 -07:00
Kelsi
7f3c7379b5 debug: log pre-cast facing orientation details 2026-03-28 16:45:57 -07:00
Kelsi
4ff59c6f76 debug: log castSpell calls and SMSG_CAST_RESULT at WARNING level 2026-03-28 16:41:15 -07:00
Kelsi
1aec1c6cf1 fix: send heartbeat after SET_FACING before cast to ensure server has orientation 2026-03-28 16:27:26 -07:00
Kelsi
9cb6c596d5 fix: face target before casting any targeted spell (not just melee)
Only melee abilities sent MSG_MOVE_SET_FACING before the cast packet.
Ranged spells like Smite used whatever orientation was in movementInfo
from the last movement, causing "target not in front" server rejection.

Now sends a facing update toward the target entity before ANY targeted
spell cast. The server checks a ~180° frontal arc for most spells.
2026-03-28 16:23:27 -07:00
Kelsi
c58537e2b8 fix: load binary DBCs from Data/db/ fallback path
CreatureDisplayInfo.dbc (691KB, 24K+ entries) exists at Data/db/ but
the loader only checked DBFilesClient\ (MPQ manifest) and expansion CSV.
The CSV had only 13248 entries (malformed export), so TBC+ creatures
(Mana Wyrms, Blood Elf area) had no display data and were invisible.

Now checks Data/db/ as a fallback for binary DBCs. This path contains
pre-extracted DBCs shared across expansions. Binary DBCs have complete
record data including proper IDs.
2026-03-28 16:17:59 -07:00
Kelsi
d8c768701d fix: visible item field base 284→283 (confirmed by raw field dump)
RAW FIELDS dump shows item entries at odd indices: 283, 285, 287, 289...
With base=283, stride=2: 17 of 19 slots have valid item IDs (14200,
12020, 14378, etc). Slots 12-13 (trinkets) correctly empty.

With base=284: only 5 entries, and values are enchant IDs (913, 905, 904)
— these are the field AFTER each entry, confirming base was off by 1.
2026-03-28 16:12:31 -07:00
Kelsi
fdca990209 debug: dump raw fields for first 3 players (lowered threshold to size>20) 2026-03-28 16:06:03 -07:00
Kelsi
f74b79f1f8 debug: log outgoing heartbeat coords and chat, fix BG filter for SAY type
Heartbeat: log canonical + wire coords every 30th heartbeat to detect
if we're sending wrong position (causing server to teleport us).

Chat: log outgoing messages at WARNING level to confirm packets are sent.

BG filter: announcer uses SAY (type=0) with color codes, not SYSTEM.
Match "BG Queue Announcer" in message body regardless of chat type.
2026-03-28 16:02:36 -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
b1e2b8866d fix: use faction-correct language for outgoing chat (COMMON vs ORCISH)
Chat was always sent with COMMON (7) language. For Horde players,
AzerothCore rejects COMMON and silently drops the message. Alliance
players nearby also couldn't see Horde messages.

Now detects player race and sends ORCISH (1) for Horde races, COMMON (7)
for Alliance. This matches what the real WoW client sends.
2026-03-28 15:42:01 -07:00
Kelsi
2f96bda6fa fix: far same-map teleport blocked packet handler for 41 seconds
loadOnlineWorldTerrain() was called directly from the worldEntryCallback
inside the packet handler, running the 20s warmup loop synchronously.
This blocked ALL packet processing and froze the game for 20-41 seconds.

Now defers the world reload to pendingWorldEntry_ which is processed on
the next frame, outside the packet handler. Position and camera snap
immediately so the player doesn't drift at the old location.

The /y respawn report was actually a server-initiated teleport (possibly
anti-spam or area trigger) that hit this 41-second blocking path.
2026-03-28 15:38:44 -07:00
Kelsi
9666b871f8 fix: BG announcer filter was suppressing ALL chat messages
The filter matched ALL chat types for patterns like "[H:" + "A:" which
are common in normal messages. Any SAY/WHISPER/GUILD message containing
both substrings was silently dropped. This broke all incoming chat.

Now only filters SYSTEM messages and only matches specific BG announcer
keywords: "Queue status", "BG Queue", "BGAnnouncer".
2026-03-28 15:31:16 -07:00
Kelsi
6edcad421b fix: group invite popup never showing (hasPendingGroupInvite stale getter)
hasPendingGroupInvite() and getPendingInviterName() were inline getters
reading GameHandler's stale copies. SocialHandler owns the canonical
pendingGroupInvite/pendingInviterName state. Players were auto-added to
groups without seeing the accept/decline popup.

Now delegates to socialHandler_.
2026-03-28 15:29:19 -07:00
Kelsi
1af1c66b04 fix: SMSG_TRADE_STATUS_EXTENDED format (whichPlayer is uint32, add missing fields)
WotLK trade packet format was wrong in multiple ways:
- whichPlayer was read as uint8, actually uint32
- Missing tradeId field (we read tradeId as tradeCount)
- Per-slot size was 52 bytes, actually 64 (missing suffixFactor,
  randomPropertyId, lockId = 12 bytes)
- tradeCount is 8 (7 trade + 1 "will not be traded"), not capped at 7

Verified: header(4+4=8) + 8×(1+64=65) + gold(4) = 532 bytes matches
the observed packet size exactly.

Note: Classic trade format differs and will need its own parser.
2026-03-28 15:24:26 -07:00
Kelsi
df9dad952d fix: handle TRADE_STATUS_UNACCEPT (status=8), revert to Open state 2026-03-28 15:21:32 -07:00
Kelsi
ce54b196e7 fix: trade COMPLETE resets state before EXTENDED can populate items/gold
SMSG_TRADE_STATUS(COMPLETE) and SMSG_TRADE_STATUS_EXTENDED arrive in the
same packet batch. COMPLETE was calling resetTradeState() which cleared
all trade slots and gold BEFORE EXTENDED could write the final data.
The trade window showed "7c" (garbage gold) because the gold field read
from the wrong offset (slot size was also wrong: 60→52 bytes).

Now COMPLETE just sets status to None without full reset, preserving
trade state for EXTENDED to populate. The TRADE_CLOSED addon event
still fires correctly.
2026-03-28 15:18:33 -07:00
Kelsi
ed7cbccceb fix: trade slot size check 60→52 bytes, add trade diagnostic logging 2026-03-28 15:14:53 -07:00
Kelsi
615db79819 fix: skip all-zero equipment emit, broaden BG announcer filter
Equipment: the first emitOtherPlayerEquipment call fired before any item
queries returned, sending all-zero displayIds that stripped players naked.
Now skips the callback when resolved=0 (waiting for queries). Equipment
only applies once at least one item resolves, preventing the naked flash.

BG announcer: broadened filter to match ALL chat types (not just SYSTEM),
and added more patterns: "BGAnnouncer", "[H: N, A: N]" with spaces.

Also added diagnostic logging in setOnlinePlayerEquipment to trace
displayId counts reaching the renderer.
2026-03-28 15:09:52 -07:00
Kelsi
12f5aaf286 fix: filter BG queue announcer spam from system chat
ChromieCraft/AzerothCore BG queue announcer module floods chat with
SYSTEM messages like "Queue status for Alterac Valley [H: 12/40, A: 15/40]".
Now filtered by detecting common patterns: "Queue status", "BG Queue",
"Announcer]", and "[H:...A:..." format.

Equipment status: resolved items ARE rendering (head, shoulders, chest,
legs confirmed with displayIds). Remaining unresolved slots (weapons)
are item queries the server hasn't responded to yet — timing issue,
not a client bug. Items trickle in over ~5 seconds as queries return.
2026-03-28 15:01:25 -07:00
Kelsi
11571c582b fix: hearthstone from action bar, far teleport loading screen
Action bar hearthstone: the slot was type SPELL (spell 8690) not ITEM.
castSpell sends CMSG_CAST_SPELL which the server rejects for item-use
spells. Now detects item-use spells via getItemIdForSpell() and routes
through useItemById() instead, sending CMSG_USE_ITEM correctly.

Far same-map teleport: hearthstone on the same continent (e.g., Westfall
→ Stormwind on Azeroth) skipped the loading screen, so the player fell
through unloaded terrain. Now triggers a full world reload with loading
screen for teleports > 500 units, with the warmup ground check ensuring
WMO floors are loaded before spawning.
2026-03-28 14:55:58 -07:00
Kelsi
4e709692f1 fix: filter officer chat for non-officers (server sends to all guild members)
Some private servers (AzerothCore/ChromieCraft) send OFFICER chat type
to all guild members regardless of rank. The real WoW client checks the
GR_RIGHT_OFFCHATLISTEN (0x80) guild rank permission before displaying.

Now checks the player's guild rank rights from the roster data and
suppresses officer chat if the permission bit is not set.
2026-03-28 14:45:51 -07:00
Kelsi
21fb2aa11c fix: backpack window jumps position when selling items (missing ##id in title) 2026-03-28 14:42:21 -07:00
Kelsi
504d112625 fix: gossip/vendor windows not closing when opening mailbox/trainer/taxi
Domain handlers were setting `owner_.gossipWindowOpen = false` directly
on GameHandler's stale member, but isGossipWindowOpen() delegates to
QuestHandler's copy. The gossip window stayed open because the
delegating getter never saw the close.

Fix: use owner_.closeGossip() / owner_.closeVendor() which properly
delegate to QuestHandler/InventoryHandler to close the canonical state.

Affected: InventoryHandler (3 sites: mail, trainer, bank opening),
MovementHandler (1 site: taxi opening), QuestHandler (2 sites: gossip
opening closes vendor).
2026-03-28 14:36:14 -07:00
Kelsi
e5959dceb5 fix: add bare-points spline fallback for flying/falling splines (0x10000) 2026-03-28 12:47:37 -07:00
Kelsi
47bea0d233 fix: use delegating getters for vendor buyback refresh (stale member read) 2026-03-28 12:45:59 -07:00
Kelsi
b81c616785 fix: delegate gossip/quest detail getters to QuestHandler (NPC dialog broken)
4 more stale getters from PR #23 split:
- isGossipWindowOpen() — QuestHandler owns gossipWindowOpen_
- getCurrentGossip() — QuestHandler owns currentGossip_
- isQuestDetailsOpen() — QuestHandler owns questDetailsOpen_
- getQuestDetails() — QuestHandler owns currentQuestDetails_

Also fix GameHandler::update() distance-close checks to use delegating
getters instead of stale member variables for vendor/gossip/taxi/trainer.

Map state (currentMapId_, worldStateZoneId_, exploredZones_) confirmed
NOT stale — domain handlers write via owner_. reference to GameHandler's
members. Those getters are correct as-is.
2026-03-28 12:43:44 -07:00
Kelsi
ee02faa183 fix: delegate all 113 stale GameHandler getters to domain handlers
PR #23 split GameHandler into 8 domain handlers but left 113 inline
getters reading stale duplicate member variables. Every feature that
relied on these getters was silently broken (showing empty/stale data):

InventoryHandler (32): bank, mail, auction house, guild bank, trainer,
  loot rolls, vendor, buyback, item text, master loot candidates
SocialHandler (43): guild roster, battlegrounds, LFG, duels, petitions,
  arena teams, instance lockouts, ready check, who results, played time
SpellHandler (10): talents, craft queue, GCD, pet unlearn, queued spell
QuestHandler (13): quest log, gossip POIs, quest offer/request windows,
  tracked quests, shared quests, NPC quest statuses
MovementHandler (15): all 8 server speeds, taxi state, taxi nodes/data

All converted from inline `{ return member_; }` to out-of-line
delegations: `return handler_ ? handler_->getter() : fallback;`
2026-03-28 12:18:14 -07:00
Kelsi
d6b387ae35 fix: increase tile-count fallback from 10s to 20s to prevent premature spawn 2026-03-28 12:04:54 -07:00
Kelsi
2633a490eb fix: remove reinterpret_cast UB in trade slot delegation
The TradeSlot structs differ between GameHandler (has bag/slot fields)
and InventoryHandler (no bag/slot). The reinterpret_cast was undefined
behavior that corrupted memory, potentially causing the teleport bug.

Now properly copies fields between the two struct layouts.

NOTE: 113 stale getters remain in GameHandler that read duplicate member
variables never updated by domain handlers. These need systematic fixing.
2026-03-28 12:02:08 -07:00
Kelsi
f37994cc1b fix: trade accept dialog not showing (stale state from domain handler split)
GameHandler::hasPendingTradeRequest() and all trade getters were reading
GameHandler's own tradeStatus_/tradeSlots_ which are never written after
the PR #23 split. InventoryHandler owns the canonical trade state.

Delegate all trade getters to InventoryHandler:
- getTradeStatus, hasPendingTradeRequest, isTradeOpen, getTradePeerName
- getMyTradeSlots, getPeerTradeSlots, getMyTradeGold, getPeerTradeGold

Also fix InventoryHandler::isTradeOpen() to include Accepted state.
2026-03-28 11:58:02 -07:00
Kelsi
99ac31987f fix: add missing <algorithm> include for std::clamp (Windows build) 2026-03-28 11:51:34 -07:00
Kelsi
b9ac3de498 tweak: camera defaults stiffness=30, pivot height=1.6 2026-03-28 11:45:21 -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
Kelsi
5a8ab87a78 fix: warmup checks WMO floor proximity, not just terrain existence
Stormwind players stand on WMO floors ~95m above terrain. The previous
check only tested if terrain existed at the spawn XY (it did — far below).
Now checks WMO floor first, then terrain, requiring the ground to be within
15 units of spawn Z. Falls back to tile count after 10s.

Also adds diagnostic logging for useItemBySlot (hearthstone debug).
2026-03-28 11:34:07 -07:00
Kelsi
8aaa2e7ff3 fix: warmup checks WMO floor + terrain + tile count before spawning
Stormwind players stand on WMO floors, not terrain. The terrain-only
check passed immediately (terrain exists below the city) but the WMO
floor hadn't loaded yet, so the player fell through.

Now checks three ground sources in order:
1. Terrain height at spawn point
2. WMO floor height at spawn point (for cities/buildings)
3. After 8s, accepts if 4+ terrain tiles are loaded (fallback)

Won't exit warmup until at least one ground source returns valid height,
or the 25s hard cap is reached.
2026-03-28 11:23:18 -07:00
Kelsi
7b26938e45 fix: warmup terrain check uses server spawn coords, not character position
The terrain readiness check was using getCharacterPosition() which is
(0,0,0) during warmup — always returned a valid height and exited
immediately, causing the player to spawn before terrain loaded.

Now uses the server-provided spawn coordinates (x,y,z from world entry)
converted to render coords for the terrain query. Also logs when terrain
isn't ready after 5 seconds to show warmup progress.

Player spawn callbacks and equipment re-emit chain confirmed working.
2026-03-28 11:18:36 -07:00
Kelsi
ada95756ce fix: don't exit warmup until terrain under player is loaded
Added terrain readiness check to the warmup exit condition: the loading
screen won't drop until getHeightAt(playerPos) returns a valid height,
ensuring the ground exists under the player's feet before spawning.

Also increased warmup hard cap from 15s to 25s to give terrain more time
to load in cities like Stormwind with dense WMO/M2 assets.

Equipment re-emit chain confirmed working: items resolve 3-4 seconds
after spawn and equipment is re-applied with valid displayIds.
2026-03-28 11:09:36 -07:00
Kelsi
15f6aaadb2 fix: revert stride to 2 (correct for WotLK visible items), add re-emit tracing
Stride 4 was wrong — the raw dump shows entries at 284, 288, 292 which
are slots 0, 2, 4 with stride 2 (slot 1=NECK is zero because necks are
invisible). Stride 2 with base 284 correctly maps 19 equipment slots.

Added WARNING-level log when item query responses trigger equipment
re-emit for other players, to confirm the re-emit chain works.

The falling-through-world issue is likely terrain chunks not loading
fast enough — the terrain streaming stalls are still present.
2026-03-28 11:07:17 -07:00
Kelsi
05ab9922c4 fix: visible item stride 2→4 (confirmed from raw field dump)
RAW FIELDS dump shows equipment entries at indices 284, 288, 292, 296, 300
— stride 4, not 2. Each visible item slot occupies 4 fields (entry +
enchant + 2 padding), not 2 as previously assumed.

Field dump evidence:
  [284]=3817(Reinforced Buckler) [288]=3808(Double Mail Boots)
  [292]=3252 [296]=3823 [300]=3845 [312]=3825 [314]=3827

With stride 2, slots 0-18 read indices 284,286,288,290... which interleaves
entries with enchant/padding values, producing mostly zeros for equipment.
With stride 4, slots correctly map to entry-only fields.
2026-03-28 11:04:29 -07:00
Kelsi
f70beba07c fix: visible item field base 408→286 (computed from INV_SLOT_HEAD=324)
PLAYER_VISIBLE_ITEM_1_ENTRYID = PLAYER_FIELD_INV_SLOT_HEAD(324) - 19*2
= 286. The previous value of 408 landed far past inventory slots in
string/name data, producing garbage entry IDs (ASCII fragments like
"mant", "alk ", "ryan") that the server rejected as invalid items.

Derivation: 19 visible item slots × 2 fields (entry + enchant) = 38
fields immediately before PLAYER_FIELD_INV_SLOT_HEAD at index 324.
2026-03-28 10:58:34 -07:00
Kelsi
37300d65ce fix: remove Classic spline fallback, add no-parabolic WotLK variant
The Classic fallback silently succeeded on WotLK data by false-positive
matching, consuming wrong bytes and producing corrupt entity data that
was silently dropped — resulting in zero other players/NPCs visible.

Now tries 4 WotLK-only variants in order:
1. Full WotLK (durationMod+durationModNext+vertAccel+effectStart+compressed)
2. Full WotLK uncompressed
3. WotLK without parabolic fields (durationMod+durationModNext+points)
4. WotLK without parabolic, compressed

This covers servers that don't unconditionally send vertAccel+effectStart
(the MEMORY.md says AzerothCore does, but other cores may not).
2026-03-28 10:55:46 -07:00
Kelsi
559f100204 fix: restore Classic spline fallback to prevent UPDATE_OBJECT packet loss
The previous fix (b8a9efb7) that returned false on spline failure was too
aggressive — it aborted the ENTIRE UPDATE_OBJECT packet, not just one
block. Since many entity spawns (NPCs, other players) share the same
packet, a single spline parse failure killed ALL entities in the batch.

Restored the Classic-format fallback as a last resort after WotLK format
fails. The key difference from the original bug is that WotLK is now
tried FIRST (with proper position save/restore), and Classic only fires
if WotLK fails. This prevents the false-positive match that originally
caused corruption while still handling edge-case spline formats.
2026-03-28 10:52:26 -07:00
Kelsi
f4a2a631ab fix: visible item field base 284→408 (was reading quest log, not equipment)
PLAYER_VISIBLE_ITEM_1_ENTRYID for WotLK 3.3.5a is at UNIT_END(148) + 260
= field index 408 with stride 2. The previous default of 284 (UNIT_END+136)
was in the quest log field range, causing item IDs like "Lesser Invisibility
Potion" and "Deathstalker Report" to be read as equipment entries.

This was the root cause of other players appearing naked — item queries
returned valid responses but for the WRONG items (quest log entries instead
of equipment), so displayInfoIds were consumable/quest item appearances.

The heuristic auto-detection still overrides for Classic/TBC (different
stride per expansion), so this only affects the WotLK default before
detection runs.

Also filter addon whispers (GearScore GS_*, DBM, oRA, BigWigs, tab-prefixed)
from chat display — these are invisible in the real WoW client.
2026-03-28 10:49:00 -07:00
Kelsi
1bcb05aac4 fix: only show fishing message for player's own bobber, not others'
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
SMSG_GAMEOBJECT_CUSTOM_ANIM with animId=0 on a fishing node (type 17)
was triggering "A fish is on your line!" for ALL fishing bobbers in
range, including other players'. Now checks OBJECT_FIELD_CREATED_BY
(fields 6-7) matches the local player GUID before showing the message.
2026-03-28 10:35:53 -07:00
Kelsi
b8a9efb721 fix: abort movement block on spline parse failure instead of corrupting stream
When both WotLK compressed and uncompressed spline point parsing fail,
the parser was silently continuing with a corrupted read position (16
bytes of WotLK spline header already consumed). This caused the update
mask to read garbage (maskBlockCount=40), corrupting the current entity
AND all remaining blocks in the same UPDATE_OBJECT packet.

Now returns false on spline failure, cleanly aborting the single block
parse and allowing the remaining blocks to be recovered (if the parser
can resync). Also logs the failing GUID and spline flags for debugging.

This fixes:
- Entities spawning with displayId=0/entry=0 (corrupted parse)
- "Unknown update type: 128" errors from reading garbage
- Falling through the ground (terrain entities lost in corrupted batch)
- Phantom "fish on your line" from fishing bobber entity parse failure
2026-03-28 10:31:53 -07:00
Kelsi
ed8ff5c8ac fix: increase packet parse/callback budgets to fix Warden module stall
Warden module download (18756 bytes, 38 chunks of 500 bytes) stalled at
32 chunks because the per-pump packet parse budget was 16 — after two
2ms pump cycles (32 packets), the TCP receive buffer filled and the
server stopped sending. Character list never arrived.

- kDefaultMaxParsedPacketsPerUpdate: 16 → 64
- kDefaultMaxPacketCallbacksPerUpdate: 6 → 48

Also adds WARNING-level diagnostic logs for auth pipeline packets and
Warden module download progress (previously DEBUG-only, invisible in
production logs).
2026-03-28 10:28:20 -07:00