#67 torso replication, part 1: the dead-reckoning clock fix + the bug REPRODUCED on the rig

THE CLOCK FIX (landed, verified live).  The replicant torso's extrapolation
window was hardwired to ZERO: both shim accessors returned the same field
(torso.hpp, the "dormant in single-player" note).  The engine maintains both
clocks exactly as the binary expects -- WriteUpdateRecord stamps
lastUpdate=lastPerformance on the master, ReadUpdateRecord stamps
lastUpdate=Now() on the replicant (SIMULATE.cpp:276/296, the 1995 "HACK"
comment intact) -- so the fix is one mapping: GetCreationTime (misnamed; now
GetLastUpdateTime) reads lastUpdate.  ComputeTargetTwist's
`current - lastUpdate` window is real again.  Why legs always replicated
while torsos did not: Mover is ENGINE code reading these fields natively; the
Torso is our reconstruction with the collapsed shim.  Rig-verified: the
copy-side log now shows distinct lastUpd/now values.

THE FIELD BUG REPRODUCED (2-pod rig, thor vs thor -- thor because the first
attempt used FOGDAY's Black Hawk, whose torso is authentically FIXED like the
Owens: hEn=0, limits +/-0.01 deg):

    [torso-copy] cur=-3.31613 target=-3.31613 atUpd=-3750

The replicant renders at EXACTLY its twist limit (thor: +/-3.31613 rad)
because twistAtUpdate holds garbage (-3750) and the limit clamp slams it to
the stop -- "twisted full right but was not using TT", on demand.  An earlier
run poisoned it with 2.1e-44 (= int 15 as float, suspiciously the torso's
subsystem index).

