diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c6dfee..577b1df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 0f71b33..162fe7c 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -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; } diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 354598a..d956306 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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 }