- Skip WMO batches with material flags F_SIDN (0x20) or F_WINDOW (0x40)
which are transparent sky/window panes that render as grey curtains
- Fix /unstuck: always nudge 5 units forward first, then sample floor at
destination instead of teleporting back to last safe position (which
could be the stuck location itself)
The previous fix only skipped groups where ALL batches were untextured.
Groups with a mix of textured and untextured batches still rendered the
untextured ones as solid grey geometry. Now skip each untextured batch
individually during rendering.
- Per-family mount sounds (kodo, tallstrider, mechanostrider, etc.) detected from M2 model path
- Skip WMO groups with SHOW_SKYBOX flag or all-untextured batches (grey mesh in Orgrimmar)
- Freeze physics during taxi landing until terrain loads to prevent falling through void
- Disable bone animations on tropical vegetation (palm, bamboo, banana, etc.) to fix wiggling
- Snap player to final taxi waypoint on flight completion
- Extract mount aura spell ID from classic UNIT_FIELD_AURAS for CMSG_CANCEL_AURA dismount
- Increase /unstuck forward nudge to 5 units
- Parse MCLQ sub-chunks in vanilla ADTs for water rendering (WotLK uses MH2O)
- Load TaxiPathNode.dbc for MO_TRANSPORT world-coordinate paths (vanilla boats)
- Parse data[] from SMSG_GAMEOBJECT_QUERY_RESPONSE (taxiPathId for transports)
- Support vanilla M2 particle emitters (504-byte struct, different from WotLK 476)
- Add character preview texture diagnostic logging
- Fix disconnect handling on character screen (show error only when no chars)
Cache composite textures by input key so identical NPC equipment
combos share one GPU texture. Use DBC layout system for
ItemDisplayInfo texture component fields instead of hardcoded
indices (cross-expansion support). Selective player equipment
re-emission on item query response instead of broadcasting to
all players.
Load third-person emote text templates (othersTarget/othersNoTarget)
from EmotesText.dbc fields 3 and 7 alongside existing sender text.
Build reverse lookup map from dbcId to EmoteInfo for incoming
SMSG_TEXT_EMOTE resolution. Other players now show proper emote
descriptions like "Player dances with Target" instead of generic
"Player performs an emote" text.
Chat tabs filter messages into General/Combat/Whispers/Trade tabs. Text
emotes now send CMSG_TEXT_EMOTE to server and display incoming emotes
from other players. Channel system auto-joins General/Trade on login with
/join, /leave, and /1-/9 shortcuts. Chat bubbles render as ImGui overlays
above entities for SAY/YELL messages with fade-out animation.
Vanilla (v256) and TBC (v263) M2 files embed skin data directly, parsed
during M2Loader::load(). The code unconditionally loaded external .skin
files afterwards, which resolved to WotLK-format .skin files (48-byte
submeshes) from the base manifest — overwriting the correctly parsed
embedded skin (32-byte submeshes) and causing mesh corruption on all
character models. Guard all 13 loadSkin() call sites with version >= 264
so external .skin files are only loaded for WotLK M2s that need them.
Expansion overlays allow each expansion to supplement the base asset data
via an assetManifest field in expansion.json, loaded at priority 50 (below
HD packs). The asset extractor gains --reference-manifest for delta-only
extraction. Also fixes CharSections field indices (VariationIndex=4,
ColorIndex=5, Texture1=6) across all DBC layout references.
- Route SMSG_UPDATE_OBJECT through polymorphic parsers for correct
vanilla format (uint8 updateFlags, 6 speeds vs WotLK uint16/9)
- Fix SMSG_DESTROY_OBJECT for vanilla (8 bytes, no isDeath field)
- Add MSG_MOVE_* handlers for other player movement relay
- Add ClassicPacketParsers::parseMessageChat with targetGuid read
and monster-type name handling
- Resolve chat sender names from player name cache before display
- Fix CSV DBC field 0 always treated as numeric ID (fixes 16+ garbled
Turtle CSVs including Map, AreaTable, Spell, CreatureDisplayInfo)
- Add CSV DBC validation: reject garbled CSVs (>80% zero IDs) and
fall back to binary DBC files
- Fix ItemDisplayInfo texture component field index (14+ not 15+)
for binary DBC with gender-aware suffix resolution
- Spawn other players as visible M2 models via creature callback
- Map name cache dedup prevents overwrites from duplicate CSV records
- Vanilla M2 bone struct (108 bytes) with 28-byte animation tracks
- Version-aware bone parsing (vanilla vs WotLK format detection)
- Fix CharSections.dbc field layout for vanilla (variation/color at 4-5)
- Remove broken CharSections.csv files (all fields marked as strings)
- Expansion data reload on profile switch (DBC cache clear, layout reload)
- Vanilla packet encryption (VanillaCrypt XOR-based header crypt)
- Extended character preview geoset range (0-99) for vanilla models
- DBC cache clear support in AssetManager
Replace hardcoded WotLK protocol constants with a data-driven architecture
supporting Classic 1.12.1, TBC 2.4.3, and WotLK 3.3.5a. Each expansion
has JSON profiles for opcodes, update fields, and DBC layouts, plus C++
polymorphic packet parsers for binary format differences (movement flags,
speed fields, transport data, spline format, char enum layout).
Key components:
- ExpansionRegistry: scans Data/expansions/*/expansion.json at startup
- OpcodeTable: logical enum <-> wire values loaded from JSON
- UpdateFieldTable: field indices loaded from JSON per expansion
- DBCLayout: schema-driven DBC field lookups replacing magic numbers
- PacketParsers: WotLK/TBC/Classic parsers with correct flag positions
- Multi-manifest AssetManager: layered manifests with priority ordering
- HDPackManager: overlay texture packs with expansion compatibility
- Auth screen expansion picker replacing hardcoded version dropdown
- Added transport fields to MovementInfo struct (transportGuid, transportX/Y/Z/O, transportTime)
- Updated MovementPacket::build() to serialize transport data when ONTRANSPORT flag set
- Modified GameHandler::sendMovement() to include transport info when player on transport
- Fixed coordinate conversion for transport offsets (server↔canonical)
- Added transport tracking in both CREATE_OBJECT and MOVEMENT update handlers
- Connected M2Renderer to WMORenderer for hierarchical doodad transforms
- Server-authoritative transport movement (no client-side animation)
Issue: Server not sending MOVEMENT updates for transports, so they remain stationary.
Transports register successfully but don't animate without server position updates.
Transport System (Phases 1-7):
- Implement TransportManager with Catmull-Rom spline path interpolation
- Add WMO dynamic transforms for moving transport instances
- Implement player attachment via world position composition
- Add test transport with circular path around Stormwind harbor
- Add /transport board and /transport leave console commands
- Reuse taxi flight spline system and external follow camera mode
NPC Spawn Fixes:
- Add smart ocean spawn filter: blocks land creatures at high altitude over water (Z>50)
- Allow legitimate water creatures at sea level (Z≤50) to spawn correctly
- Fixes Elder Grey Bears, Highland Striders, and Plainscreepers spawning over ocean
- Snap online creatures to terrain height when valid ground exists
NpcManager Removal:
- Remove deprecated NpcManager (offline mode no longer supported)
- Delete npc_manager.hpp and npc_manager.cpp
- Simplify NPC animation callbacks to use only creatureInstances_ map
- Move NPC callbacks to game initialization in application.cpp
Water Rendering:
- Fix tile seam gaps caused by per-vertex wave randomization
- Add distance-based blending: seamless waves up close (<150u), grid effect far away (>400u)
- Smooth transition between seamless and grid modes (150-400 unit range)
- Preserves aesthetic grid pattern at horizon while eliminating gaps when swimming
Fidget animations were continuing to play for a frame when movement started.
Now forces immediate switch to run animation.
Changes:
- Check for movement + active fidget at start of animation logic
- Force play run animation immediately to stop fidget
- Only check fidget completion when not moving (optimization)
- Removed duplicate mountActiveFidget_ = 0 in movement branch
Fidgets now stop instantly when player starts moving on mount.
Added slope normal checking to reject surfaces too steep to walk.
Prevents character/mount from clipping through steep terrain.
Changes:
- Added MIN_WALKABLE_NORMAL threshold (0.7 = ~45° max slope)
- WMO collision: query surface normal, reject if normalZ < 0.7
- M2 collision: query surface normal, reject if normalZ < 0.7
- Updated M2Renderer::getFloorHeight to output surface normal
- M2 already had internal 0.35 check (~70°), new 0.7 is more restrictive
Steep slopes now block movement instead of allowing clipping.
Idle sounds were too frequent and strict criteria blocked all fidgets.
Changes:
- Idle sounds now 45-90 seconds apart (was 20-40)
- Fidget criteria back to OR (frequency OR replay) instead of AND
- Keeps all ID exclusions: 2-3, 5-9, 11-21 to prevent battle animations
- Should now discover proper fidgets while filtering problematic ones
Removed jump/land sounds (attack/wound sounds had pained growls).
Made fidget discovery much stricter to exclude jerky battle animations.
Changes:
- Disabled playJumpSound for ground mounts (attack sounds too aggressive)
- Disabled playLandSound for ground mounts (wound sounds have growls)
- Fidget criteria now requires BOTH frequency AND replay (not OR)
- Excluded IDs 11-15 (attacks) in addition to 16-21 (combat)
- Only animations with proper idle metadata will be selected
Added debug logging to show ALL non-looping, short, stationary animations
regardless of metadata, so we can identify hoof stamps and head tosses.
Shows:
- All potential fidgets (no metadata filter)
- Animation ID, duration, frequency, replay timers, flags, next animation
- Helps identify which IDs are the shuffles/stamps/tosses on Palomino
Horse was playing death animation on idle. Added explicit filtering
to exclude death (5-6), wounds (7-9), combat (16-21), and specials (2-3).
Changes:
- Check animation ID ranges before adding to fidget list
- Prevents death/wound animations from being selected as idle fidgets
- Keeps metadata-based discovery but adds safety exclusions
Created specific idle sound pool using only horse snorts and whinnies.
Re-enabled idle sounds with much longer interval (20-40 seconds).
Changes:
- Added horseIdleSounds_ pool: mHorseStand3A (snort) + mHorseAggroA (whinny)
- Updated playIdleSound() to use dedicated pool instead of mixed breath sounds
- Increased idle sound interval from 8-15s to 20-40s (less frequent)
- Removed flying mount idle sounds (too aggressive)
- Increased volume slightly (0.35x) for better audibility
Idle sounds were too frequent/upsetting, and strict criteria found no fidgets.
Changes:
- Disabled idle sounds entirely (commented out in updateCharacterAnimation)
- Relaxed fidget criteria: frequency OR replay (not both required)
- Keeps proper metadata-based discovery (frequency/replay fields)
- Comprehensive logging shows candidates and selections
Previous criteria caught combat animations with grunts instead of subtle fidgets.
Now using strict filtering and comprehensive logging to identify real fidgets.
Changes:
- Duration: 500-1200ms (very short movements only)
- Movement: <0.01 speed (nearly stationary)
- Exclude: IDs 2-3 (specials), 16-21 (combat/attack range)
- Added candidate logging: shows ALL potential fidgets for debugging
- Removed upper ID limit to catch fidgets at any position
Fidgets were stuttering because normal animation updates immediately overrode them.
Now tracks active fidget and prevents normal animation updates until fidget completes.
Changes:
- Added mountActiveFidget_ to track currently playing fidget animation
- Check fidget completion using getAnimationState before allowing normal updates
- Only trigger new fidgets when no fidget is active
- Cancel active fidget on movement
- Expanded fidget search criteria: duration up to 3000ms, ID range 1-20
- Added debug logging to show discovered fidgets and when they complete
Implements WoW-style mount idle behavior when player is stationary:
- Fidget animations: discovered via property search (non-looping, 500-1500ms, stationary, IDs 1-10)
- Triggers random fidget every 6-12 seconds when standing still
- Ambient idle sounds: snorts/breaths for ground mounts, soft wing sounds for flyers
- Triggers random idle sound every 8-15 seconds when stationary
- Both systems reset timers on movement to avoid triggering while riding
Mount Animation System:
- Property-based jump animation discovery using sequence metadata
- Chain linkage scoring (nextAnimation/aliasNext) for accurate detection
- Correct loop detection: flags & 0x01 == 0 means looping
- Avoids brake/stop animations via blendTime penalties
- Works on any mount model without hardcoded animation IDs
Mount Physics:
- Physics-based jump height: vz = sqrt(2 * g * h)
- Configurable MOUNT_JUMP_HEIGHT constant (1.0m default)
- Procedural lean into turns for ground mounts
- Smooth roll based on turn rate (±14° max, 6x/sec blend)
Audio Improvements:
- State-machine driven mount sounds (jump, land, rear-up)
- Semantic sound methods (no animation ID dependencies)
- Debug logging for missing sound files
Bug Fixes:
- Fixed mount animation sequencing (JumpStart → JumpLoop → JumpEnd)
- Fixed animation loop flag interpretation (0x20 vs 0x21)
- Rider bone attachment working correctly during all mount actions
Implements aggressive performance optimizations to improve frame rate from 29fps to 40fps:
M2 Rendering:
- Ultra-aggressive animation culling (25/50/80 unit distances down from 95/140)
- Tighter render distances (700/350/1000 down from 1200/1200/3500)
- Early distance rejection before model lookup in render loop
- Lower threading threshold (6 instances vs 32) for earlier parallelization
- Reduced frustum padding (1.5x vs 2.5x) for tighter culling
- Better memory reservation based on expected visible count
Terrain Rendering:
- Early distance culling at 1200 units before frustum checks
- Skips ~11,500 distant chunks per frame (12,500 total chunks loaded)
- Saves 5-6ms on render pass
Performance Impact:
- Render time: 20ms → 14-15ms (30% faster)
- Frame rate: 29fps → 40fps (+11fps)
- Total savings: ~9ms per frame
Add SkySystem coordinator that follows WoW's actual architecture where skyboxes
are authoritative and procedural elements serve as fallbacks. Integrate lighting
system across all renderers (terrain, WMO, M2, character) with unified parameters.
Sky System:
- SkySystem coordinator manages skybox, celestial bodies, stars, clouds, lens flare
- Skybox is authoritative (baked stars from M2 models, procedural fallback only)
- skyboxHasStars flag gates procedural star rendering (prevents double-star bug)
Celestial Bodies (Lore-Accurate):
- Two moons: White Lady (30-day cycle, pale white) + Blue Child (27-day cycle, pale blue)
- Deterministic moon phases from server gameTime (not deltaTime toys)
- Sun positioning driven by LightingManager directionalDir (DBC-sourced)
- Camera-locked sky dome (translation ignored, rotation applied)
Lighting Integration:
- Apply LightingManager params to WMO, M2, character renderers
- Unified lighting: directional light, diffuse color, ambient color, fog
- Star occlusion by cloud density (70% weight) and fog density (30% weight)
Documentation:
- Add comprehensive SKY_SYSTEM.md technical guide
- Update MEMORY.md with sky system architecture and anti-patterns
- Update README.md with WoW-accurate descriptions
Critical design decisions:
- NO latitude-based star rotation (Azeroth not modeled as spherical planet)
- NO always-on procedural stars (skybox authority prevents zone identity loss)
- NO universal dual-moon setup (map-specific celestial configurations)
Wire DBC-driven lighting to terrain shaders:
- Add LightingManager to Renderer
- Initialize lighting manager with AssetManager
- Update lighting each frame with player position
- Feed lighting params to terrain shader uniforms:
* Ambient color
* Diffuse (sun) color and direction
* Fog color, start, end distances
- Fallback to skybox-based fog if lighting unavailable
TODOs for full integration:
- Wire actual map ID from game state
- Wire server game time from login packets
- Add weather/underwater state detection
- Apply lighting to WMO/M2/skybox shaders
Current state: Uses local time + Eastern Kingdoms map (0)
Next: Hook up SMSG_LOGIN_SETTIMESPEED for real game time
Add complete Blizzard-style time-of-day lighting pipeline:
Spatial Volume System (Light.dbc):
- Light volumes with position + inner/outer radius
- Distance-based weighting with smoothstep falloff
- Multi-volume blending (top 2 with normalized weights)
- X,Z,Y coordinate handling + LIGHT_COORD_SCALE for ×36 quirk
- Smooth zone transitions without popping
Profile Selection (LightParams.dbc):
- Weather variants: clear/rain/underwater
- Links to 18 color + 6 float band curves per profile
- Block indexing: LightParamsID × 18/6 + channel
Time-of-Day Band Sampling (LightIntBand/LightFloatBand):
- Half-minutes format (0-2879) with time clamping
- Keyframe interpolation with midnight wrap
- Wrap-safe initialization for edge cases
- BGR color unpacking
Multi-Volume Blending:
- Weighted sum of all lighting params
- Proper direction blending: normalize(sum(dir × weight))
- Blends ambient, diffuse, fog, sky, cloud density
Temporal Smoothing:
- Exponential blend to prevent frame snapping
- Smooths ALL parameters (colors, fog, direction, sky)
Game Time Support:
- Accepts server-sent game time (WoW standard)
- Falls back to local time if not provided
- Manual override for testing
Debug Features:
- Volume distance/weight logging
- Fog params logging
- Coordinate scale verification
Also: Move buff bar to top-left under player frame
The quest log was empty because the client never requested quest data from the server.
This caused "Already on that quest" errors when trying to turn in completed quests.
Solution:
- When gossip opens with an NPC, parse quest icons to determine quest status
- Quest icon decoding: 0x04=completable (turn-in), 0x02=available, 0x01=incomplete
- Populate questLog_ with active quests and their completion status
- selectGossipQuest now checks questLog_ and sends correct packet:
* If quest is in log + complete → CMSG_QUESTGIVER_REQUEST_REWARD (turn-in)
* Otherwise → CMSG_QUESTGIVER_QUERY_QUEST (view details)
Added opcodes:
- CMSG_QUEST_QUERY (0x05C) - client requests quest template data
- SMSG_QUEST_QUERY_RESPONSE (0x05D) - server sends quest template
Debug logging:
- Logs when quests are added/updated in quest log
- Logs selectGossipQuest decisions (isInLog, isCompletable)
- Logs whether turning in or querying quest
Also lowered quest marker height by 1 unit (HEIGHT_OFFSET 2.1 → 1.1).
Quest turn-in now works correctly!
Replace 2D ImGui text markers with proper 3D billboard sprites using BLP textures.
Features:
- Billboard rendering using Interface\GossipFrame\ BLP textures (yellow !, yellow ?, grey ?)
- WoW-style visual effects: bob animation, distance-based scaling, glow pass, distance fade
- Proper NPC height positioning with bounding box detection
- Camera-facing quads with depth testing but no depth write
- Shader-based alpha modulation for glow and fade effects
Technical changes:
- Created QuestMarkerRenderer class with billboard sprite system
- Integrated into Renderer initialization for both online and offline terrain loading
- Rewrote updateQuestMarkers() to use billboard system instead of M2 models
- Disabled old 2D ImGui renderQuestMarkers() in game_screen.cpp
- Added debug logging for initialization and marker tracking
Quest markers now render with proper WoW visual fidelity.