NARROWED: the tx/rx probes added here (BT_TORSO_LOG prints every torso record
both directions, header + raw payload dwords) logged ZERO records in the
poisoned runs -- Torso::Read/WriteUpdateRecord never executed.  The garbage
therefore arrives OUTSIDE the torso record path: a raw state write during
mech spawn/consolidation spraying the torso's fields (stream-walker framing
or a consolidation blit).  That writer is part 2's hunt; the probes and the
deterministic repro make it a short one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-27 07:13:07 -05:00
co-authored by Claude Opus 5
parent 9bb95d8580
commit a3329a64da
2 changed files with 50 additions and 10 deletions
+34 -6
View File
@@ -341,7 +341,7 @@ Torso::Torso(
hitElevTop = hitElevBottom = 0; // @0x258 / @0x25C
hitTwistLeft = hitTwistRight = 0; // @0x260 / @0x264
recenterActive = 0; // @0x274
lastUpdateTime = GetCreationTime(); // @0x254 = this[5]
lastUpdateTime = GetLastUpdateTime(); // @0x254 = this[5]
// bring-up verification (env BT_TORSO_LOG): the gyro cross-links to
// (Torso*)+0x1D8 (== currentTwist) at a RAW offset (mech.cpp:740), so the
@@ -465,9 +465,9 @@ void
Torso::ReadUpdateRecord(UpdateRecord *message)
{
lastUpdateTime = GetCurrentTime(); // @0x254
if ((Scalar)(lastUpdateTime - GetCreationTime()) / MsPerSecond < MinSlewMs)
if ((Scalar)(lastUpdateTime - GetLastUpdateTime()) / MsPerSecond < MinSlewMs)
{
lastUpdateTime += (lastUpdateTime - GetCreationTime()); // stretch tiny windows
lastUpdateTime += (lastUpdateTime - GetLastUpdateTime()); // stretch tiny windows
}
Subsystem::ReadUpdateRecord(message); // FUN_0041bd34
@@ -475,6 +475,23 @@ void
twistAtUpdate = RecordField(message, 0x10); // @0x21C
twistVelocity = RecordField(message, 0x14); // @0x1E8
twistRate = RecordField(message, 0x18); // @0x238
// #67 stream forensics (BT_TORSO_LOG): the copy received atUpd=2.1e-44
// (= int 15 as float) on the rig -- a mis-framed record. Print the raw
// header + payload dwords of every record this torso consumes.
if (getenv("BT_TORSO_LOG"))
{
const unsigned int *raw = (const unsigned int *)message;
DEBUG_STREAM << "[torso-rec-rx] len=" << message->recordLength
<< " subID=" << (int)message->subsystemID
<< " recID=" << (int)message->recordID
<< std::hex
<< " p10=0x" << raw[4] << " p14=0x" << raw[5]
<< " p18=0x" << raw[6] << std::dec
<< " -> atUpd=" << twistAtUpdate
<< " vel=" << twistVelocity << " rate=" << twistRate
<< "\n" << std::flush;
}
}
//
@@ -503,6 +520,17 @@ void
WriteRecordField(message, 0x14, twistVelocity); // @0x1E8
WriteRecordField(message, 0x18, twistRate); // @0x238
twistAtUpdate = currentTwist; // @0x21C snapshot at send
// #67 stream forensics: what the MASTER actually serialized.
if (getenv("BT_TORSO_LOG"))
{
DEBUG_STREAM << "[torso-rec-tx] len=" << message->recordLength
<< " subID=" << (int)message->subsystemID
<< " recID=" << (int)message->recordID
<< " twist=" << currentTwist
<< " vel=" << twistVelocity << " rate=" << twistRate
<< "\n" << std::flush;
}
}
@@ -730,7 +758,7 @@ void
}
else
{
Scalar age = (Scalar)(lastUpdateTime - GetCreationTime()) / MsPerSecond;
Scalar age = (Scalar)(lastUpdateTime - GetLastUpdateTime()) / MsPerSecond;
currentTwist = Lerp(currentTwist, targetTwist, time_slice / (age + time_slice)); // FUN_004081e0
}
targetTwist = Min(targetTwist, horizontalLimitLeft); // @0x1E0
@@ -776,12 +804,12 @@ Logical
if (isDamagedCopy == 0 || lastUpdateTime <= GetCurrentTime())
{
base = GetCurrentTime() - GetCreationTime(); // this[0x10] - this[0x14]
base = GetCurrentTime() - GetLastUpdateTime(); // this[0x10] - this[0x14]
slewing = False;
}
else
{
base = lastUpdateTime - GetCreationTime(); // @0x254 - this[0x14]
base = lastUpdateTime - GetLastUpdateTime(); // @0x254 - this[0x14]
slewing = True;
}
+16 -4
View File
@@ -155,15 +155,27 @@ class Joint; // engine skeleton node (JOINT.h); the twist target
// HeatStateLevel <- HeatWatcher::heatAlarm level (this+0x140)
// ElectricalState <- PowerWatcher::watchdogAlarm level (this+0x198)
// HeatModelOff <- MechSubsystem::simulationState==Destroyed (this+0x40)
// Creation/Current <- engine Simulation::lastPerformance (this+0x14/0x10)
// the net/copy timing is dormant in single-player;
// equal reads give a 0 window == the shipped master path.
// Current <- engine Simulation::lastPerformance (this+0x10)
// LastUpdate <- engine Simulation::lastUpdate (this+0x14)
// #67 (2026-07-27): both accessors used to return
// lastPerformance ("dormant in single-player") --
// which zeroed the replicant torso's dead-reckoning
// window (ComputeTargetTwist: elapsed = current -
// lastUpdate) and broke remote torso twist in MP
// (mechs stuck twisted / muzzles at mech centre).
// The engine maintains BOTH fields exactly as the
// binary expects: WriteUpdateRecord stamps
// lastUpdate=lastPerformance on the master,
// ReadUpdateRecord stamps lastUpdate=Now() on the
// replicant (SIMULATE.cpp:276/296). Mover -- engine
// code reading these fields natively -- is why LEGS
// replicated fine while the torso did not.
// SetMovedFlag -> engine Simulation::ForceUpdate() dirty bit (this+0x18)
// (GetSegmentFlags removed -- unused; the ctor reads owner->simulationFlags.)
HeatSink::HeatState HeatStateLevel() const { return (HeatSink::HeatState)heatAlarm.GetLevel(); }
Logical HeatModelOff() const { return simulationState == 1; /* Destroyed */ }
PoweredSubsystem::ElectricalState ElectricalStateLevel() const{ return (PoweredSubsystem::ElectricalState)watchdogAlarm.GetLevel(); }
LWord GetCreationTime() const { return (LWord)(long)lastPerformance; }
LWord GetLastUpdateTime() const { return (LWord)(long)lastUpdate; }
LWord GetCurrentTime() const { return (LWord)(long)lastPerformance; }
// Current horizontal (yaw) torso aim in radians (binary torso+0x1d8); the
// damage-table PieSlice wheel reads this when RotateWithTorso is set.