- Create HousingRoomEntity (objectType=18, Housing/2 GUID, fragments [21,31,220])
for each room in SpawnRoomMeshObjects with MeshObjects array and door data
- Send room entities in deferred callback UPDATE_OBJECT (not initial batch —
type-18 entities in initial UPDATE_OBJECT crash without full housing context)
- Fix room GUID format: arg2=roomEntryId matching retail (sniff-verified)
- Load rooms BEFORE decor in Housing::LoadFromDB so decor RoomGuid matches
- Auto-assign RoomGuid for interior decor placement when client sends empty
- Add HousingRoomEntity::AddDoor using MutableFieldReferenceWithChangesMask
- Fix SQL hotfix idempotency for trait_tree columns
Implement SPELL_AURA_MOD_SPELL_CURRENCY_REAGENTS_COUNT_PCT
TrinityCore's PathGenerator uses a shared dtNavMeshQuery which is NOT
thread-safe — calling it from bot worker threads causes ACCESS_VIOLATION
crashes in dtNavMeshQuery::getPathToNode.
ThreadSafePathfinder creates per-thread dtNavMeshQuery instances using
thread_local storage. Each thread gets its own query with independent
node pool and open list, sharing the read-only dtNavMesh. This is safe
because dtNavMesh is read-only after tile loading.
API: ThreadSafePathfinder::IsReachable(mapId, start, dest)
- Returns true if navmesh path exists from start to dest
- Creates query on first use per thread per map
- 64-poly path check (enough for reachability, not full pathfinding)
Re-enabled navmesh reachability check in SearchForQuestGivers using
the thread-safe pathfinder instead of PathGenerator.
Audit found 200+ direct bot stat reads on worker threads across
strategy files. Converted the highest-impact patterns:
- RestStrategy: NeedsFood/NeedsDrink/IsActive/UpdateBehavior now use
ai->GetCachedHealthPct() and ai->GetCachedManaPct() from snapshot
- GrindStrategy: Health/mana recovery check uses snapshot
- SoloStrategy: Rest trigger, recovery check, mana check use snapshot
- Strategy.cpp: ShouldFlee health check uses snapshot
- BotAI.h: Added DistanceTo/Distance2dTo helpers using snapshot position
All reads go through BotAI accessors which read from the PlayerSnapshot
populated every 100ms from the spatial grid's main thread update.
Replace the broken cache system (SnapshotBotState ran before
Player::Update so values were always stale) with the spatial grid's
PlayerSnapshot which is populated on the main thread every 100ms
with current data via the DoubleBufferedSpatialGrid.
BotAI now stores a PlayerSnapshot and refreshes it at the start of
each UpdateAI via RefreshFromSpatialGrid(). All accessors
(GetCurrentPosition, GetCachedHealthPct, GetCachedManaPct, etc.)
read from this snapshot. Worker threads get accurate, lock-free data.
Changes:
- BotAI: Replace 5 individual cache fields with single PlayerSnapshot
- BotAI: Add RefreshFromSpatialGrid() querying spatial grid by bot GUID
- BotAI: Add GetSnapshot(), GetCachedIsAlive(), GetCachedLevel() etc.
- RestStrategy: NeedsFood/NeedsDrink/IsActive use snapshot via ai->
- Remove BotSession::SnapshotBotState() entirely
- Remove SnapshotBotState call from BotWorldSessionMgr
- Remove MoveSpline.h dependency from BotAI and BotSession
The main thread SnapshotBotState runs in OnWorldUpdate BEFORE
Map::Update/Player::Update, so it captures pre-regen health values.
The cache showed 49.4% while actual health was 100%.
Worker thread reads of bot->GetHealthPct() return correct values
because Player::Update has already run by the time the worker executes.
Reverted to direct reads which are proven correct by the
RestStrategy::UpdateBehavior logs showing health=100%.