Files
Commonwealth-Online-Public/docs/phase2-interpolation-debugging.md
T
andrew a11c7d4475 Add interpolation system and integrate into proxy
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.
2026-06-03 22:46:57 +12:00

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.