Files
ThordekkCore/MOVEMENT_AI_CONFLICT_ANALYSIS.md
T
2026-01-20 21:33:16 -03:00

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:

  1. ClassAI Combat Layer (WarriorAI, PaladinAI, HunterAI)
  2. CombatMovementStrategy (Role-based positioning)
  3. LeaderFollowBehavior (Group follow logic)
  4. 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 setting
  • PaladinAI.cpp:50-200 - Similar pattern for paladins
  • HunterAI.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 loop
  • CombatMovementStrategy.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 calculation
  • LeaderFollowBehavior.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 calculation
  • PositionStrategyBase.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 flag
  • CombatMovementStrategy.cpp:320-332 - Tank positioning
  • CombatMovementStrategy.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 positioning
  • CombatMovementStrategy.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 positioning
  • CombatMovementStrategy.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

  1. CombatMovementStrategy (80) overrides ClassAI combat logic
  2. No coordination between CombatMovementStrategy and ClassAI positioning needs
  3. 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
}

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:

  1. Add MovementArbiter to BotAI
  2. Modify all movement systems to submit requests instead of direct MotionMaster calls
  3. Arbiter evaluates requests each frame and issues one movement command
  4. 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:

  1. 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;
}
  1. 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)

  1. ✅ Complete this analysis (DONE)
  2. ⚠️ Disable CombatMovementStrategy temporarily to test if it's the primary culprit
  3. ⚠️ Add diagnostic logging to track movement command sources

High Priority (This Week)

  1. Fix role detection (OPTION C, step 1)
  2. Implement movement locks (OPTION B) OR design movement arbiter (OPTION A)
  3. Add facing preservation in ClassAI combat updates

Medium Priority (Next Sprint)

  1. Refactor ClassAI positioning to use consistent positioning API
  2. Add movement request visualization (debug overlay showing which system is controlling movement)
  3. 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:

  1. Spawn solo warrior bot
  2. Attack training dummy
  3. Monitor movement commands (add logging)
  4. 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:

  1. Create group with player leader + 3 bot followers (Warrior, Paladin, Hunter)
  2. Enter combat
  3. Verify follow deactivates IMMEDIATELY
  4. 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:

  1. Create bots with different specs
  2. Verify role assignment matches spec, not class
  3. 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:

  1. ClassAI (sets flags, calculates positions)
  2. CombatMovementStrategy (clears MotionMaster, issues new commands)
  3. 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 loop
  • src/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 combat
  • src/modules/Playerbot/AI/ClassAI/Paladins/PaladinAI.cpp:50-200 - Paladin combat
  • src/modules/Playerbot/AI/ClassAI/Hunters/HunterAI.cpp:132-200 - Hunter combat

Positioning

  • src/modules/Playerbot/AI/ClassAI/PositionStrategyBase.cpp:54-199 - Position calculation
  • src/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)