Introduce a waypoint-based interpolation system and integrate it into the proxy actor controller. Added F4T::InterpolationSystem (header + implementation) with TimePoint, InterpolationComponent, Lerp helpers and Update() logic. Integrates per-slot InterpolationComponent into F4TProxyActorController, switches to kinematic SetPosition-based smoothing (per-frame Lerp with snap thresholds) and removes character-controller velocity injection to prevent drift; also clears waypoints on hard snaps and uses steady_clock for timing. Added a descriptor-based bulk animation write (ApplyDesiredStateToGraphDescriptorBased) in ProxyAnimationSync for efficient graph variable updates with failure logging. Also included testing and debugging docs (phase2-interpolation-testing.md and phase2-interpolation-debugging.md) and flipped some debug flags to false.
243 lines
7.2 KiB
Markdown
243 lines
7.2 KiB
Markdown
# Phase 2 Interpolation Debugging Guide
|
|
|
|
## Problem: "Still Jerky, Not Smooth Movement"
|
|
|
|
This guide will help diagnose why interpolation isn't producing smooth movement.
|
|
|
|
---
|
|
|
|
## Quick Diagnostics
|
|
|
|
### 1. **Check If Interpolation is Even Being Called**
|
|
|
|
**Log signature to find:**
|
|
```
|
|
[Local Player ID: X] Phase 2 interpolation: remote player Y, actor=ZZZZZZZZ, smooth blending active.
|
|
```
|
|
|
|
**If this log is NOT appearing:**
|
|
- Interpolation is not being triggered
|
|
- Check: Is `distanceDrift > 0.01F`? (Remote player must be moving)
|
|
- Check: Is `a_shouldSnap == false`? (Not a teleport/snap movement)
|
|
- Check: Does `a_optSlot` exist? (Must be a runtime proxy, not fallback)
|
|
|
|
### 2. **Verify Update Rate is Sufficient**
|
|
|
|
**What's needed for smooth interpolation:**
|
|
- Updates must arrive **frequently enough** to queue waypoints
|
|
- Too infrequent updates = large gaps between waypoints = large lerps = still jerky
|
|
|
|
**Check fake client update rate:**
|
|
```bash
|
|
# In fake_client.py, look for the movement loop
|
|
# It should be sending updates at least 10-30 Hz (10-30 per second)
|
|
```
|
|
|
|
---
|
|
|
|
## Root Causes of Jerkiness
|
|
|
|
### **Root Cause 1: Not Enough Waypoints**
|
|
|
|
**Symptom:** Movement is still teleport-like, not smooth
|
|
**Reason:** Interpolation needs AT LEAST 2 waypoints
|
|
|
|
**Diagram:**
|
|
```
|
|
Bad (jerky):
|
|
Frame 1: ╔══ Position A
|
|
Frame 2: ║ ╔══ Position B (teleport)
|
|
Frame 3: ║ ║ ╔══ Position C (teleport)
|
|
|
|
Good (smooth):
|
|
Frame 1: ╔══════════════════╗ WayPoint 1 (A) queued
|
|
Frame 2: ║ Interp between ║ WayPoint 2 (B) queued, lerp A→B at 0.5
|
|
║ A and B at 25% ║
|
|
Frame 3: ║ Interp between ║ WayPoint 3 (C) queued, lerp B→C at 0.5
|
|
║ B and C at 50% ║
|
|
```
|
|
|
|
**Diagnosis:**
|
|
- Add extra logging to `ApplyRuntimeProxyTransform()`:
|
|
```cpp
|
|
LogInfoWithLocalPlayerPrefix(std::format(
|
|
"Waypoint queue size: {}, distanceDrift: {:.2f}, interpolating: {}",
|
|
a_optSlot->interpolationComponent.TimePoints.size(),
|
|
distanceDrift,
|
|
interpResult.IsInterpolating));
|
|
```
|
|
|
|
### **Root Cause 2: Update Arrival Rate Too Slow**
|
|
|
|
**Symptom:** Movement jumps happen, then freeze, then jump again
|
|
**Reason:** Remote player updates arriving slower than game frame rate
|
|
|
|
**Example:**
|
|
```
|
|
Game FPS: 60 (16ms per frame)
|
|
Update Rate: 10 Hz (100ms per update)
|
|
|
|
Result:
|
|
Frame 1-6: (100ms of frames) Interpolate slowly
|
|
Frame 7: NEW UPDATE arrives, big jump
|
|
Frame 8-13: Interpolate again
|
|
```
|
|
|
|
**Fix:** Increase fake client update frequency
|
|
```python
|
|
# In fake_client.py, change sleep duration:
|
|
time.sleep(0.033) # 30 Hz instead of slower
|
|
```
|
|
|
|
### **Root Cause 3: Alpha Calculation Wrong**
|
|
|
|
**Symptom:** Movement speed changes unexpectedly, or stalls
|
|
**Reason:** Frame counter might not be incrementing correctly
|
|
|
|
**Check the alpha calculation:**
|
|
```cpp
|
|
// In InterpolationSystem::Update()
|
|
float alpha = static_cast<float>(tickElapsed) / static_cast<float>(tickDelta);
|
|
|
|
// If tickDelta is 0 or 1, alpha calculation breaks
|
|
// tickDelta should be the FRAME DIFFERENCE between waypoints (e.g., 30-60)
|
|
```
|
|
|
|
### **Root Cause 4: Waypoint Queue Pruning Too Aggressive**
|
|
|
|
**Symptom:** Interpolation logs appear but movement still jerky
|
|
**Reason:** Queue size limited to 3, old waypoints popped too early
|
|
|
|
**Current code:**
|
|
```cpp
|
|
while (a_optSlot->interpolationComponent.TimePoints.size() > 3) {
|
|
a_optSlot->interpolationComponent.TimePoints.pop_front();
|
|
}
|
|
```
|
|
|
|
**Try increasing to 5-10 waypoints** for more buffer.
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### **Step 1: Add Detailed Logging**
|
|
|
|
Edit `F4TProxyActorController.cpp` in `ApplyRuntimeProxyTransform()`:
|
|
|
|
```cpp
|
|
if (a_optSlot && !a_shouldSnap && distanceDrift > 0.01F) {
|
|
static uint64_t frameCounter = 0;
|
|
frameCounter++;
|
|
|
|
// ADD THIS DEBUG LOG:
|
|
if (frameCounter % 10 == 0) { // Log every 10th frame to avoid spam
|
|
LogInfoWithLocalPlayerPrefix(std::format(
|
|
"Phase2-DEBUG: frame={}, distanceDrift={:.2f}, queueSize={}, interpolating={}",
|
|
frameCounter,
|
|
distanceDrift,
|
|
a_optSlot->interpolationComponent.TimePoints.size(),
|
|
/* will know after Update */
|
|
false));
|
|
}
|
|
|
|
// ... rest of code
|
|
}
|
|
```
|
|
|
|
### **Step 2: Monitor Log Output**
|
|
|
|
After recompiling, run fake client and watch log:
|
|
|
|
```bash
|
|
# Terminal: Watch logs in real-time
|
|
Get-Content "c:\Users\User\Documents\My Games\Fallout4\F4SE\Fallout4Together.log" -Wait -Tail 50 | Select-String "Phase2-DEBUG"
|
|
```
|
|
|
|
**Expected pattern (good interpolation):**
|
|
```
|
|
Phase2-DEBUG: frame=10, distanceDrift=345.62, queueSize=2, interpolating=true
|
|
Phase2-DEBUG: frame=20, distanceDrift=298.45, queueSize=2, interpolating=true
|
|
Phase2-DEBUG: frame=30, distanceDrift=251.23, queueSize=2, interpolating=true ← Smooth decrease
|
|
```
|
|
|
|
**Bad pattern (not working):**
|
|
```
|
|
Phase2-DEBUG: frame=10, distanceDrift=345.62, queueSize=1, interpolating=false
|
|
Phase2-DEBUG: frame=20, distanceDrift=1000.00, queueSize=1, interpolating=false ← Jumps!
|
|
Phase2-DEBUG: frame=30, distanceDrift=500.00, queueSize=1, interpolating=false
|
|
```
|
|
|
|
### **Step 3: Check Fake Client Update Rate**
|
|
|
|
Add logging to `fake_client.py`:
|
|
|
|
```python
|
|
last_update_time = time.time()
|
|
while True:
|
|
# ... send update ...
|
|
now = time.time()
|
|
update_interval = now - last_update_time
|
|
if update_interval > 0.05: # Log if >50ms between updates
|
|
print(f"WARNING: Long update interval: {update_interval*1000:.1f}ms")
|
|
last_update_time = now
|
|
time.sleep(0.033) # ~30 Hz
|
|
```
|
|
|
|
---
|
|
|
|
## Comprehensive Fix Checklist
|
|
|
|
- [ ] **Verify waypoints are queuing**: Add frame counter debug log
|
|
- [ ] **Confirm queue size > 1**: Should see "queueSize=2" in logs during movement
|
|
- [ ] **Check update frequency**: Fake client sending at 20-30 Hz minimum
|
|
- [ ] **Verify distanceDrift threshold**: Is `distanceDrift > 0.01F`?
|
|
- [ ] **Test with moving fake client**: Stationary player won't trigger interpolation
|
|
- [ ] **Increase waypoint buffer**: Try `queueSize > 5` instead of 3
|
|
- [ ] **Monitor alpha calculation**: Should go 0.0 → 1.0 as player moves between waypoints
|
|
|
|
---
|
|
|
|
## Quick Fix: Increase Update Frequency
|
|
|
|
**Easiest fix:** Make fake client send updates more frequently
|
|
|
|
Edit `fake_client.py`:
|
|
```python
|
|
# Find the main loop:
|
|
while True:
|
|
# ... code ...
|
|
time.sleep(0.033) # Change from 0.1 or higher to 0.033 (30 Hz)
|
|
```
|
|
|
|
Or increase queue size in `ApplyRuntimeProxyTransform()`:
|
|
```cpp
|
|
// Change from:
|
|
while (a_optSlot->interpolationComponent.TimePoints.size() > 3) {
|
|
// To:
|
|
while (a_optSlot->interpolationComponent.TimePoints.size() > 10) {
|
|
```
|
|
|
|
---
|
|
|
|
## If Still Jerky After All This
|
|
|
|
The issue might be **architectural**: Maybe F4 doesn't support sub-frame smooth movement, and we need to adjust strategy.
|
|
|
|
**Next options:**
|
|
1. **Reduce frame times via velocity injection**: Make proxies move via physics instead of SetPosition
|
|
2. **Use different lerp approach**: Accumulate delta over frames instead of tick-based
|
|
3. **Accept some jerkiness**: Design animations to hide it (blending animations during transitions)
|
|
|
|
---
|
|
|
|
## Need More Help?
|
|
|
|
Post the following in your logs:
|
|
1. Output of "Phase2-DEBUG" log lines (10 consecutive)
|
|
2. Fake client update interval timing
|
|
3. FPS you're getting in-game
|
|
4. Distance between waypoints in the logs
|
|
|
|
This will help diagnose the exact cause.
|