MP: the Mech-level update records @0x4a0c2c/@0x4a1232 -- all 9 types byte-exact (task #1)

- Writer transcribed from the recovered disasm; reader rewritten from
  part_012.c with every Wword absorber promoted to named engine/port members
  (updateOrigin/updateVelocity/projectedOrigin/projectedVelocity/nextUpdate/
  lastUpdate + bodyTargetSpeed/latches/alarms).  Wire sizes verified live:
  0x14/0x20/0x2c/0x78.
- movementMode UNIFIED with Simulation::simulationState (binary mech+0x40 =
  StateIndicator@0x2c currentState) -- death/limbo/airborne now replicate in
  every record header.  Three mislabels of the same binary fns retired:
  SetInstanceFlags + RequestActionFlags -> Mech::ForceUpdate (updateModel |=
  mask, 0xfe03 disabled filter); IsNetworkCopy -> IsDisabled.
- Senders wired byte-exact: gait transitions Force(8), knockdown Force(1|0x20),
  death Force(1|0x40), Reset Force(0x1f) + binary zero-set, perf-loop
  deadbands (speed type 2, orientation type 4, heat type 7).
- 2-node verified: types 2/3/4 flow while driving; kill -> type-6 record
  (simState=9) -> the OBSERVER's replicant runs the wreck sink loop with no
  double death transition; respawn 0x1f burst snaps + un-wrecks the peer;
  walking replicant un-regressed (run 12, cycle tracking); solo clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 11:38:49 -05:00
co-authored by Claude Fable 5
parent 3c34ae6de6
commit c9f0c2a7f1
6 changed files with 641 additions and 185 deletions
+61 -21
View File
@@ -318,7 +318,7 @@ Logical
// Advance the body gait (airborne flavour while jumping).
Scalar cycleDistance;
if ((movementMode == 3 || movementMode == 4) && jumpCapable) // 0x40, 0x580
if ((MovementMode() == 3 || MovementMode() == 4) && jumpCapable) // 0x40, 0x580
cycleDistance = AdvanceBodyAnimationAirborne(time_slice, loop); // FUN_004a5bf8
else
cycleDistance = AdvanceBodyAnimation(time_slice, loop); // FUN_004a5678
@@ -991,7 +991,7 @@ Logical
// vital hit run the WHOLE death transition again: double kill score, and a
// Score dispatched into the respawn window's severed playerVehicle -> the
// engine's Check(playerVehicle) abort (task #52 cdb catch).
if (movementMode == 2 || movementMode == 9)
if (MovementMode() == 2 || MovementMode() == 9)
return True;
return graphicAlarm.GetLevel() >= 9 ? True : False;
}
@@ -1039,9 +1039,14 @@ void
ReconQuatIdentity(&angularAccum, &kIdentityQuat);
ReconQuatIdentity(&aimRate, &kIdentityQuat);
// --- CLEAR THE DEATH LATCH: alive gait mode + no going-down alarm, so
// IsMechDestroyed() is false and the drive/render treat it as alive ---
movementMode = 1; // ground/alive (death settled it to 9)
// --- CLEAR THE DEATH LATCH (binary FUN_0049fb74: FUN_0041bbd8(+0x2c, 0)
// + the motion/replication scalars zeroed) ---
SetMovementMode(0); // simulationState 0 = init/reset; the
// drive re-selects 1 next frame [T1]
legCycleSpeed = 0.0f; // @0x348 (decomp Reset zeroes these)
bodyCycleSpeed = 0.0f; // @0x6b8
bodyTargetSpeed = 0.0f; // @0x6b4
poseSyncLatch = 0; // @0x77c
graphicAlarm.SetLevel(0); // clear >=9 (the vital-kill trigger)
// --- HEAL every damage zone: full structure, intact skin, no burning ---
@@ -1074,15 +1079,14 @@ void
BTRebuildMechModel((Entity *)this);
}
// --- broadcast the reset state to replicants (FUN_004a4c54(this, 0x1f)) ---
ForceUpdate();
// Also re-broadcast the (now-healed) damage zones so a REPLICANT observer sees
// the zone levels FALL from destroyed back to intact -- the falling edge that
// MechDeathHandler::Tick uses to un-wreck + warp the peer on the other screen
// (the master's Reset above un-wrecks + warps LOCALLY; without this the observer
// has no signal that the peer respawned and its wreck sits forever). Same
// channel the damage path uses (mechdmg.cpp:1016).
ForceUpdate(Entity::DamageZoneUpdateModelFlag);
// --- broadcast the reset state to replicants: the binary Reset tail is
// ForceUpdate(0x1f) -- record types 0-4 in one shot (pose + damage
// zones + speed + leg-state + orientation resync), replicant-gated on
// (flags+0x28 & 0xc) != 4. Bit 1 re-broadcasts the healed damage
// zones: the falling edge MechDeathHandler::Tick uses to un-wreck +
// warp the peer on the observer's screen. [T1 @0x49fdfc]
if (GetInstance() != ReplicantInstance)
ForceUpdate(0x1f);
if (getenv("BT_DEATH_LOG"))
DEBUG_STREAM << "[respawn] Mech::Reset " << GetEntityID()
@@ -1099,7 +1103,7 @@ void
{
if (!IsMechDestroyed()) // alive
return;
if (movementMode == 9) // already settled (disabled/frozen)
if (MovementMode() == 9) // already settled (disabled/frozen)
{
// WRECK SMOKE (port addition, [T3]): keep the dead hulk smoking -- re-fire
// the death/rubble smoke plume (psfx 1 = DDTHSMK, "the mech death/rubble
@@ -1180,7 +1184,16 @@ void
if (s != 0)
s->DeathShutdown(1);
}
movementMode = 9; // disabled: IsDisabled() true, locomotion frozen
// Request the DEATH record BEFORE entering the disabled state (the
// ForceUpdate filter masks types 2..8 once IsDisabled) -- the binary
// death sender is Force(1) + Force(0x40) (@0x4aab2f/@0x4aab3a). The
// record emits on the next update pump with simulationState 9 in its
// header; that flips the replicant's MovementMode, whose own
// UpdateDeathState then runs the wreck sink/burial on the OBSERVER's
// screen (this function runs for every mech, replicants included).
ForceUpdate(Simulation::DefaultUpdateModelFlag
| (1 << MechDeathUpdateModelBit));
SetMovementMode(9); // disabled: IsDisabled() true, locomotion frozen
if (getenv("BT_DEATH_LOG"))
DEBUG_STREAM << "[death] mech " << GetEntityID()
<< " destroyed -> subsystem shutdown + frozen wreck (IsDisabled="
@@ -1983,7 +1996,7 @@ void
if (!(forwardCycleRate >= 25.0f && forwardCycleRate < 10000.0f))
forwardCycleRate = 25.0f;
if (!IsMechDestroyed()) // a dead mech keeps its death movementMode
movementMode = 1; // ground, non-death, non-airborne
SetMovementMode(1); // ground, non-death, non-airborne
// reverseSpeedMax2@0x7a0 is the run-cycle bodyCycleSpeed CLAMP (AdvanceBody
// Animation case 12/13); LoadLocomotionClips does not set it -> it reads
// 0xCDCDCDCD (-4.3e8) and the clamp clobbers bodyCycleSpeed -> the run cycle
@@ -2529,12 +2542,37 @@ void
localOrigin.angularPosition);
if (
error.LengthSquared() > 0.04f
|| Abs(angular_deviation.w) < 0.997f
|| lastPerformance - lastUpdate > 2.0f
)
{
ForceUpdate();
ForceUpdate(); // type 0: position deadband + heartbeat
}
// ORIENTATION rides ONLY the type-4 resync record now (the
// authentic case-0 writer/reader save-restore strips it from the
// pose record) -- request it on angular deviation or the turn
// never reaches the replicant. Binary deadbands @0x4aac2b/
// @0x4aac6c diff the live vs last-replicated angular channels
// against model constants (+0x76c/+0x770, not yet streamed);
// the quat-deviation test stands in. [T3 threshold only]
if (Abs(angular_deviation.w) < 0.997f)
{
ForceUpdate(1 << MechResyncUpdateModelBit); // type 4
}
// Commanded-speed deadband (binary @0x4aac88): the mapper's live
// speedDemand vs the last-replicated bodyTargetSpeed -> the tiny
// type-2 record. This is the AUTHENTIC replicant-gait feed.
if (controlsMapper != 0
&& controlsMapper->speedDemand != bodyTargetSpeed)
{
ForceUpdate(1 << MechSpeedUpdateModelBit); // type 2
}
// Heat/impact-state change (binary @0x4aab75: heatAlarm level vs
// the frame-entry snapshot @0x780) -> the type-7 record.
if ((int)heatAlarm.GetLevel() != heatLevelSnapshot)
{
ForceUpdate(1 << MechImpactUpdateModelBit); // type 7
}
heatLevelSnapshot = (int)heatAlarm.GetLevel(); // @0x780 snapshot
if (getenv("BT_REPL_LOG"))
{
static float s_emitLog = 0.0f; s_emitLog += dt;
@@ -3366,8 +3404,10 @@ void
// Stagger the BODY channel too so both freeze + recover from the SAME bmp
// clip IN PHASE. BodyClipFinished case 32 recovers it (mech2.cpp:308).
SetBodyAnimation(0x20);
RequestActionFlags(1); // FUN_004a4c54(this, 1)
RequestActionFlags(0x20); // FUN_004a4c54(this, 0x20)
ForceUpdate(1); // FUN_004a4c54(this, 1): pose record
ForceUpdate(0x20); // FUN_004a4c54(this, 0x20): the
// type-5 KNOCKDOWN record -- the
// peer's replicant staggers too
if (GroundLog())
{
static int s_binds = 0;