Three fixes for BG bot lifecycle issues:
1. End BG when last human leaves: MonitorActiveBattlegrounds() now checks
for human presence in active BGs. After a 30s grace period with no
humans, the BG ends as a draw via EndBattleground(TEAM_OTHER).
2. Release and logout BG bots on BG end: OnBattlegroundEnd() now releases
pool bots via InstanceBotPool::ReleaseBot(), logs out all BG bots via
BotWorldSessionMgr::RemovePlayerBot(), and notifies the orchestrator
via OnInstanceEnded(). Previously only tracking maps were cleared.
3. Fix zone spawner over-spawning with no humans: CalculateTargetBotCount()
now uses _lastRealPlayerCount (humans only) instead of the broken
condition that always applied minimums in static mode or counted bot
sessions as active players.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: luis <[email protected]>
- Add BotAI.h include to 9 files that use unique_ptr<BotAI> via BotSession.h
Files fixed: BotCharacterCreator, BotResourcePool, BotSpawnOrchestrator,
BotSpawner, GracefulExitHandler, DynamicQuestSystem, ObjectiveTracker,
LootDistribution
- Fix CorpseCrashMitigation.cpp deprecated TrinityCore APIs:
* Replace SetDeathState() with setDeathState() (correct casing)
* Replace SetFlag/RemoveFlag/GetByteValue with internal tracking set
* Replace deprecated PLAYER_FIELD_BYTES2 with _pendingPrevention set
* Fix dynamic_cast<BotAI*> by using BotSession->GetAI() pattern
* Fix OrderedSharedMutex usage (direct lock/unlock instead of std::lock)
- Add _pendingPrevention unordered_set to CorpseCrashMitigation.h
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>
**Problem:**
Classic check-then-set race condition in spawn queue processing:
Thread 1: Check _processingQueue=false ✓ (line 283)
Thread 2: Check _processingQueue=false ✓ (before T1 sets it!)
Thread 1: Set _processingQueue=true, enters processing (line 285)
Thread 2: Set _processingQueue=true, enters processing ❌ CONCURRENT PROCESSING!
Multiple threads could simultaneously enter the spawn queue processing critical
section because the check and set operations were not atomic:
```cpp
// OLD CODE - UNSAFE
if (!_processingQueue.load() && queueHasItems) // CHECK (T1)
{
_processingQueue.store(true); // SET (T2) - RACE!
ProcessSpawnQueue(); // Both threads enter!
_processingQueue.store(false);
}
```
This could cause:
- Duplicate spawn attempts for the same request
- Concurrent map modifications (TBB concurrent_hash_map isn't safe for this pattern)
- Throttler/orchestrator confusion from concurrent access
- Potential crashes from race conditions in spawn logic
**Root Cause:**
Non-atomic check-then-set pattern. Between line 283 (check) and line 285 (set),
another thread could pass the same check, resulting in both threads setting the
flag and entering the critical section.
**Solution:**
Atomic compare-exchange-strong operation:
```cpp
// NEW CODE - SAFE
bool expected = false;
if (queueHasItems && _processingQueue.compare_exchange_strong(
expected, true, std::memory_order_acquire, std::memory_order_relaxed))
{
try {
ProcessSpawnQueue(); // Only ONE thread can enter
} catch (...) {
TC_LOG_ERROR("Exception in queue processing");
}
_processingQueue.store(false, std::memory_order_release);
}
```
**How compare_exchange_strong Works:**
1. Atomically checks if _processingQueue == expected (false)
2. If true, atomically sets _processingQueue = true
3. Returns true (only ONE thread succeeds)
4. Other threads see expected changed to true by the compare, fail check
5. Guarantees mutual exclusion without explicit mutex
**Thread Execution Example:**
- Thread 1: CAS(false→true) succeeds, expected=false, returns true ✓
- Thread 2: CAS(false→true) fails (already true), expected=true, returns false ✓
- Thread 1: Processes queue exclusively
- Thread 2: Skips processing, continues Update()
**Memory Ordering:**
- acquire: Ensures all subsequent reads see values written before the store(true)
- release: Ensures all prior writes are visible before store(false) becomes visible
- Proper synchronization without full sequential consistency overhead
**Additional Safety:**
- Wrapped processing in try-catch to ensure flag is always reset
- Used memory_order_release when resetting flag for proper visibility
- Exception-safe cleanup guarantees no stuck flag state
**Testing:**
- Compiled cleanly (RelWithDebInfo)
- No new warnings
- Pattern guarantees mutual exclusion
**Benefits:**
- Zero contention: Only atomic operations, no mutex overhead
- Correct: Guarantees exactly one thread processes queue per update cycle
- Fast: Lock-free atomic operations are ~10-100x faster than mutex
- Safe: Exception-safe cleanup ensures flag is always reset
**Location:** src/modules/Playerbot/Lifecycle/BotSpawner.cpp:267-420
**Priority:** P1 (Race condition in spawn queue processing)
**Task:** SESSION_LIFECYCLE_FIXES Task 4/7
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>
**Problem:**
Classic Time-Of-Check-Time-Of-Use (TOCTOU) race in spawn validation:
Thread 1: Check count=99 < 100 ✓ (line 737 ValidateSpawnRequest)
Thread 2: Check count=99 < 100 ✓ (before T1 increments)
Thread 1: Spawns, count becomes 100 (line 1131 ContinueSpawnWithCharacter)
Thread 2: Spawns, count becomes 101 ❌ POPULATION CAP OVERFLOW!
Multiple threads could pass population cap check simultaneously because:
1. Check happens in ValidateSpawnRequest (line 737-755)
2. Increment happens much later in ContinueSpawnWithCharacter (line 1131)
3. Time gap between check and increment allows race conditions
With 4 threads spawning 50 bots each (target: 100 max), actual count could
reach 120-150 due to concurrent validation passes.
**Root Cause:**
```cpp
// OLD FLOW - UNSAFE
SpawnBot() {
if (!ValidateSpawnRequest()) return false; // CHECK (T1)
// TIME GAP - other threads can also pass check here
return SpawnBotInternal(); // USE (T2)
}
ContinueSpawnWithCharacter() {
_activeBotCount.fetch_add(1); // INCREMENT (T3) - too late!
}
```
**Solution:**
Atomic pre-increment with rollback pattern:
```cpp
// NEW FLOW - SAFE
SpawnBot() {
// 1. Basic validation (non-population)
if (!ValidateSpawnRequestBasic()) return false;
// 2. ATOMIC PRE-INCREMENT - reserves slot, returns OLD value
uint32 oldCount = _activeBotCount.fetch_add(1, acquire);
// 3. Check cap using OLD value (before increment)
if (oldCount >= maxBotsTotal) {
_activeBotCount.fetch_sub(1, release); // Rollback
return false;
}
// 4. Spawn (counter already incremented, no double-count)
if (!SpawnBotInternal()) {
_activeBotCount.fetch_sub(1, release); // Rollback on error
return false;
}
return true;
}
```
**Why This Works:**
- fetch_add() is atomic - only ONE thread can get oldCount=99
- Thread that gets oldCount=99 passes (reserves slot 100)
- Next thread gets oldCount=100, fails check, rolls back
- Zero time gap between check and increment
- Exact cap enforcement guaranteed
**Changes:**
1. Created ValidateSpawnRequestBasic() - non-population validation
2. Updated SpawnBot() - atomic pre-increment with rollback
3. Removed increment from ContinueSpawnWithCharacter() - prevent double-count
4. Kept ValidateSpawnRequest() for backward compatibility (marked deprecated)
**Testing:**
- Compiled cleanly (RelWithDebInfo)
- No new warnings
- Pattern guarantees exact cap enforcement
**Limitations:**
- Zone/map caps still best-effort (no per-zone atomic counters yet)
- Global cap is the hard limit (perfectly enforced)
- TODO: Add per-zone atomic counters for perfect zone cap enforcement
**Location:**
- src/modules/Playerbot/Lifecycle/BotSpawner.cpp (lines 527-620, 698-795, 1130-1135)
- src/modules/Playerbot/Lifecycle/BotSpawner.h (line 205)
**Priority:** P1 (Race condition causing population cap overflow)
**Task:** SESSION_LIFECYCLE_FIXES Task 3/7
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>
**Problem:**
Range-based for loop over TBB concurrent_hash_map in DespawnAllBots() was NOT
atomic. Other threads could modify _activeBots during iteration, causing:
- Iterator invalidation
- Missing bots (removed during iteration)
- Seeing bots twice (if rehash happens)
- Potential crashes from concurrent modification
**Root Cause:**
```cpp
// UNSAFE - NOT atomic, race condition window
for (auto const& [guid, zoneId] : _activeBots) {
botsToRemove.push_back(guid);
}
```
While iterating, other threads could call SpawnBot/DespawnBot, modifying the
underlying hash table structure.
**Solution:**
Implemented atomic swap pattern:
1. Create empty concurrent_hash_maps
2. Atomically swap with _activeBots and _botsByZone (TBB swap is atomic)
3. Process isolated snapshot with zero race condition risk
4. Directly cleanup sessions (bypass DespawnBot since entries already removed)
**Benefits:**
- Thread-safe: No race conditions possible
- Fast: Single pass, no repeated lookups
- Clean: All session cleanup handled properly
- Stats: Batch updates for performance
**Technical Details:**
- Used tbb::concurrent_hash_map::swap() atomic operation
- Isolated oldBots map guarantees no concurrent access
- Direct session cleanup via RemoveAllPlayerBots()
- Atomic counter updates with memory_order_release
- Batch stat updates (single fetch_add vs N individual calls)
**Testing:**
- Compiled cleanly (RelWithDebInfo)
- No new warnings
**Location:** src/modules/Playerbot/Lifecycle/BotSpawner.cpp:1256-1300
**Priority:** P1 (Race condition in mass despawn operation)
**Task:** SESSION_LIFECYCLE_FIXES Task 2/7
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>
This commit syncs accumulated changes from previous development sessions:
Code Changes:
- Added BG invitation hook (OnBGInvitationReceived) to PlayerBotHooks
- Updated spell validations for WoW 12.0 compatibility
- Minor ClassAI adjustments across multiple specs
- Combat system refinements
- BattlegroundQueue core integration
Documentation Updates:
- Updated multiple phase completion documents
- Synchronized technical specifications
- Updated architecture and testing documentation
- Configuration guides and deployment docs
Project Maintenance:
- Serena project configuration updated
- Build system verified (successful compilation)
- All changes compile cleanly
This represents incremental development progress and is being committed
before proceeding with next priority tasks.
Build Status: SUCCESS (worldserver.exe 55MB)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>
Warm pool and JIT bots for BG/dungeon/arena are temporary special-purpose
bots that should not be blocked by the MaxBots configuration setting.
Changes:
- Add bypassMaxBotsLimit field to SpawnRequest struct
- Pass bypassMaxBotsLimit through BotSpawner chain to AddPlayerBot
- Set bypassMaxBotsLimit=true in InstanceBotPool::WarmUpBot
This fixes the issue where setting MaxBots=0 (to disable world population
bots) also blocked warm pool bots from spawning for battleground queues.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>
JIT bots were failing to create (1/29 success rate) because
Player::Create() calls ValidateAppearance() which requires valid
ChrCustomizationChoice entries for WoW 11.x.
The fix generates default customizations using DB2Manager APIs:
- GetCustomiztionOptions() to get available options for race/gender
- GetCustomiztionChoices() to get valid choices for each option
- Uses first available choice for each customization option
Applied to both CreateBotCharacter() overloads to fix JIT/Instance bot
creation for BG queue population.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>
Build fixes:
- Add Player forward declaration to JITBotFactory.h
- Add missing includes: BotWorldSessionMgr.h, BotSession.h, DB2Stores.h
- Fix LocalizedString usage with DEFAULT_LOCALE instead of integer index
- Fix SpellName pointer dereference: (*spellInfo->SpellName)[DEFAULT_LOCALE]
- Fix PlayerSpell member access: .active/.disabled instead of .IsActive()/.IsDisabled()
- Remove non-existent BotSessionMgr.cpp/.h from CMakeLists.txt
New features:
- Implement enterprise-grade role detection in JITBotFactory using DB2 spec data
- Add GetPlayerSpecRole() using ChrSpecializationEntry for accurate role detection
- Add GroupRoleToBotRole() conversion for pool system compatibility
- Add DetermineBotRole() as main entry point for spec-based role determination
- Distinguish melee/ranged DPS using ChrSpecializationFlag
Code cleanup:
- Remove obsolete BotSessionMgr files (replaced by BotWorldSessionMgr)
- Remove IBotSessionMgr interface (no longer needed)
- Remove MockSpatialGridManager test mock
- Remove invalid dynamic_cast<BotAI*>(UnitAI*) - types are unrelated
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: luis <[email protected]>