chore: add AI state diagnostics for animation investigation
Added diagnostic functions to inspect proxy actor AI state: - DiagnosticProxyAIState(): logs AIProcess presence, current package, pathfinding state - EnableProxyAIAnimationMode(): placeholder for AI animation enablement - Called on proxy spawn to gather evidence about AI setup This is part of the AI approach investigation - we need to verify if proxies already have active AIProcess instances and what packages they have assigned. If the animation issue is just about calling Move() naturally (which happens when the AI runs), we can potentially enable this by assigning proper packages to the proxy and letting the game loop drive movement. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3260,4 +3260,60 @@ PlaceAtMe proxy actors are "puppets" - they:
|
||||
|
||||
This is a fundamental limitation of Fallout 4's architecture - the animation system is tightly coupled to the AI/pathfinding system, which is not suitable for networked puppet actors.
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-03: AI Animation Mode Investigation - Real NPC Approach (ONGOING)
|
||||
|
||||
### New Hypothesis
|
||||
|
||||
**NPCs play animations because:**
|
||||
- They have active `AIProcess` instances
|
||||
- Their AI packages direct `Move()` calls each frame
|
||||
- `Move()` updates character controller velocity
|
||||
- Animation graph evaluates based on velocity
|
||||
|
||||
**Key Question:** Do proxies already have active AIProcess? If so, why don't they animate?
|
||||
|
||||
### Investigation Status
|
||||
|
||||
- Added `EnableProxyAIAnimationMode()` function to inspect/enable AI on proxies
|
||||
- Discovered proxies may already have AIProcess (created via PlaceAtMe)
|
||||
- Realized: SetPosition() **bypasses the character controller**, preventing `Move()` calls
|
||||
- Hypothesis: If AI-driven Move() is called each frame, animations should play
|
||||
|
||||
### The Core Insight
|
||||
|
||||
The real problem might be:
|
||||
- We use `SetPosition()` for movement (bypasses character controller)
|
||||
- This prevents the game's normal loop from calling `Move()`
|
||||
- Which prevents velocity updates
|
||||
- Which prevents animations
|
||||
|
||||
**Solution Path:** Enable proxy AI packages to let the game's normal update loop drive `Move()` naturally
|
||||
|
||||
### Proposed AI-Driven Animation Approach
|
||||
|
||||
Instead of external SetPosition control:
|
||||
1. Assign proxy a "go to player" or "patrol to position X" package
|
||||
2. Let the game's AIProcess call `Move()` every frame
|
||||
3. `Move()` naturally updates velocity → animations play
|
||||
4. Use `SetPosition()` less frequently (every ~200ms) to correct drift
|
||||
5. Result: Smooth animated movement with network sync
|
||||
|
||||
### Technical Challenges
|
||||
|
||||
1. **Package Creation**: TESPackage objects are complex to create from code
|
||||
2. **Pathfinding**: Creation Engine's pathfinding can cause issues (why we use SetPosition)
|
||||
3. **AI State Management**: Need to prevent combat, fleeing, or unwanted interactions
|
||||
4. **Synchronization**: Need to balance AI autonomy with network updates
|
||||
|
||||
### Next Steps
|
||||
|
||||
- [ ] Add diagnostic logging to inspect proxy AIProcess state
|
||||
- [ ] Check what packages proxies currently have assigned
|
||||
- [ ] Implement safe package assignment (follow, patrol, or go-to)
|
||||
- [ ] Test if Move() gets called and if animations play
|
||||
- [ ] Handle edge cases (pathfinding failures, network lag)
|
||||
- [ ] Hybrid approach: AI movement + occasional SetPosition override
|
||||
|
||||
```
|
||||
@@ -481,6 +481,74 @@ namespace
|
||||
a_proxy.SetHeading(a_heading);
|
||||
}
|
||||
|
||||
// NEW: Enable AI-driven animation mode
|
||||
// This function attempts to make the proxy a "real" AI actor that plays animations
|
||||
// Strategy: Let the AI system drive movement via Move(), which triggers animations
|
||||
// Override position with SetPosition() to keep network sync accurate
|
||||
void EnableProxyAIAnimationMode(
|
||||
RE::Actor& a_proxy,
|
||||
const RE::PlayerCharacter& a_player)
|
||||
{
|
||||
(void)a_player;
|
||||
|
||||
// Ensure the proxy has an AIProcess (normally should have one by default)
|
||||
if (!a_proxy.currentProcess) {
|
||||
// If no AI process exists, we can't enable animation mode
|
||||
LogWarningWithLocalPlayerPrefix(std::format(
|
||||
"EnableProxyAIAnimationMode: Proxy {:08X} has no AIProcess; cannot enable animation mode.",
|
||||
a_proxy.GetFormID()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the proxy is already set up for movement
|
||||
auto* process = a_proxy.currentProcess;
|
||||
|
||||
// Key insight: We want the proxy to think it's following the player
|
||||
// But we DON'T want actual pathfinding (Creation Engine path bugs)
|
||||
// Solution: The proxy will idle, and we'll move it via our network updates
|
||||
// While the animation system still gets velocity updates from game loop
|
||||
|
||||
// For now, just document that this mode is available
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Proxy {:08X} has active AIProcess; AI animation mode is available.",
|
||||
a_proxy.GetFormID()));
|
||||
}
|
||||
|
||||
// Diagnostic: Log proxy's AI state
|
||||
void DiagnosticProxyAIState(
|
||||
RE::Actor& a_proxy,
|
||||
std::string_view a_label)
|
||||
{
|
||||
if (!a_proxy.currentProcess) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"[{}] Proxy {:08X}: NO AIProcess",
|
||||
a_label,
|
||||
a_proxy.GetFormID()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get package info
|
||||
std::string packageInfo = "NO_PACKAGE";
|
||||
if (a_proxy.currentProcess->currentPackage.package) {
|
||||
packageInfo = std::format("PACKAGE_EXISTS");
|
||||
}
|
||||
|
||||
// Get locomotion state
|
||||
bool isPathing = a_proxy.IsPathing();
|
||||
bool isPathValid = a_proxy.IsPathValid();
|
||||
bool isPathComplete = a_proxy.IsPathingComplete();
|
||||
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"[{}] Proxy {:08X}: AIProcess={}, {}, isPathing={}, pathValid={}, pathComplete={}",
|
||||
a_label,
|
||||
a_proxy.GetFormID(),
|
||||
(void*)a_proxy.currentProcess,
|
||||
packageInfo,
|
||||
isPathing,
|
||||
isPathValid,
|
||||
isPathComplete));
|
||||
}
|
||||
|
||||
bool IsProxyMovementPackageType(RE::PTYPE a_type)
|
||||
{
|
||||
switch (a_type) {
|
||||
@@ -3482,6 +3550,10 @@ namespace
|
||||
g_runtimeProxySpawnSucceeded = true;
|
||||
g_runtimeProxySpawnAttemptInProgress = false;
|
||||
StartRuntimeProxyVisibilityHoldIfNeeded();
|
||||
|
||||
// NEW: Log AI state for animation investigation
|
||||
DiagnosticProxyAIState(*spawnedActor, "ProxySpawnSuccess");
|
||||
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy spawn succeeded: actorBase={:08X}, actorBaseSource={}, handle={:08X}, createdRef={:08X}, resolvedAsActor=true, parentCell={:08X}, position={}. Stage 3 will prefer this actor for the single active remote representation while keeping the placed fallback available.",
|
||||
a_baseActor->GetFormID(),
|
||||
|
||||
Reference in New Issue
Block a user