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
+6 -1
View File
@@ -157,7 +157,12 @@ add_library(munga_engine STATIC
"engine/MUNGA/TERRAIN.cpp"
"engine/MUNGA/TESTALL.cpp"
"engine/MUNGA/TIME.cpp"
"engine/MUNGA/TIMESTUB.cpp"
# TIMESTUB.cpp REMOVED (2026-07-14): its GetRTC/GetHiRes were 2007 call-counter
# stubs (`return time++` -- the "clock" advanced per CALL, not per ms) that WON
# the /FORCE duplicate-symbol race over the REAL QueryPerformanceCounter clock
# in L4TIME.cpp (LNK4006 "second definition ignored"). Every Now()-domain
# consumer (dead-reckoning above all) ran on call-count pseudo-time -> the
# replicant stall/jump chop. L4TIME.cpp covers every symbol TIMESTUB defined.
"engine/MUNGA/TOOL.cpp"
"engine/MUNGA/TRACE.cpp"
"engine/MUNGA/TRACSTUB.cpp"
+16
View File
@@ -870,6 +870,13 @@ Mech::Mech(
// dispatch (task #56) gates on it every frame; garbage
// nonzero silently disabled the writers until death anims ran
legResetLatch = 0; // @0x654 -- same latch family, same fill hazard
legAnimationState = 0; // @0x3b0 -- same hazard (found 2026-07-14, the real-clock
// crash): the type-3 record writer re-dispatches
// SetBodyAnimation(legAnimationState) on itself; a record
// emitted before the leg SM's first tick passed the raw
// 0xCDCDCDCD as a clip index -> AV in SetBodyAnimation.
// (The stub call-counter clock had merely re-ordered record
// emission so the window was never hit.) Standing = 0.
attrPad = 0.0f; // shared read pad for un-populated gauge attributes
linearSpeed = 0.0f; // live forward ground speed (LinearSpeed gauge); set per frame
radarRange = 1000.0f; // radar display scale (SetTargetRange is stubbed;
@@ -2091,6 +2098,15 @@ void
}
updateOrigin.angularPosition = localOrigin.angularPosition; // FUN_00409968(0x138 <- 0x10c)
updateVelocity.angularMotion = localVelocity.angularMotion; // FUN_00408440(0x2d4 <- 0x1d0)
// RE-BASE the master's peer-estimate mirror (2026-07-14, replicant-chop
// fix): projectedOrigin/projectedVelocity model what the PEER will now
// extrapolate from this record. The send gate compares local vs projected
// (the peer's drift); without the re-base the mirror went stale, the gate
// fired EVERY frame, and the per-frame records re-based the replicant into
// the stall/snap chop. (The binary maintains this in the un-decompiled
// master perf; the writer-side re-base is the coherent reconstruction.)
projectedOrigin.angularPosition = localOrigin.angularPosition;
projectedVelocity.angularMotion = localVelocity.angularMotion;
record->speedDemand = speedDemand; // rec+0x28
bodyTargetSpeed = speedDemand;
}
+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
}