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.
7.2 KiB
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_optSlotexist? (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:
# 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():
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
# 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:
// 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:
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():
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:
# 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:
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 > 5instead 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:
# 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():
// 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:
- Reduce frame times via velocity injection: Make proxies move via physics instead of SetPosition
- Use different lerp approach: Accumulate delta over frames instead of tick-based
- Accept some jerkiness: Design animations to hide it (blending animations during transitions)
Need More Help?
Post the following in your logs:
- Output of "Phase2-DEBUG" log lines (10 consecutive)
- Fake client update interval timing
- FPS you're getting in-game
- Distance between waypoints in the logs
This will help diagnose the exact cause.