# 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(tickElapsed) / static_cast(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.