From 94647dd3785e97a682728992da42c85d5e60ac4f Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 14 Jul 2026 11:30:18 -0500 Subject: [PATCH] MP: FIX phantom walk-in-place -- master sends a pose record on came-to-rest (task #50) The linear pose send-gate watched only POSITION drift + the 2s heartbeat. When a mech stops, its localOrigin is pinned and the master's own dead-reckon projection re-bases to it each frame, so error->0 and NO pose record fires -- leaving the peer holding the last WALKING velocity (updateVelocity). The replicant then dead-reckons + animates a phantom walk-in-place until the 2s heartbeat (stretched to ~30s when the master window is OS-throttled to ~8fps while backgrounded during solo two-window testing). Add the symmetric linear 'came-to-rest' trigger -- the exact analog of the angular (live yaw-rate==0 && replicated yaw-rate!=0) resync trigger already present in the type-4 gate: fire a pose record the frame the live horizontal speed collapses while the last-SENT speed was still non-zero, so the replicant receives velocity=0 immediately instead of waiting for the heartbeat. Diagnosis established via BT_WIRE tx0/rx0 probes: records deliver 1:1 (239 tx =~ 240 rx), velocities match -- NOT a replication stall; the master simply never transmitted the stop. Also confirmed the dominant choppiness is OS window-throttling (master 8fps backgrounded vs observer 63fps), a two-windows-on-one-box test artifact absent on real pod hardware. Co-Authored-By: Claude Opus 4.8 (1M context) --- game/reconstructed/mech.cpp | 7 +++++++ game/reconstructed/mech4.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 162fe7c..6c76b92 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -1810,6 +1810,13 @@ void updateVelocity.angularMotion = savedAngV; // FUN_00408440 poseSyncLatch = 1; // @0x77c -- arm the dead-reckon re-base + if (getenv("BT_WIRE")) + DEBUG_STREAM << "[rx0] ent=" << GetEntityID() + << " uvLin=(" << updateVelocity.linearMotion.x << "," + << updateVelocity.linearMotion.z << ")" + << " uPos=(" << updateOrigin.linearPosition.x << "," + << updateOrigin.linearPosition.z << ")\n" << std::flush; + if (bodyAnimationState == 2 || bodyAnimationState == 3) // @0x728 { projectedVelocity.linearMotion = ZeroVector; // FUN_0040a7f4(0x298, zeroMotion) diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 10c6a07..cf0ed7b 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -3414,12 +3414,38 @@ void { Scalar posDb = (updatePositionDeadband > 0.0f) ? updatePositionDeadband : 0.04f; + // LINEAR "came-to-rest" trigger -- the symmetric analog of the + // angular (live yaw-rate==0 && replicated yaw-rate!=0) resync + // trigger in the type-4 gate below. The position deadband alone + // does NOT catch a stop: once the master halts, its localOrigin is + // pinned and its OWN dead-reckon projection is re-based to it each + // frame, so error->0 and no pose record fires -- leaving the peer + // holding the last WALKING velocity (updateVelocity), which the + // replicant keeps dead-reckoning + animating as a phantom walk + // until the 2s heartbeat. Fire a pose record the frame the live + // horizontal speed collapses while the last-SENT speed was still + // non-zero, so the replicant gets velocity=0 immediately. + const Scalar liveSpdSq = + (Scalar)(localVelocity.linearMotion.x * localVelocity.linearMotion.x + + localVelocity.linearMotion.z * localVelocity.linearMotion.z); + const Scalar sentSpdSq = + (Scalar)(updateVelocity.linearMotion.x * updateVelocity.linearMotion.x + + updateVelocity.linearMotion.z * updateVelocity.linearMotion.z); + const Logical cameToRest = (liveSpdSq < 0.01f && sentSpdSq > 0.01f); if ( error.LengthSquared() > posDb || lastPerformance - lastUpdate > 2.0f + || cameToRest ) { ForceUpdate(); // type 0: pose record + if (getenv("BT_WIRE")) + DEBUG_STREAM << "[tx0] errSq=" << error.LengthSquared() + << " posDb=" << posDb + << " hb=" << (float)(lastPerformance - lastUpdate) + << (cameToRest ? " REST" : "") + << " lvLin=(" << localVelocity.linearMotion.x << "," + << localVelocity.linearMotion.z << ")\n" << std::flush; } } // ORIENTATION rides ONLY the type-4 resync record (the authentic