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:
co-authored by
Claude Fable 5
parent
3c34ae6de6
commit
c9f0c2a7f1
+384
-110
@@ -388,7 +388,6 @@ template<class...A> inline void CloseNotation(A&&...) {}
|
||||
template<class...A> inline void ParseVector(A&&...) {}
|
||||
template<class...A> inline int ParseJointResource(A&&...) { return 0; }
|
||||
template<class...A> inline int CreateModelResourceBase(A&&...){ return 0; }
|
||||
template<class...A> inline int IsNetworkCopy(A&&...) { return 0; }
|
||||
template<class...A> inline void StreamDamageZone(A&&...) {}
|
||||
template<class...A> inline void StreamDamageLookup(A&&...) {}
|
||||
|
||||
@@ -870,13 +869,16 @@ Mech::Mech(
|
||||
creationTime = Now(); // FUN_00414b60 -> Wword(0x1de)
|
||||
Wword(0xd2) = 0;
|
||||
Wword(0x1ae) = 0;
|
||||
Wword(0x1ad) = 0;
|
||||
bodyTargetSpeed = 0.0f; // @0x6b4 (was absorbed Wword(0x1ad))
|
||||
|
||||
//
|
||||
// Seed the pose: identity body rotation, identity aim quaternions.
|
||||
// Seed the pose + the dead-reckon baselines (task #1 corrections: the
|
||||
// binary's this[0x98]/this[0x4b] Origin copies are projectedOrigin@0x260
|
||||
// and updateOrigin@0x12c seeded from localOrigin@0x100 -- the ENGINE
|
||||
// inherited members, not mech-local "torso" fields).
|
||||
//
|
||||
torsoRotation = bodyRotation; // FUN_0040a938(this+0x98,this+0x40)
|
||||
Wword(0x4b) = bodyRotation; // FUN_0040a938(this+0x4b,this+0x40)
|
||||
projectedOrigin = localOrigin; // FUN_0040a938(this+0x98,this+0x40)
|
||||
updateOrigin = localOrigin; // FUN_0040a938(this+0x4b,this+0x40)
|
||||
Wword(0x163) = Wword(0x40); // FUN_00408440(this+0x163,this+0x40)
|
||||
turretBase.SetIdentity(); // FUN_0040a7f4(this+0x71, IdentityQuat)
|
||||
torsoAimCurrent.SetIdentity(); // this+0xa6
|
||||
@@ -888,7 +890,8 @@ Mech::Mech(
|
||||
Wword(0x166) = ZeroVector;
|
||||
Wword(0x169) = 0;
|
||||
Wword(0xe6) = 0;
|
||||
Wword(0x1df) = 0;
|
||||
poseSyncLatch = 0; // @0x77c (was absorbed Wword(0x1df))
|
||||
heatLevelSnapshot = 0; // @0x780 (type-7 deadband baseline)
|
||||
Wword(0xfe) = Wword(0xfc) = Wword(0x105) = 0;
|
||||
mechName.Copy(&DAT_004e0f8c); // FUN_00408e90(this+0xd8,"")
|
||||
Wword(0x160) = Wword(0x15f) = 0;
|
||||
@@ -897,7 +900,8 @@ Mech::Mech(
|
||||
Wword(0x20b).SetIdentity();
|
||||
Wword(0x1e7) = Wword(0x1dd) = Wword(0x209) = 0;
|
||||
Wword(0xd5) = Wword(0xd6) = Wword(0xd7) = 0;
|
||||
Wword(0x12d) = 0;
|
||||
fallScalar = 0.0f; // @0x4b4 (was absorbed Wword(0x12d))
|
||||
fallDirection = Vector3D(0.0f, 0.0f, 1.0f); // @0x4a8 fall/impact record payload
|
||||
throttleState = 2; // Wword(0x129) = 2
|
||||
Wword(0x1e0) = Wword(0x119);
|
||||
|
||||
@@ -1643,161 +1647,431 @@ Logical
|
||||
//###########################################################################
|
||||
// ReadUpdateRecord -- @004a1232 (vtable slot 6)
|
||||
//
|
||||
// Applies a streamed update packet. The packet's 16-bit type field (+6)
|
||||
// selects how much of the Mech's replicated state is overwritten. After the
|
||||
// switch, a torso/leg state transition re-syncs the stability alarm.
|
||||
// Applies one Mech-level replication record. The switch key is the
|
||||
// inherited Simulation::UpdateRecord recordID (u16 @+6) == the updateModel
|
||||
// bit that requested the record on the master. Byte-exact against
|
||||
// part_012.c:9576-9692, with every binary offset mapped to the NAMED
|
||||
// engine/port member (task #1, 2026-07-11; the old Wword-absorber body is
|
||||
// gone). Cross-checked field-for-field against the writer @004a0c2c. [T1]
|
||||
//
|
||||
// NOTE: the decomp recovers the body via the EBP frame (it is invoked through
|
||||
// the vtable as a member fn); param shapes below are reconstructed.
|
||||
// Base chains: case 0 / default -> the JointedMover-level pair
|
||||
// (FUN_0042249c = Mover::ReadUpdateRecord); cases 2..8 -> ONLY the
|
||||
// Simulation base (FUN_0041bd34: lastUpdate + SetSimulationState) -- the
|
||||
// old reconstruction chained JointedMover everywhere, which misparsed the
|
||||
// short records.
|
||||
//
|
||||
// simulationState rides EVERY record header (+0xC) and the base reader
|
||||
// applies it -- so the replicant's MovementMode() (death 9 / limbo 2 /
|
||||
// airborne 3,4) tracks the master automatically; the case-0 / tail edge
|
||||
// tests below then see the old->current transition.
|
||||
//
|
||||
void
|
||||
Mech::ReadUpdateRecord(Simulation::UpdateRecord *message)
|
||||
{
|
||||
Mech__UpdateRecord *record = (Mech__UpdateRecord *)message;
|
||||
|
||||
switch (record->recordType) // *(u16*)(msg+6)
|
||||
static const int s_mrecLog = getenv("BT_REPL_LOG") ? 1 : 0;
|
||||
if (s_mrecLog && message->recordID != 0)
|
||||
DEBUG_STREAM << "[mrec-rx] type=" << (int)message->recordID
|
||||
<< " ent=" << GetEntityID() << " len=" << (int)message->recordLength
|
||||
<< " simState=" << (int)message->simulationState << "\n" << std::flush;
|
||||
switch (message->recordID) // u16 @+6
|
||||
{
|
||||
case 0: // full pose snapshot
|
||||
case 0: // full pose (Mover record + speedDemand tail)
|
||||
{
|
||||
Vector3D savedPos = worldPosition; // Wword(0x4e)
|
||||
EulerAngles savedRot = netOrientation; // Wword(0xb5)
|
||||
JointedMover::ReadUpdateRecord(message); // FUN_0042249c
|
||||
worldPosition = savedPos;
|
||||
netOrientation = savedRot;
|
||||
Wword(0x1df) = 1;
|
||||
if (throttleState == 2 || throttleState == 3) // Wword(0x1ca)
|
||||
Mech__PoseUpdateRecord *record = (Mech__PoseUpdateRecord *)message;
|
||||
|
||||
// The mech OWNS its replicated-orientation channel (the type-4
|
||||
// resync record): save updateOrigin's rotation (quat->euler
|
||||
// roundtrip, exactly the binary's FUN_00408f44/FUN_00409a00) and
|
||||
// the update angular velocity around the base reader, which would
|
||||
// otherwise overwrite both from the pose record.
|
||||
EulerAngles savedRot;
|
||||
savedRot = updateOrigin.angularPosition; // FUN_00408f44 (quat->euler)
|
||||
Vector3D savedAngV = updateVelocity.angularMotion; // FUN_00408440
|
||||
|
||||
Mover::ReadUpdateRecord(message); // FUN_0042249c
|
||||
|
||||
updateOrigin.angularPosition = savedRot; // FUN_00409a00 (euler->quat)
|
||||
updateVelocity.angularMotion = savedAngV; // FUN_00408440
|
||||
poseSyncLatch = 1; // @0x77c -- arm the dead-reckon re-base
|
||||
|
||||
if (bodyAnimationState == 2 || bodyAnimationState == 3) // @0x728
|
||||
{
|
||||
torsoAimCurrent.SetIdentity(); // this+0xa6
|
||||
projectedVelocity.linearMotion = ZeroVector; // FUN_0040a7f4(0x298, zeroMotion)
|
||||
projectedVelocity.angularMotion = ZeroVector;
|
||||
}
|
||||
// torso/leg sync when the move state flips to/from "2"
|
||||
if (Wword(0x10) != Wword(0xf) && (Wword(0xf) == 2 || Wword(0x10) == 2))
|
||||
// Enter/leave simulation state 2 (disabled/limbo): re-base the
|
||||
// local + projected origins onto the freshly replicated
|
||||
// updateOrigin and drop the re-base latch.
|
||||
if (GetSimulationState() != GetOldSimulationState()
|
||||
&& (GetOldSimulationState() == 2 || GetSimulationState() == 2))
|
||||
{
|
||||
torsoRotation.Mul(Wword(0x4b));
|
||||
torsoAimCurrent.Mul(Wword(0x4b));
|
||||
torsoAimCurrent = torsoAimTarget;
|
||||
Wword(0x1df) = 0;
|
||||
if ((instanceFlags & 0x40) == 0)
|
||||
localOrigin = updateOrigin; // FUN_0040a938(0x100 <- 0x12c)
|
||||
projectedOrigin = updateOrigin; // FUN_0040a938(0x260 <- 0x12c)
|
||||
projectedVelocity = updateVelocity; // FUN_0040a7f4(0x298 <- 0x2c8)
|
||||
poseSyncLatch = 0;
|
||||
if ((instanceFlags & 0x4000) == 0) // byte(this+0x29)&0x40 [T4 bit name]
|
||||
{
|
||||
ResetPose(); // (**(this+0x34))(this)
|
||||
ResetPose(); // vcall vtable+0x34 (slot 13) [T4]
|
||||
}
|
||||
}
|
||||
Wword(0x1ad) = record->sequence; // puVar1[0x1d]
|
||||
bodyTargetSpeed = record->speedDemand; // @0x6b4 <- rec+0x74
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // alarm-only
|
||||
JointedMover::ReadUpdateRecord(message); // FUN_0041bd34
|
||||
Wword(0x1ad) = record->field4;
|
||||
case 2: // commanded-speed update
|
||||
{
|
||||
Mech__SpeedUpdateRecord *record = (Mech__SpeedUpdateRecord *)message;
|
||||
Simulation::ReadUpdateRecord(message); // FUN_0041bd34
|
||||
bodyTargetSpeed = record->speedDemand; // @0x6b4 <- rec+0x10
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // score / heat state
|
||||
JointedMover::ReadUpdateRecord(message);
|
||||
Wword(0x196) = record->field4;
|
||||
stabilityAlarm.SetLevel(record->field6);
|
||||
if (record->field5 == 0)
|
||||
case 3: // leg/body state + stability
|
||||
{
|
||||
controllableSubsystems.Reset(); // FUN_004283b8
|
||||
statusAlarm.SetLevel(record->field5);
|
||||
}
|
||||
else if (record->field5 == 1)
|
||||
{
|
||||
statusAlarm.SetLevel(record->field5);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatusState(record->field5); // FUN_004a800c [mech2]
|
||||
}
|
||||
Wword(0x1ad) = record->field7;
|
||||
break;
|
||||
|
||||
case 4: // pose + server-time re-sync
|
||||
{
|
||||
JointedMover::ReadUpdateRecord(message);
|
||||
creationTime = Now(); // Wword(0x1de)
|
||||
worldPosition = record->position; // this+0x4e <- msg+4
|
||||
netOrientation = record->orientation; // this+0xb5 <- msg+7
|
||||
Wword(0xb8) = Now();
|
||||
Scalar age = (Scalar)(Wword(0xb8) - Wword(5)) / FrameTimeScale;
|
||||
if (age < ReSyncSpeedThreshold) // 10.0f
|
||||
Mech__StateUpdateRecord *record = (Mech__StateUpdateRecord *)message;
|
||||
Simulation::ReadUpdateRecord(message);
|
||||
bodyResetLatch = record->legResetLatch; // @0x658 <- rec+0x10
|
||||
stabilityAlarm.SetLevel(record->stability); // @0x4c4 <- rec+0x18
|
||||
if (record->legState == 0)
|
||||
{
|
||||
Wword(0xb8) += (Wword(0xb8) - Wword(5)); // extrapolate
|
||||
legAnimation.Reset(1); // FUN_004283b8(0x65c, 1)
|
||||
bodyStateAlarm.SetLevel(record->legState); // @0x714
|
||||
}
|
||||
Wword(0x1ad) = record->fieldA;
|
||||
else if (record->legState == 1)
|
||||
{
|
||||
bodyStateAlarm.SetLevel(record->legState);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetBodyAnimation(record->legState); // FUN_004a800c
|
||||
}
|
||||
bodyTargetSpeed = record->speedDemand; // rec+0x1c
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // subsystem alarm (variant A)
|
||||
JointedMover::ReadUpdateRecord(message);
|
||||
heatAlarm.SetLevel(record->field4); // this+0x114
|
||||
Wword(0x129) = record->field5;
|
||||
Wword(0x12a) = record->field6;
|
||||
Wword(0x12d) = record->field9;
|
||||
SetStatusState(0x20); // FUN_004a800c [mech2]
|
||||
torsoAimCurrent.SetIdentity();
|
||||
Wword(0x1ad) = record->fieldA;
|
||||
case 4: // orientation + angular-velocity re-sync
|
||||
{
|
||||
Mech__ResyncUpdateRecord *record = (Mech__ResyncUpdateRecord *)message;
|
||||
Simulation::ReadUpdateRecord(message);
|
||||
creationTime = Now(); // @0x778 -- the dead-reckon ref time
|
||||
{
|
||||
EulerAngles e(Radian(record->eulerX), Radian(record->eulerY),
|
||||
Radian(record->eulerZ));
|
||||
updateOrigin.angularPosition = e; // FUN_00409a00 (euler->quat)
|
||||
}
|
||||
updateVelocity.angularMotion = record->angularVelocity; // @0x2d4 <- rec+0x1c
|
||||
nextUpdate = Now(); // @0x2e0
|
||||
{
|
||||
// extrapolate the next-update horizon unless the record is stale
|
||||
Scalar age = (Scalar)(nextUpdate.ticks - lastUpdate.ticks) / FrameTimeScale;
|
||||
if (age < ReSyncSpeedThreshold) // 10.0f (_DAT_004a1670)
|
||||
{
|
||||
nextUpdate.ticks += (nextUpdate.ticks - lastUpdate.ticks);
|
||||
}
|
||||
}
|
||||
bodyTargetSpeed = record->speedDemand; // rec+0x28
|
||||
}
|
||||
break;
|
||||
|
||||
case 6: // subsystem alarm (variant B -- death)
|
||||
JointedMover::ReadUpdateRecord(message);
|
||||
heatAlarm.SetLevel(record->field4);
|
||||
Wword(0x129) = record->field5;
|
||||
Wword(0x12a) = record->field6;
|
||||
Wword(0x12d) = record->field9;
|
||||
statusAlarm.SetLevel(0);
|
||||
Wword(0x195) = Wword(0x196) = 1;
|
||||
torsoAimCurrent.SetIdentity();
|
||||
Wword(0x1ad) = record->fieldA;
|
||||
case 5: // knockdown
|
||||
{
|
||||
Mech__FallUpdateRecord *record = (Mech__FallUpdateRecord *)message;
|
||||
Simulation::ReadUpdateRecord(message);
|
||||
heatAlarm.SetLevel(record->heatLevel); // @0x450 <- rec+0x10
|
||||
throttleState = record->throttleState; // @0x4a4 <- rec+0x14
|
||||
fallDirection = record->fallDirection; // @0x4a8 <- rec+0x18
|
||||
fallScalar = record->fallScalar; // @0x4b4 <- rec+0x24
|
||||
SetBodyAnimation(0x20); // FUN_004a800c -- the knockdown clip
|
||||
projectedVelocity.linearMotion = ZeroVector; // FUN_0040a7f4(0x298, zeroMotion)
|
||||
projectedVelocity.angularMotion = ZeroVector;
|
||||
bodyTargetSpeed = record->speedDemand; // rec+0x28
|
||||
}
|
||||
break;
|
||||
|
||||
case 7: // subsystem alarm (variant C)
|
||||
JointedMover::ReadUpdateRecord(message);
|
||||
heatAlarm.SetLevel(record->field4);
|
||||
Wword(0x129) = record->field5;
|
||||
Wword(0x12a) = record->field6;
|
||||
Wword(0x12d) = record->field9;
|
||||
Wword(0x1ad) = record->fieldA;
|
||||
case 6: // death
|
||||
{
|
||||
Mech__FallUpdateRecord *record = (Mech__FallUpdateRecord *)message;
|
||||
Simulation::ReadUpdateRecord(message);
|
||||
heatAlarm.SetLevel(record->heatLevel);
|
||||
throttleState = record->throttleState;
|
||||
fallDirection = record->fallDirection;
|
||||
fallScalar = record->fallScalar;
|
||||
bodyStateAlarm.SetLevel(0); // @0x714
|
||||
legResetLatch = 1; // @0x654 (reader sets BOTH latches;
|
||||
bodyResetLatch = 1; // @0x658 the writer only 0x658)
|
||||
projectedVelocity.linearMotion = ZeroVector;
|
||||
projectedVelocity.angularMotion = ZeroVector;
|
||||
bodyTargetSpeed = record->speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
case 8: // single scalar
|
||||
JointedMover::ReadUpdateRecord(message);
|
||||
Wword(0xfd) = record->field4;
|
||||
Wword(0x1ad) = record->field5;
|
||||
case 7: // impact (fields only, no side effects)
|
||||
{
|
||||
Mech__FallUpdateRecord *record = (Mech__FallUpdateRecord *)message;
|
||||
Simulation::ReadUpdateRecord(message);
|
||||
heatAlarm.SetLevel(record->heatLevel);
|
||||
throttleState = record->throttleState;
|
||||
fallDirection = record->fallDirection;
|
||||
fallScalar = record->fallScalar;
|
||||
bodyTargetSpeed = record->speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
JointedMover::ReadUpdateRecord(message); // FUN_0042249c
|
||||
case 8: // airborne/reverse cycle-rate selector
|
||||
{
|
||||
Mech__AirborneUpdateRecord *record = (Mech__AirborneUpdateRecord *)message;
|
||||
Simulation::ReadUpdateRecord(message);
|
||||
airborneSelect = record->airborne; // @0x3f4 <- rec+0x10
|
||||
bodyTargetSpeed = record->speedDemand; // rec+0x14
|
||||
}
|
||||
break;
|
||||
|
||||
default: // type 1 (damage zones) + anything unknown -> the base chain
|
||||
Mover::ReadUpdateRecord(message); // FUN_0042249c
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Re-fire the stability alarm when the leg state transitions into "2".
|
||||
// Tail (shared with the writer @004a11d9): when the mech just LEFT
|
||||
// simulation state 2 (re-activated from limbo), the binary rebuilt this
|
||||
// entity's floating name bitmap (FUN_00436668 NameBitmapEntry +
|
||||
// FUN_004364e4 RendererManager::ReplaceNameBitmap). That per-entry API
|
||||
// belonged to the Division IG renderer and was removed in the WinTesla
|
||||
// port -- the bulk SortAndReloadNameBitmaps refresh subsumes it (port
|
||||
// precedent: btl4pb.cpp:560). The state edge is then explicitly
|
||||
// acknowledged (SetState(current) collapses oldState = currentState).
|
||||
//
|
||||
if (Wword(0xf) != Wword(0x10) && Wword(0xf) == 2)
|
||||
if (GetOldSimulationState() != GetSimulationState()
|
||||
&& GetOldSimulationState() == 2)
|
||||
{
|
||||
StabilityMessage msg; // FUN_00436668
|
||||
BroadcastToTeam(msg); // FUN_004364e4
|
||||
stabilityAlarm.SetLevel(Wword(0x10)); // FUN_0041bbd8(this+0xb,...)
|
||||
SetSimulationState(GetSimulationState()); // FUN_0041bbd8(this+0x2c, cur)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//###########################################################################
|
||||
// SetInstanceFlags -- @004a4c54
|
||||
// WriteUpdateRecord -- @004a0c2c (vtable slot 7)
|
||||
//
|
||||
// OR a flag word into the entity instance flags (this+0x18); copies (network
|
||||
// replicants) are stripped of all but the low/high reserved bits.
|
||||
// Serializes one Mech-level record; the jump-table type == the updateModel
|
||||
// bit that requested it (see Mech::ForceUpdate and the senders in
|
||||
// mech2/mech4). Transcribed instruction-for-instruction from the recovered
|
||||
// disasm (reference/decomp/mech_writeupdate_004a0c2c.disasm.txt), offsets
|
||||
// mapped to named members (task #1, 2026-07-11). [T1]
|
||||
//
|
||||
// Conventions the engine requires: recordLength must equal each type's
|
||||
// exact size (the receiver frames the stream on it, ENTITY.cpp:390) and
|
||||
// subsystemID = 0 routes the record to the entity itself. The trailing
|
||||
// speedDemand stamp mirrors into bodyTargetSpeed@0x6b4 -- the master-side
|
||||
// "last replicated" baseline the perf-loop deadbands diff against.
|
||||
//
|
||||
void
|
||||
Mech::SetInstanceFlags(Word flags)
|
||||
Mech::WriteUpdateRecord(Simulation::UpdateRecord *message, int record_type)
|
||||
{
|
||||
if (IsNetworkCopy()) // FUN_0049fb54
|
||||
// The binary stamps *(subsystemArray[0] + 0x128) = the controls mapper's
|
||||
// speedDemand into every record tail. Our live mapper handle is
|
||||
// controlsMapper (the [0x10d] cache); replicants have no pilot input.
|
||||
// (Bridge fn: this TU carries a local recon stub under the mapper's name,
|
||||
// so the bridge is type-erased; mechmppr.cpp casts to the real class.)
|
||||
extern Scalar BTMapperSpeedDemandRaw(void *mapper);
|
||||
Scalar speedDemand = BTMapperSpeedDemandRaw((void *)controlsMapper);
|
||||
|
||||
static const int s_mrecLog = getenv("BT_REPL_LOG") ? 1 : 0;
|
||||
if (s_mrecLog && record_type != 0)
|
||||
DEBUG_STREAM << "[mrec-tx] type=" << record_type
|
||||
<< " ent=" << GetEntityID() << " state=" << (int)GetSimulationState()
|
||||
<< "\n" << std::flush;
|
||||
|
||||
switch (record_type)
|
||||
{
|
||||
flags &= 0xfe03;
|
||||
case 0: // full pose @4a0c79
|
||||
{
|
||||
Mech__PoseUpdateRecord *record = (Mech__PoseUpdateRecord *)message;
|
||||
|
||||
EulerAngles savedRot;
|
||||
savedRot = updateOrigin.angularPosition; // FUN_00408f44 (quat->euler)
|
||||
Vector3D savedAngV = updateVelocity.angularMotion; // FUN_00408440
|
||||
|
||||
Mover::WriteUpdateRecord(message, record_type); // FUN_004225a4
|
||||
|
||||
record->subsystemID = 0; // word @+4
|
||||
poseSyncLatch = 1; // @0x77c = 1
|
||||
updateOrigin.angularPosition = savedRot; // FUN_00409a00 restore
|
||||
updateVelocity.angularMotion = savedAngV; // FUN_00408440 restore
|
||||
|
||||
if (legAnimationState == 2 || legAnimationState == 3) // @0x3b0
|
||||
{
|
||||
projectedVelocity.linearMotion = ZeroVector; // FUN_0040a7f4(0x298, zero)
|
||||
projectedVelocity.angularMotion = ZeroVector;
|
||||
}
|
||||
if (GetOldSimulationState() != GetSimulationState()
|
||||
&& (GetOldSimulationState() == 2 || GetSimulationState() == 2))
|
||||
{
|
||||
projectedOrigin = updateOrigin; // FUN_0040a938(0x260 <- 0x12c)
|
||||
projectedVelocity = updateVelocity; // FUN_0040a7f4(0x298 <- 0x2c8)
|
||||
poseSyncLatch = 0;
|
||||
}
|
||||
record->speedDemand = speedDemand; // rec+0x74
|
||||
bodyTargetSpeed = speedDemand; // @0x6b4
|
||||
record->recordLength = 0x78;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // leg/body state + stability @4a0d84
|
||||
{
|
||||
Mech__StateUpdateRecord *record = (Mech__StateUpdateRecord *)message;
|
||||
Simulation::WriteUpdateRecord(message, record_type); // FUN_0041bd60
|
||||
record->recordLength = 0x20;
|
||||
record->subsystemID = 0;
|
||||
record->legResetLatch = legResetLatch; // rec+0x10 <- @0x654
|
||||
record->legState = legAnimationState; // rec+0x14 <- @0x3b0
|
||||
record->stability = (int)stabilityAlarm.GetLevel(); // rec+0x18 <- @0x4d8
|
||||
bodyResetLatch = record->legResetLatch; // @0x658 <- rec+0x10
|
||||
// the writer runs the SAME state dispatch on itself:
|
||||
if (record->legState == 0)
|
||||
{
|
||||
legAnimation.Reset(1); // FUN_004283b8(0x65c, 1)
|
||||
bodyStateAlarm.SetLevel(record->legState);
|
||||
}
|
||||
else if (record->legState == 1)
|
||||
{
|
||||
bodyStateAlarm.SetLevel(record->legState);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetBodyAnimation(record->legState); // FUN_004a800c
|
||||
}
|
||||
record->speedDemand = speedDemand; // rec+0x1c
|
||||
bodyTargetSpeed = speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // commanded speed @4a0e5b
|
||||
{
|
||||
Mech__SpeedUpdateRecord *record = (Mech__SpeedUpdateRecord *)message;
|
||||
Simulation::WriteUpdateRecord(message, record_type);
|
||||
record->recordLength = 0x14;
|
||||
record->subsystemID = 0;
|
||||
record->speedDemand = speedDemand; // rec+0x10
|
||||
bodyTargetSpeed = speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // orientation + angular-velocity re-sync @4a0e9e
|
||||
{
|
||||
Mech__ResyncUpdateRecord *record = (Mech__ResyncUpdateRecord *)message;
|
||||
Simulation::WriteUpdateRecord(message, record_type);
|
||||
record->recordLength = 0x2c;
|
||||
record->subsystemID = 0;
|
||||
localOrigin.angularPosition.Normalize(); // FUN_0040a138(0x10c)
|
||||
{
|
||||
EulerAngles e;
|
||||
e = localOrigin.angularPosition; // FUN_00408f44 (quat->euler)
|
||||
record->eulerX = e.pitch; // rec+0x10
|
||||
record->eulerY = e.yaw;
|
||||
record->eulerZ = e.roll;
|
||||
}
|
||||
record->angularVelocity = localVelocity.angularMotion; // rec+0x1c <- @0x1d0
|
||||
creationTime = Now(); // @0x778
|
||||
nextUpdate = Now(); // @0x2e0
|
||||
{
|
||||
Scalar age = (Scalar)(nextUpdate.ticks - lastUpdate.ticks) / FrameTimeScale;
|
||||
if (age < ReSyncSpeedThreshold) // 10.0f
|
||||
{
|
||||
nextUpdate.ticks += (nextUpdate.ticks - lastUpdate.ticks);
|
||||
}
|
||||
}
|
||||
updateOrigin.angularPosition = localOrigin.angularPosition; // FUN_00409968(0x138 <- 0x10c)
|
||||
updateVelocity.angularMotion = localVelocity.angularMotion; // FUN_00408440(0x2d4 <- 0x1d0)
|
||||
record->speedDemand = speedDemand; // rec+0x28
|
||||
bodyTargetSpeed = speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // knockdown @4a0fab
|
||||
{
|
||||
Mech__FallUpdateRecord *record = (Mech__FallUpdateRecord *)message;
|
||||
Simulation::WriteUpdateRecord(message, record_type);
|
||||
record->recordLength = 0x2c;
|
||||
record->subsystemID = 0;
|
||||
record->heatLevel = (int)heatAlarm.GetLevel(); // rec+0x10 <- @0x464
|
||||
record->throttleState = throttleState; // rec+0x14 <- @0x4a4
|
||||
record->fallDirection = fallDirection; // rec+0x18 <- @0x4a8
|
||||
record->fallScalar = fallScalar; // rec+0x24 <- @0x4b4
|
||||
SetBodyAnimation(0x20); // FUN_004a800c(this, 0x20)
|
||||
projectedVelocity.linearMotion = ZeroVector; // FUN_0040a7f4(0x298, zero)
|
||||
projectedVelocity.angularMotion = ZeroVector;
|
||||
record->speedDemand = speedDemand; // rec+0x28
|
||||
bodyTargetSpeed = speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
case 6: // death @4a104d
|
||||
{
|
||||
Mech__FallUpdateRecord *record = (Mech__FallUpdateRecord *)message;
|
||||
Simulation::WriteUpdateRecord(message, record_type);
|
||||
record->recordLength = 0x2c;
|
||||
record->subsystemID = 0;
|
||||
record->heatLevel = (int)heatAlarm.GetLevel();
|
||||
record->throttleState = throttleState;
|
||||
record->fallDirection = fallDirection;
|
||||
record->fallScalar = fallScalar;
|
||||
bodyStateAlarm.SetLevel(0); // @0x714
|
||||
bodyResetLatch = 1; // @0x658 (writer sets ONLY this one)
|
||||
projectedVelocity.linearMotion = ZeroVector;
|
||||
projectedVelocity.angularMotion = ZeroVector;
|
||||
record->speedDemand = speedDemand;
|
||||
bodyTargetSpeed = speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
case 7: // impact @4a10ff (fields only)
|
||||
{
|
||||
Mech__FallUpdateRecord *record = (Mech__FallUpdateRecord *)message;
|
||||
Simulation::WriteUpdateRecord(message, record_type);
|
||||
record->recordLength = 0x2c;
|
||||
record->subsystemID = 0;
|
||||
record->heatLevel = (int)heatAlarm.GetLevel();
|
||||
record->throttleState = throttleState;
|
||||
record->fallDirection = fallDirection;
|
||||
record->fallScalar = fallScalar;
|
||||
record->speedDemand = speedDemand;
|
||||
bodyTargetSpeed = speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
case 8: // airborne/reverse selector @4a117f
|
||||
{
|
||||
Mech__AirborneUpdateRecord *record = (Mech__AirborneUpdateRecord *)message;
|
||||
Simulation::WriteUpdateRecord(message, record_type);
|
||||
record->recordLength = 0x18;
|
||||
record->subsystemID = 0;
|
||||
record->airborne = airborneSelect; // rec+0x10 <- @0x3f4
|
||||
record->speedDemand = speedDemand; // rec+0x14
|
||||
bodyTargetSpeed = speedDemand;
|
||||
}
|
||||
break;
|
||||
|
||||
default: // type 1 (damage zones) + unknown @4a11cb
|
||||
Mover::WriteUpdateRecord(message, record_type); // FUN_004225a4
|
||||
break;
|
||||
}
|
||||
|
||||
// Tail @4a11d9 -- identical to the reader's (see there for the name-
|
||||
// bitmap note): acknowledge the leave-state-2 edge.
|
||||
if (GetOldSimulationState() != GetSimulationState()
|
||||
&& GetOldSimulationState() == 2)
|
||||
{
|
||||
SetSimulationState(GetSimulationState()); // FUN_0041bbd8(this+0x2c, cur)
|
||||
}
|
||||
*(Word *)((char *)this + 0x18) |= flags;
|
||||
}
|
||||
|
||||
|
||||
// (The @004a4c54 body lives inline in mech.hpp as Mech::ForceUpdate -- the
|
||||
// old "SetInstanceFlags" reading here was wrong twice over: +0x18 is
|
||||
// Simulation::updateModel, not the entity instance flags, and FUN_0049fb54
|
||||
// is IsDisabled (simulationState 2||9), not an is-network-copy test.)
|
||||
|
||||
|
||||
//###########################################################################
|
||||
//###########################################################################
|
||||
// Resource authoring (offline tools)
|
||||
|
||||
Reference in New Issue
Block a user