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:
arcattack
2026-07-14 16:27:04 -05:00
co-authored by Claude Opus 4.8
parent 7615ecd316
commit 9eff043973
2 changed files with 51 additions and 0 deletions
+31
View File
@@ -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)