MP: replicant motion chop ROOT-CAUSED + FIXED -- stale peer-mirror spammed re-base

records; also the 2007 call-counter clock stub + two crash fixes

User-reported: peer mechs turn/move choppy ("missing frames"). Measured per-frame
(BT_REPL_HDG): 13% of frames the replicant's heading STALLED, 11% it JUMPED 2-6x.

THREE layered defects found:

1) THE CLOCK STUB (engine substrate): TIMESTUB.cpp's GetRTC/GetHiRes were 2007
   `return time++` call-counters -- the "clock" advanced per CALL, not per ms --
   and TIMESTUB won the /FORCE duplicate-symbol race over the REAL QPC clock in
   L4TIME.cpp (LNK4006). Every Now()-domain consumer (dead-reckon above all) ran
   on call-count pseudo-time. Removed TIMESTUB from the build; L4TIME covers every
   symbol. [The real clock alone did NOT cure the chop -- but it was objectively
   broken and un-gated the two latent bugs below.]

2) TWO CRASH FIXES the real clock exposed:
   - legAnimationState@0x3b0 never ctor-initialized (the task-#56 0xCDCDCDCD
     family): the type-3 writer re-dispatches SetBodyAnimation(legAnimationState)
     on the WRITER; a record emitted before the leg SM's first tick passed raw
     0xCDCDCDCD as a clip index -> AV (cdb-pinned, mech2.cpp:233). Init 0.
   - Replicants were SERIALIZING update records: the tail WriteSimulationUpdate ran
     for every instance, and the port's replicant leg-SM accommodation (task #50)
     calls ForceUpdate -> replicants emitted derived/uninitialized state into the
     stream. Master-gated; replicant marks discarded (master-authoritative).

3) THE CHOP ITSELF: the master's resync send-gate compares localOrigin vs
   projectedOrigin -- the PEER-ESTIMATE mirror -- but the port master never
   maintained projectedOrigin (the bring-up drive replaced Mover::Perform; the
   engine only updates the projection inside replicant-only DeadReckon). Stale
   mirror -> |local-projected| > deadband EVERY frame -> a type-4 re-base record
   EVERY frame -> the replicant hard-copied the master's deadband-quantized
   heading each frame (the engine lerp never engaged; nextUpdate always behind
   till) -> stall/snap beat = the chop. FIX: advance the mirror each frame by the
   last-SENT angular velocity (what the peer is extrapolating) and re-base it in
   the type-4 writer. Records now flow only on TRUE drift (~1 per 5 frames in a
   steady spin), the replicant extrapolates smoothly between them, and the lerp
   horizon finally engages. Measured: STALLS 13%->1%, JUMPS 11%->1% (residue: tiny
   sub-degree backward corrections on record arrival -- inherent dead-reckon
   overshoot the lerp absorbs).

Diag probes: BT_REPL_HDG (per-frame replicant heading + dead-reckon internals),
BT_REPL_TRN (replicant turn/leg state). scratchpad/clockcrash_bp.txt (cdb).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-14 10:08:47 -05:00
co-authored by Claude Fable 5
parent e6ad29d648
commit a3b735d5da
3 changed files with 66 additions and 3 deletions
+44 -2
View File
@@ -2000,6 +2000,24 @@ void
<< " spd=" << replMppr->speedDemand << "\n" << std::flush;
}
}
// PER-FRAME heading trace (BT_REPL_HDG, choppy-spin diagnosis): the
// replicant's rendered yaw each frame + dt. A smooth dead-reckoned
// spin advances ~yawRate*dt every frame; choppiness shows as heading
// STALLS followed by SNAPS (records arriving without integration
// between them).
if (getenv("BT_REPL_HDG") && yawRate != 0.0f)
{
YawPitchRoll rh, ph, uh;
rh = localOrigin.angularPosition;
ph = projectedOrigin.angularPosition;
uh = updateOrigin.angularPosition;
DEBUG_STREAM << "[replhdg] yaw=" << (Scalar)rh.yaw
<< " proj=" << (Scalar)ph.yaw
<< " upd=" << (Scalar)uh.yaw
<< " lp-lu=" << (lastPerformance.ticks - lastUpdate.ticks)
<< " nu-lp=" << (nextUpdate.ticks - lastPerformance.ticks)
<< " yawRate=" << yawRate << " dt=" << dt << "\n" << std::flush;
}
// REPLICANT BEAMS (task #51): the emitters carry live replicated
// discharge state (Emitter::ReadUpdateRecord); draw them with the
@@ -3356,6 +3374,20 @@ void
// UpdateTurnVelocityDiffrence. [T1 expressions; zero-deadband
// guard falls back to the old quat-w stand-in]
{
// PEER-ESTIMATE MIRROR (2026-07-14, replicant-chop fix): advance the
// projection by the last-SENT angular velocity each frame -- this is
// what the replicant's dead-reckoner is doing with the last record.
// The gate below then measures the peer's TRUE drift, so a steady
// turn (matching velocity) sends almost nothing and the replicant
// extrapolates smoothly, instead of the stale mirror firing a
// re-base record EVERY frame (the stall/snap chop). The type-4
// writer re-bases this mirror on each send (mech.cpp case 4).
{
Vector3D angStepM;
angStepM.Multiply(updateVelocity.angularMotion, dt);
projectedOrigin.angularPosition.Add(
projectedOrigin.angularPosition, angStepM);
}
Scalar angDb = (updateTurnAngleDeadband > 0.0f) ? updateTurnAngleDeadband : -1.0f;
Scalar velDb = (updateTurnVelocityDeadband > 0.0f) ? updateTurnVelocityDeadband : -1.0f;
Logical resync = False;
@@ -4333,8 +4365,18 @@ void
}
// Keep the simulation/networking bookkeeping consistent (this is exactly
// what the base "no time / stasis" early-out does).
WriteSimulationUpdate(update_stream);
// what the base "no time / stasis" early-out does). MASTER ONLY (2026-07-14):
// replication is master-authoritative -- a replicant must never serialize.
// The port's replicant runs the leg SM for JOINTS (task #50 accommodation),
// whose transitions call ForceUpdate() and mark updateModel; serializing those
// emitted derived/uninitialized state back into the stream (and the type-3
// writer re-dispatches SetBodyAnimation on the WRITER -- the real-clock crash
// rode this path with legAnimationState still 0xCDCDCDCD). Discard replicant
// marks instead.
if (GetInstance() != ReplicantInstance)
WriteSimulationUpdate(update_stream);
else
updateModel = 0; // drop accommodation-path marks
}