fix: eliminate frequent SetPosition calls that break AI

BREAKTHROUGH INSIGHT:
The user reported: AI goes aggressive then immediately returns to neutral.
This matches the symptom of SetPosition() breaking the character controller.

ROOT CAUSE:
- We were calling SetPosition() every 500ms
- SetPosition() bypasses character controller → kills all movement
- Between SetPosition calls, Move() had to re-establish control
- This constant switching BREAKS the AI's ability to function

THE FIX:
- ONLY use SetPosition() for emergencies (drift > 300 units OR > 2 seconds)
- Always use Move() with small, bounded deltas (max 50 units/frame)
- This lets the character controller and AI run continuously
- AI can now maintain state and play animations properly

CRITICAL CHANGES:
1. Emergency thresholds: 300 unit drift OR 2 second timeout (not 200/500ms)
2. Normal movement: Use Move() with small incremental deltas
3. Never SetPosition during normal gameplay
4. SetPosition only as emergency correction

EXPECTED RESULT:
- ✅ Smooth continuous movement (no more jerky jumps)
- ✅ AI stays active (can get aggro and respond naturally)
- ✅ Proper animations (velocity continuous, not reset every 500ms)
- ✅ Network sync maintained (emergency corrections prevent drift)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-03 14:59:08 +12:00
co-authored by Cursor
parent e156d048ec
commit 2449525175
2 changed files with 90 additions and 23 deletions
+50
View File
@@ -3387,6 +3387,56 @@ Previous investigation assumed proxies were "puppets" without real AI. We now kn
---
## 2026-06-03: CRITICAL FIX - Call Move() EVERY FRAME
### The Problem Found
Test showed proxy was moving in jerky jumps every 300ms (only when SetPosition was called). **The proxy wasn't moving smoothly between syncs!**
**Root Cause:** We were assuming the AI would naturally call Move() if we just didn't call SetPosition(). **Wrong!** The AI doesn't automatically drive movement on PlaceAtMe proxies. We have to call `Move()` ourselves.
### The Fix ✅
**Call Move() EVERY FRAME** with the calculated delta to target position!
```cpp
RE::NiPoint3 deltaPos = a_nextPosition - a_proxy.GetPosition();
a_proxy.Move(0.016F, deltaPos, true); // Every frame!
```
**Why this works:**
- Move() updates character controller velocity every frame
- Velocity is what the animation graph actually evaluates
- SetPosition() every 500ms corrects accumulated position error
- Result: Smooth movement + animations!
### New Strategy
```
Every game frame:
1. Call Move(0.016F, deltaPos, true) to update velocity
→ Smooth movement
→ Animation graph sees velocity → plays walk/run anims
Every 500ms:
2. Call SetPosition() to correct position
→ Prevents drift from network target
→ Maintains sync accuracy
```
### Updated Implementation
- Modified ApplyRuntimeProxyTransform() to call Move() every frame
- Increased sync interval to 500ms (less frequent position overrides)
- Increased drift threshold to 200 units (tolerate more drift between syncs)
**Expected Result:**
- ✅ Smooth continuous movement (not jerky)
- ✅ Proper animations (from velocity updates)
- ✅ Network synchronized (SetPosition every 500ms)
---
## 2026-06-03: HYBRID MOVEMENT IMPLEMENTATION - Animations Now Enabled
### Implementation Complete ✅