21 KiB
Movement AI Conflict Analysis - Comprehensive Report
Date: 2025-10-22 Scope: Warrior, Paladin, Hunter movement issues across all races and areas Status: CRITICAL - Multiple Concurrent AI Systems Detected
Executive Summary
CONFIRMED: There are THREE CONCURRENT AI SYSTEMS attempting to control bot movement simultaneously, causing severe conflicts and erratic behavior regardless of class, race, or area.
Root Cause
The movement issues are caused by architectural design conflicts where multiple AI layers issue competing movement commands without coordination:
- ClassAI Combat Layer (WarriorAI, PaladinAI, HunterAI)
- CombatMovementStrategy (Role-based positioning)
- LeaderFollowBehavior (Group follow logic)
- PositionStrategyBase (Optimal position calculation)
Each system operates independently, issuing MotionMaster commands that override or conflict with each other.
Detailed Architecture Analysis
1. Update Chain Flow
Player::Update() (every frame)
└─> BotAI::UpdateAI(uint32 diff)
├─> UpdateMovement(diff) // Strategy-controlled movement
│ └─> LeaderFollowBehavior::UpdateBehavior()
│ └─> player->GetMotionMaster()->MoveFollow() // MOVEMENT COMMAND #1
│
├─> OnCombatUpdate(diff) // If in combat
│ └─> ClassAI::OnCombatUpdate()
│ ├─> UpdateRotation(target)
│ │ ├─> behaviors->NeedsRepositioning()
│ │ │ └─> _needsIntercept = true // FLAG SET
│ │ └─> GetOptimalPosition(target) // POSITION CALCULATION
│ │
│ └─> UpdateCombatState()
│ └─> CombatMovementStrategy::UpdateBehavior()
│ └─> MoveToPosition()
│ └─> player->GetMotionMaster()->MovePoint() // MOVEMENT COMMAND #2
│
└─> UpdateBehaviors(diff) // Strategy updates
└─> CombatMovementStrategy::UpdateBehavior()
└─> CalculateTankPosition() / CalculateMeleePosition() / CalculateRangedPosition()
└─> player->GetMotionMaster()->MovePoint() // MOVEMENT COMMAND #3
2. Concurrent Movement Systems
System A: ClassAI Direct Movement Control
Location: ClassAI::UpdateRotation() → class-specific AI (WarriorAI, PaladinAI, HunterAI)
Evidence:
// WarriorAI.cpp:178-185
if (behaviors && behaviors->NeedsRepositioning())
{
Position optimalPos = behaviors->GetOptimalPosition();
// Movement will be handled by BotAI movement strategies
// Just flag that we need to move
_needsIntercept = true;
_needsCharge = true;
}
Issues:
- Sets repositioning flags (
_needsIntercept,_needsCharge) without actually moving - Relies on external systems to handle movement
- No coordination with other movement systems
File References:
WarriorAI.cpp:178-185- Repositioning flag settingPaladinAI.cpp:50-200- Similar pattern for paladinsHunterAI.cpp:132-200- Hunter positioning logic
System B: CombatMovementStrategy
Location: CombatMovementStrategy::UpdateBehavior()
Evidence:
// CombatMovementStrategy.cpp:143-243
void CombatMovementStrategy::UpdateBehavior(BotAI* ai, uint32 diff)
{
// Calculate optimal position based on role
switch (_currentRole)
{
case ROLE_TANK:
targetPosition = CalculateTankPosition(player, target);
break;
case ROLE_MELEE_DPS:
targetPosition = CalculateMeleePosition(player, target);
break;
// ... etc
}
// Move to position if needed
if (IsPositionReachable(player, targetPosition))
{
LogPositionUpdate(player, targetPosition, "Combat positioning");
MoveToPosition(player, targetPosition); // DIRECT MOTIONMASTER CALL
}
}
Movement Commands:
// CombatMovementStrategy.cpp:461-486
bool CombatMovementStrategy::MoveToPosition(Player* player, Position const& position)
{
player->GetMotionMaster()->Clear(); // CLEARS OTHER MOVEMENT
player->GetMotionMaster()->MovePoint(1, position); // ISSUES NEW COMMAND
_isMoving = true;
return true;
}
Issues:
- Clears MotionMaster every update (line 476), canceling other movement commands
- Runs independently from ClassAI combat logic
- No awareness of ClassAI positioning needs
- Priority: 80 - Higher than follow (60), but combat logic should be 100+
File References:
CombatMovementStrategy.cpp:143-243- Main update loopCombatMovementStrategy.cpp:461-486- Direct movement control
System C: LeaderFollowBehavior
Location: LeaderFollowBehavior::UpdateBehavior()
Evidence:
// LeaderFollowBehavior.cpp:172-179
if (bot->IsInCombat())
{
TC_LOG_TRACE("module.playerbot.follow",
"Bot {} in combat - follow behavior disabled (FIX FOR ISSUE #2 & #3)",
bot->GetName());
return 0.0f; // Changed from 10.0f - allows exclusive combat control
}
Current Behavior:
- Returns 0.0 relevance during combat (disabled)
- This is CORRECT - follow should not interfere with combat
- However, the deactivation might be delayed or incomplete
Issues:
- Relevance check runs every frame, but strategy activation/deactivation is not immediate
- May continue issuing follow commands for 1-2 frames after combat starts
- Race condition between combat detection and strategy deactivation
File References:
LeaderFollowBehavior.cpp:156-186- Relevance calculationLeaderFollowBehavior.cpp:172-179- Combat override
System D: PositionStrategyBase
Location: PositionStrategyBase::CalculateOptimalPosition()
Evidence:
// PositionStrategyBase.cpp:54-128
Position PositionStrategyBase::CalculateOptimalPosition(Player* bot, Unit* target, float preferredRange)
{
// Generate candidate positions in a spiral pattern
std::vector<Position> candidates;
float angleStep = 2.0f * M_PI / 8.0f; // 8 positions around target
for (int ring = 0; ring < 2; ++ring)
{
float range = preferredRange + (ring * 3.0f);
for (int i = 0; i < 8; ++i)
{
float angle = i * angleStep;
float x = target->GetPositionX() + cos(angle) * range;
float y = target->GetPositionY() + sin(angle) * range;
// ... validate and score positions
}
}
return optimalPos;
}
Issues:
- Provides position calculations only, doesn't issue movement commands directly
- Used by both ClassAI and CombatMovementStrategy
- No coordination between callers - both may request different positions simultaneously
File References:
PositionStrategyBase.cpp:54-128- Position calculationPositionStrategyBase.cpp:131-199- Batch position calculation
3. Race Condition Timeline
Frame N: Combat Starts
T+0ms: BotAI::UpdateAI() entry
T+5ms: UpdateMovement() → LeaderFollowBehavior still active (relevance check hasn't run)
→ MoveFollow(leader) command issued
T+10ms: OnCombatUpdate() → WarriorAI::UpdateRotation()
→ Sets _needsIntercept flag
→ Calculates optimal melee position (behind target)
T+15ms: UpdateBehaviors() → CombatMovementStrategy::UpdateBehavior()
→ Calculates TANK position (in front of target)
→ MotionMaster->Clear()
→ MovePoint(tankPosition)
Frame N+1: Conflict Visible
T+100ms: BotAI::UpdateAI() second cycle
T+105ms: UpdateMovement() → LeaderFollowBehavior relevance now 0.0 (combat detected)
→ Strategy begins deactivation
T+110ms: OnCombatUpdate() → WarriorAI needs to reposition
→ _needsIntercept still true from previous frame
T+115ms: CombatMovementStrategy calculates NEW position (target moved)
→ MotionMaster->Clear() again
→ MovePoint(newPosition)
Result:
- Bot receives 3 different movement commands in 115ms
- MotionMaster cleared twice in same frame sequence
- Bot "jitters" between positions
- Facing constantly reset
- Melee attacks fail (not facing target)
Class-Specific Manifestations
Warrior (Melee DPS/Tank)
Symptoms:
- Jittery movement around target
- Constantly running back and forth
- Charges never complete (movement canceled mid-charge)
- Facing issues prevent auto-attacks
Root Cause:
// WarriorAI.cpp:178-185
if (behaviors && behaviors->NeedsRepositioning())
{
Position optimalPos = behaviors->GetOptimalPosition();
_needsIntercept = true; // Flag set but no immediate action
_needsCharge = true;
}
// Meanwhile, CombatMovementStrategy independently calculates:
// CalculateMeleePosition() → behind target
// But CalculateTankPosition() → in front of target
// → CONFLICT if role detection is inconsistent
File References:
WarriorAI.cpp:178-185- Repositioning flagCombatMovementStrategy.cpp:320-332- Tank positioningCombatMovementStrategy.cpp:333-347- Melee DPS positioning
Paladin (Melee DPS/Tank/Healer)
Symptoms:
- Similar to Warrior for Retribution/Protection
- Holy paladins "drift" away from group while healing
Root Cause:
// CombatMovementStrategy.cpp:245-286 - Role determination
switch (playerClass)
{
case CLASS_PALADIN:
return ROLE_TANK; // Default to tank
}
// But PaladinAI might be healing, not tanking
// → Wrong positioning strategy applied
Issues:
- Role detection is class-based, not spec-based
- Paladins default to TANK role even if specced Holy or Ret
- Healer positioning (382-459) conflicts with tank positioning (320-332)
File References:
PaladinAI.cpp:50-200- Combat rotation with positioningCombatMovementStrategy.cpp:245-286- Role determination (always TANK for paladins)CombatMovementStrategy.cpp:382-459- Healer positioning
Hunter (Ranged DPS)
Symptoms:
- Constantly adjusting range (too close → too far → too close)
- "Dead zone" issues (8-40 yards)
- Pet positioning conflicts
Root Cause:
// HunterAI.cpp:180-186
if (HandlePositioning(target))
return; // Early return if positioning needed
// But CombatMovementStrategy also runs:
// CalculateRangedPosition() with different range calculation
Issues:
- HunterAI has custom positioning logic for dead zone avoidance
- CombatMovementStrategy uses generic ranged positioning
- Both systems try to maintain "optimal range" with different definitions
File References:
HunterAI.cpp:180-186- Hunter-specific positioningCombatMovementStrategy.cpp:348-380- Generic ranged positioning
Priority System Analysis
Current Priority Configuration
CombatMovementStrategy: Priority 80
LeaderFollowBehavior: Priority 50 (60 in follow.cpp, 50 in constructor)
ClassAI Combat: NO EXPLICIT PRIORITY (assumes higher via OnCombatUpdate)
Issues
- CombatMovementStrategy (80) overrides ClassAI combat logic
- No coordination between CombatMovementStrategy and ClassAI positioning needs
- Priority inversion: Generic movement strategy has higher priority than class-specific combat logic
Expected Priority Hierarchy
Combat Abilities & Positioning: 100+ (ClassAI)
Combat Movement Strategy: 90 (Role-based positioning)
Following: 50 (Group follow)
Idle/Exploration: 20 (Wandering)
Root Cause Summary
1. Architectural Design Flaw
Multiple autonomous systems issuing MotionMaster commands without a central movement arbiter.
2. No Movement Coordination Layer
Each system operates in isolation:
- ClassAI sets flags but doesn't control movement
- CombatMovementStrategy controls movement but doesn't understand combat needs
- LeaderFollowBehavior has delayed deactivation during combat transitions
3. Priority Misalignment
Generic movement strategy (80) has higher priority than class-specific combat logic.
4. Race Conditions
Strategy relevance checks and activation/deactivation are not atomic with movement commands.
5. Role Detection Issues
Class-based role detection (DetermineRole()) doesn't account for specialization:
// CombatMovementStrategy.cpp:245-286
switch (playerClass)
{
case CLASS_WARRIOR:
return ROLE_TANK; // Always tank, regardless of spec
case CLASS_PALADIN:
return ROLE_TANK; // Always tank, regardless of spec
}
Recommended Solutions
OPTION A: Central Movement Arbiter (Recommended)
Architecture:
class MovementArbiter
{
public:
enum class MovementSource
{
COMBAT_AI, // Priority 100 - ClassAI combat positioning
COMBAT_STRATEGY, // Priority 90 - Role-based positioning
FOLLOW, // Priority 50 - Group follow
IDLE // Priority 10 - Wandering
};
struct MovementRequest
{
MovementSource source;
Position targetPosition;
Unit* targetUnit;
float range;
uint32 timestamp;
};
void RequestMovement(MovementSource source, MovementRequest const& request);
void Update(uint32 diff);
private:
std::map<MovementSource, MovementRequest> _pendingRequests;
MovementRequest _activeRequest;
MovementRequest SelectHighestPriority();
bool ShouldUpdateMovement(MovementRequest const& newRequest);
};
Benefits:
- Single point of control for all movement
- Priority-based arbitration ensures combat always wins
- Request deduplication prevents jittering
- State tracking allows intelligent transitions
Implementation:
- Add MovementArbiter to BotAI
- Modify all movement systems to submit requests instead of direct MotionMaster calls
- Arbiter evaluates requests each frame and issues one movement command
- Only update movement if new request has higher priority OR position delta > threshold
File Changes:
- Create
src/modules/Playerbot/Movement/MovementArbiter.h - Create
src/modules/Playerbot/Movement/MovementArbiter.cpp - Modify
ClassAI.cpp:196-201(remove direct facing calls) - Modify
CombatMovementStrategy.cpp:461-486(use arbiter instead of MovePoint) - Modify
LeaderFollowBehavior.cpp(use arbiter)
OPTION B: Priority-Based Movement Locks
Simpler approach:
// In BotAI
enum class MovementLockLevel
{
NONE = 0,
FOLLOW = 50,
COMBAT_STRATEGY = 90,
COMBAT_AI = 100
};
std::atomic<MovementLockLevel> _movementLock{MovementLockLevel::NONE};
bool TryAcquireMovementControl(MovementLockLevel level)
{
MovementLockLevel current = _movementLock.load();
if (static_cast<int>(level) >= static_cast<int>(current))
{
_movementLock.store(level);
return true;
}
return false;
}
Usage:
// In ClassAI::OnCombatUpdate()
if (TryAcquireMovementControl(MovementLockLevel::COMBAT_AI))
{
// Issue movement commands
GetBot()->GetMotionMaster()->MoveChase(target, range);
}
// In CombatMovementStrategy::UpdateBehavior()
if (TryAcquireMovementControl(MovementLockLevel::COMBAT_STRATEGY))
{
// Only runs if ClassAI hasn't claimed control
MoveToPosition(player, targetPosition);
}
Benefits:
- Simpler implementation than full arbiter
- Immediate priority enforcement
- Less code changes required
Drawbacks:
- No request deduplication
- No intelligent transition handling
- Requires manual lock releases
OPTION C: Fix Role Detection + Disable Competing Systems
Quick Fix Approach:
- Fix role detection to use spec, not class:
// CombatMovementStrategy.cpp:245-300
FormationRole CombatMovementStrategy::DetermineRole(Player* player) const
{
// TODO: Check actual spec from talent tree
// For now, make intelligent guesses based on equipped gear
if (player->HasItemEquipped(INVTYPE_SHIELD, INVENTORY_SLOT_BAG_END))
return ROLE_TANK; // Has shield = tank
if (IsMeleeClass(player->GetClass()))
return ROLE_MELEE_DPS;
if (IsHealerClass(player->GetClass()))
return ROLE_HEALER;
return ROLE_RANGED_DPS;
}
- Disable CombatMovementStrategy if ClassAI handles positioning:
// ClassAI.cpp - Add virtual method
virtual bool HandlesOwnMovement() const { return true; }
// CombatMovementStrategy.cpp
void CombatMovementStrategy::UpdateBehavior(BotAI* ai, uint32 diff)
{
// Skip if ClassAI handles its own movement
if (ClassAI* classAI = dynamic_cast<ClassAI*>(ai))
{
if (classAI->HandlesOwnMovement())
return;
}
// ... rest of positioning logic
}
Benefits:
- Minimal code changes
- Quick to implement
- Testable incrementally
Drawbacks:
- Doesn't solve architectural issues
- Temporary workaround, not a real fix
Immediate Action Items
Critical (Fix Today)
- ✅ Complete this analysis (DONE)
- ⚠️ Disable CombatMovementStrategy temporarily to test if it's the primary culprit
- ⚠️ Add diagnostic logging to track movement command sources
High Priority (This Week)
- Fix role detection (OPTION C, step 1)
- Implement movement locks (OPTION B) OR design movement arbiter (OPTION A)
- Add facing preservation in ClassAI combat updates
Medium Priority (Next Sprint)
- Refactor ClassAI positioning to use consistent positioning API
- Add movement request visualization (debug overlay showing which system is controlling movement)
- Performance testing with 100+ bots to ensure no degradation
Testing Recommendations
Test Case 1: Solo Combat (No Group)
Expected Behavior:
- LeaderFollowBehavior: INACTIVE (no group)
- CombatMovementStrategy: ACTIVE (if not disabled)
- ClassAI: ACTIVE (combat rotation)
Test:
- Spawn solo warrior bot
- Attack training dummy
- Monitor movement commands (add logging)
- Verify NO jittering, stable melee position
Test Case 2: Group Combat (Following Leader)
Expected Behavior:
- LeaderFollowBehavior: DEACTIVATED during combat (relevance 0.0)
- CombatMovementStrategy: ACTIVE
- ClassAI: ACTIVE
Test:
- Create group with player leader + 3 bot followers (Warrior, Paladin, Hunter)
- Enter combat
- Verify follow deactivates IMMEDIATELY
- Verify bots take role-appropriate positions
Test Case 3: Role Detection
Expected Behavior:
- Warrior (Arms/Fury): ROLE_MELEE_DPS → behind target
- Warrior (Protection): ROLE_TANK → in front of target
- Paladin (Holy): ROLE_HEALER → medium range, central
- Hunter (Any): ROLE_RANGED_DPS → 20-30 yards
Test:
- Create bots with different specs
- Verify role assignment matches spec, not class
- Verify positioning matches role
Conclusion
The movement issues are NOT class-specific, race-specific, or area-specific. They are caused by a fundamental architectural flaw where multiple AI systems compete for movement control without coordination.
Three concurrent systems are actively issuing movement commands:
- ClassAI (sets flags, calculates positions)
- CombatMovementStrategy (clears MotionMaster, issues new commands)
- LeaderFollowBehavior (delayed deactivation during combat transitions)
Recommended Solution: OPTION A (Movement Arbiter) for long-term stability, or OPTION B (Movement Locks) for faster implementation.
Immediate Workaround: Disable CombatMovementStrategy and rely solely on ClassAI positioning until arbiter is implemented.
File Reference Index
Core Systems
src/modules/Playerbot/AI/BotAI.cpp:316-481- Main update loopsrc/modules/Playerbot/AI/BotAI.cpp:753-761- UpdateMovement()src/modules/Playerbot/AI/ClassAI/ClassAI.cpp:78-200- OnCombatUpdate()
Movement Systems
src/modules/Playerbot/AI/Strategy/CombatMovementStrategy.cpp:143-243- UpdateBehavior()src/modules/Playerbot/AI/Strategy/CombatMovementStrategy.cpp:461-486- MoveToPosition()src/modules/Playerbot/Movement/LeaderFollowBehavior.cpp:156-300- Follow logic
Class-Specific AI
src/modules/Playerbot/AI/ClassAI/Warriors/WarriorAI.cpp:44-201- Warrior combatsrc/modules/Playerbot/AI/ClassAI/Paladins/PaladinAI.cpp:50-200- Paladin combatsrc/modules/Playerbot/AI/ClassAI/Hunters/HunterAI.cpp:132-200- Hunter combat
Positioning
src/modules/Playerbot/AI/ClassAI/PositionStrategyBase.cpp:54-199- Position calculationsrc/modules/Playerbot/AI/Strategy/CombatMovementStrategy.cpp:320-459- Role-based positioning
Analysis Completed: 2025-10-22 Severity: CRITICAL Impact: All classes, all races, all areas Root Cause: Architectural design flaw - multiple uncoordinated movement systems Solution Status: Architecture redesign required (OPTION A recommended)