Diag: render-rate rotation-evenness probe + angular-drift instrumentation (task #50 spin residual)
Autonomous headless spin harness (BT_AUTODRIVE+BT_FORCE_TURN) + probes that finally measure what the eye sees, not the sim heading: - BT_RENDHDG (L4VIDEO render loop): per-RENDERED-frame peer heading step evenness -- avgStep/maxStep/max-avg ratio. Confirmed the residual spin 'hesitation' is UNEVEN rendered rotation (max/avg ~2.8x), correlated with the byAngle resync-flood bursts (maxAng ~= pi), NOT a render-vs-sim rate stall (render redraws a fresh heading every frame). - BT_SPIN / BT_ANGSIGN: resync trigger breakdown (byAngle/byVel/byRest) + the frame-level local/update/projected angular-Y that exposed the Abs() macro bug. - gBTReplRenderYaw: peer heading published from the sim to the render probe. - Normalize projectedOrigin.angularPosition after the peer-mirror advance (adding a scaled ang-vel VECTOR to a quaternion denormalizes it) -- correct, but NOT sufficient: the pi divergence is REAL, from the crude large-angle quaternion projection over the sparse angular-record interval during a PURE spin (linear dense-send doesn't fire when not translating; type-4 is the only orientation carrier and resets the horizon, so dense type-4 -> half-rate). Residual ROOT (scoped, not yet fixed): the reckoner (MOVER.cpp:457-466) projects rotation by ADDING vector*t to the heading quaternion -- a small-angle approx the original kept valid via dense records; our pure-spin case has 1.6s record gaps so it diverges to ~180deg -> uneven render. Fix path = proper quaternion integration in the reckoner, or refresh the angular origin without the type-4 horizon reset. Both touch the shared engine dead-reckon -> aligning before the change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7615ecd316
commit
9eff043973
@@ -8577,6 +8577,37 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
const double presentMs = (double)(_rt2.QuadPart - _rt1.QuadPart) * 1000.0 / (double)_rf.QuadPart;
|
||||
static double sAcc = 0.0, sMaxD = 0.0, sMaxP = 0.0; static int sFrames = 0;
|
||||
sAcc += drawMs + presentMs; ++sFrames;
|
||||
// RENDER-frame heading probe (BT_RENDHDG): count render frames that redraw
|
||||
// the SAME peer heading (stale between sim updates) vs frames where it moved.
|
||||
// sameHeading >> moved => the peer sim updates slower than the render draws,
|
||||
// so the rotation stutters regardless of how smooth each sim step is.
|
||||
if (getenv("BT_RENDHDG"))
|
||||
{
|
||||
extern volatile float gBTReplRenderYaw;
|
||||
static float sPrevY = -999.0f, sMaxStep = 0.0f, sSumStep = 0.0f;
|
||||
static int sN = 0, sBack = 0; static double sHAcc = 0.0;
|
||||
const float y = gBTReplRenderYaw;
|
||||
if (sPrevY > -900.0f)
|
||||
{
|
||||
float d = y - sPrevY; // per-RENDER-frame heading change
|
||||
if (d > 3.14159f) d -= 6.28319f; // unwrap
|
||||
if (d < -3.14159f) d += 6.28319f;
|
||||
const float ad = (d < 0.0f) ? -d : d;
|
||||
sSumStep += ad; if (ad > sMaxStep) sMaxStep = ad;
|
||||
if (d > 0.0f) ++sBack; // spin is -yaw here; +step == backward
|
||||
++sN;
|
||||
}
|
||||
sPrevY = y; sHAcc += drawMs + presentMs;
|
||||
if (sHAcc >= 1000.0 && sN > 0)
|
||||
{
|
||||
const float avg = sSumStep / sN;
|
||||
DEBUG_STREAM << "[rendhdg] avgStep=" << avg << " maxStep=" << sMaxStep
|
||||
<< " max/avg=" << (avg > 1e-6f ? sMaxStep / avg : 0.0f)
|
||||
<< " backwardFrames=" << sBack << "/" << sN
|
||||
<< " (smooth: max/avg~1, backward~0)\n" << std::flush;
|
||||
sMaxStep = 0.0f; sSumStep = 0.0f; sN = 0; sBack = 0; sHAcc = 0.0;
|
||||
}
|
||||
}
|
||||
if (drawMs > sMaxD) sMaxD = drawMs;
|
||||
if (presentMs > sMaxP) sMaxP = presentMs;
|
||||
if (drawMs + presentMs > 150.0)
|
||||
|
||||
@@ -1863,6 +1863,9 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// Peer heading published each sim update, sampled by the render loop (BT_RENDHDG).
|
||||
volatile float gBTReplRenderYaw = -999.0f;
|
||||
|
||||
void
|
||||
Mech::PerformAndWatch(const Time& till, MemoryStream *update_stream)
|
||||
{
|
||||
@@ -1930,6 +1933,18 @@ void
|
||||
DeadReckon(dt); // engine: reckoner + lerp
|
||||
localToWorld = localOrigin;
|
||||
|
||||
// RENDER-vs-SIM decoupling probe: publish the heading the RENDER will
|
||||
// actually draw (localToWorld == localOrigin here). The render loop
|
||||
// (L4VIDEO.cpp) logs this PER RENDERED FRAME; if it repeats the same
|
||||
// value across render frames, the peer sim updates slower than the
|
||||
// render draws -> the rotation stutters even though each sim update is
|
||||
// smooth (which the per-update [replhdg] metric could not see).
|
||||
{
|
||||
extern volatile float gBTReplRenderYaw;
|
||||
YawPitchRoll _rry; _rry = localOrigin.angularPosition;
|
||||
gBTReplRenderYaw = (float)_rry.yaw;
|
||||
}
|
||||
|
||||
// Lightweight dead-reckon smoothness probe (one flush/sec, cheap).
|
||||
// PATH length (sum of |per-frame step|) vs NET displacement reveals
|
||||
// jitter: a smooth walk has path==net; oscillation inflates path while
|
||||
@@ -3587,6 +3602,11 @@ void
|
||||
angStepM.Multiply(updateVelocity.angularMotion, dt);
|
||||
projectedOrigin.angularPosition.Add(
|
||||
projectedOrigin.angularPosition, angStepM);
|
||||
// Adding a scaled angular-velocity VECTOR to a quaternion denormalizes
|
||||
// it (the reckoner MOVER.cpp:463 does the same); a non-unit quaternion's
|
||||
// extracted yaw is garbage (~pi), which spiked angDrift and FLOODED angle
|
||||
// resyncs in bursts -> peer horizon reset -> uneven (max/avg ~2.8x) render.
|
||||
projectedOrigin.angularPosition.Normalize();
|
||||
}
|
||||
Scalar angDb = (updateTurnAngleDeadband > 0.0f) ? updateTurnAngleDeadband : -1.0f;
|
||||
Scalar velDb = (updateTurnVelocityDeadband > 0.0f) ? updateTurnVelocityDeadband : -1.0f;
|
||||
|
||||
Reference in New Issue
Block a user