Gyro: byte-exact re-enable -- ctor map, integrators, jointeye writers (task #56)
The Gyroscope subsystem is LIVE again (was a stub since the NaN revert).
Reconstructed byte-exact from @004b3778 / @004b2ec0 / @004b30ec / @004b33e0 /
@004b34ec + the byte-verified dispatch in the unexported Mech performance
FUN_004a9b5c (calls @0x4aaf74/0x4aaf83):
- ctor field map corrected: springConstant@0x1E8 / dampingConstant@0x1F4 were
mislabelled (the old 'eyeOrientation = r->springConstant' poison line), the
0x254-0x2B3 block (externalPitchPtr, work matrix, placement) was missing so
everything after was mis-offset, and clamps/accumulators were 0xCD fill --
every field now initialised per the binary; 33 offsets static_assert-locked,
sizeof == 0x3D0 exact
- integrators: state-minus-target displacements, componentwise damping
OVERWRITE (FUN_004086d0 is a component multiply, not a cross), position step
without dt, clamp to [posSpring, negSpring]; IntegrateBody's X/Z-crossed
force + integration terms
- writers: WriteMechJoint drives ONE node ('jointeye', type 5): TRANSLATION =
eyePosition spring, ROTATION = bodyOrientation spring; WriteEyeJoint is a
multiplicative sway attenuator on 'jointlocal' (post-anim only)
- dispatch moved out of GyroscopeSimulation into the Mech performance tail
(GyroFrameJointWrite bridge, mech4.cpp) with the binary gates; gyro<->torso
pitch link wired (gyro+0x258 = &torso currentTwist) via complete-type-TU
bridges, retiring the SubProxy::linkTarget landmine
- Mech ctor: deathAnimationLatched/legResetLatch were never initialised --
0xCDCDCDCD gated the writers off silently
Runtime-verified: joints resolve (both BallTranslation), Performance installs,
WriteMechJoint finite from frame 0 (no NaN), Madcat cockpit un-regressed,
combat targeting healthy. Empirical: spring targets are SYMMETRIC -> eye
equilibrium (0,0,0), clamps +/-0.1-0.15u -- the gyro is the hit-BOUNCE
mechanism, not a steady eye offset (hypothesis disproven, recorded in KB).
Pending (task #56 tail): the damage->gyro fan-out FUN_004b2980 (unexported
gap) so hits actually kick the springs; the mech+0x3F0 overspeed sway model;
the torso-pitch EyepointRotation writer (FUN_004b66b4) + glance-look states.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
abaa145b6f
commit
cb85517ede
+221
-103
@@ -150,8 +150,38 @@ static GyroSkeleton *LoadSkeleton(const ResourceDirectories * /*dirs*/, const ch
|
||||
//###########################################################################
|
||||
struct GyroLayoutCheck
|
||||
{
|
||||
static_assert(offsetof(Gyroscope, exageration) == 0x1D8, "Gyroscope exageration must be at 0x1D8 after re-base");
|
||||
static_assert(sizeof(Gyroscope) <= 0x3D0, "Gyroscope must fit the 0x3D0 factory alloc");
|
||||
// BINARY-EXACT own-block lock (ctor @004b3778 field map, task #56).
|
||||
static_assert(offsetof(Gyroscope, exageration) == 0x1D8, "exageration @0x1D8");
|
||||
static_assert(offsetof(Gyroscope, eyePosition) == 0x1DC, "eyePosition @0x1DC");
|
||||
static_assert(offsetof(Gyroscope, springConstant) == 0x1E8, "springConstant @0x1E8");
|
||||
static_assert(offsetof(Gyroscope, dampingConstant) == 0x1F4, "dampingConstant @0x1F4");
|
||||
static_assert(offsetof(Gyroscope, eyeClampUpper) == 0x200, "eyeClampUpper @0x200");
|
||||
static_assert(offsetof(Gyroscope, eyeClampLower) == 0x20C, "eyeClampLower @0x20C");
|
||||
static_assert(offsetof(Gyroscope, posSpring) == 0x218, "posSpring @0x218");
|
||||
static_assert(offsetof(Gyroscope, negSpring) == 0x224, "negSpring @0x224");
|
||||
static_assert(offsetof(Gyroscope, eyeForce) == 0x230, "eyeForce @0x230");
|
||||
static_assert(offsetof(Gyroscope, eyeVelocity) == 0x23C, "eyeVelocity @0x23C");
|
||||
static_assert(offsetof(Gyroscope, eyeWork) == 0x248, "eyeWork @0x248");
|
||||
static_assert(offsetof(Gyroscope, spare0) == 0x254, "spare0 @0x254");
|
||||
static_assert(offsetof(Gyroscope, externalPitchPtr) == 0x258, "externalPitchPtr @0x258");
|
||||
static_assert(offsetof(Gyroscope, workMatrix) == 0x25C, "workMatrix @0x25C");
|
||||
static_assert(offsetof(Gyroscope, bodyOrientation) == 0x2B4, "bodyOrientation @0x2B4");
|
||||
static_assert(offsetof(Gyroscope, rotationSpringConstant) == 0x2C0, "rotationSpringConstant @0x2C0");
|
||||
static_assert(offsetof(Gyroscope, rotationDampingConstant) == 0x2CC, "rotationDampingConstant @0x2CC");
|
||||
static_assert(offsetof(Gyroscope, bodyClampUpper) == 0x2D8, "bodyClampUpper @0x2D8");
|
||||
static_assert(offsetof(Gyroscope, bodyClampLower) == 0x2E4, "bodyClampLower @0x2E4");
|
||||
static_assert(offsetof(Gyroscope, rotationPosSpring) == 0x2F0, "rotationPosSpring @0x2F0");
|
||||
static_assert(offsetof(Gyroscope, rotationNegSpring) == 0x2FC, "rotationNegSpring @0x2FC");
|
||||
static_assert(offsetof(Gyroscope, bodyForce) == 0x308, "bodyForce @0x308");
|
||||
static_assert(offsetof(Gyroscope, bodyVelocity) == 0x314, "bodyVelocity @0x314");
|
||||
static_assert(offsetof(Gyroscope, bodyWork) == 0x320, "bodyWork @0x320");
|
||||
static_assert(offsetof(Gyroscope, damageMultiplier) == 0x32C, "damageMultiplier @0x32C");
|
||||
static_assert(offsetof(Gyroscope, damageResponse) == 0x340, "damageResponse @0x340");
|
||||
static_assert(offsetof(Gyroscope, swayBias) == 0x3A8, "swayBias @0x3A8");
|
||||
static_assert(offsetof(Gyroscope, swayAngle) == 0x3BC, "swayAngle @0x3BC");
|
||||
static_assert(offsetof(Gyroscope, eyeJointNode) == 0x3C8, "eyeJointNode @0x3C8");
|
||||
static_assert(offsetof(Gyroscope, mechJointNode) == 0x3CC, "mechJointNode @0x3CC");
|
||||
static_assert(sizeof(Gyroscope) == 0x3D0, "Gyroscope must be exactly the 0x3D0 factory alloc");
|
||||
};
|
||||
|
||||
|
||||
@@ -232,22 +262,50 @@ Gyroscope::Gyroscope(
|
||||
percentageOnDegradation = r->percentageOnDegradation; // @0x3B4 <- +0x110
|
||||
percentageOnFailure = r->percentageOnFailure; // @0x3B8 <- +0x114
|
||||
|
||||
eyeOrientation = r->springConstant; // @0x1DC (+0x118 -> spring work)
|
||||
// the eye spring/damping + pos/neg-spring vectors are copied into the
|
||||
// integrator's working set (see IntegrateEyeJoint):
|
||||
eyePosSpring = r->posSpring; // @0x218 <- +0x130
|
||||
eyeNegSpring = r->negSpring; // @0x224 <- +0x13C
|
||||
// BINARY-EXACT copies + inits (ctor @004b3778 lines 2812-2942 -- the OLD recon's
|
||||
// single line `eyeOrientation = r->springConstant` was the NaN poison: it left
|
||||
// springConstant/dampingConstant uninitialised AND seeded the eye state with a
|
||||
// spring constant. Every accumulator below is zeroed in the binary; nothing is
|
||||
// left as MSVC 0xCD fill.)
|
||||
springConstant = r->springConstant; // @0x1E8 <- +0x118 (:2821)
|
||||
dampingConstant = r->dampingConstant; // @0x1F4 <- +0x124 (:2823)
|
||||
posSpring = r->posSpring; // @0x218 <- +0x130 (:2825)
|
||||
negSpring = r->negSpring; // @0x224 <- +0x13C (:2827)
|
||||
eyeClampUpper = posSpring; // @0x200 identity transform = copy (:2876-2879)
|
||||
eyeClampLower = negSpring; // @0x20C (:2880-2883)
|
||||
|
||||
rotationSpringConstant = r->rotationSpringConstant; // @0x2C0 <- +0x148
|
||||
rotationDampingConstant = r->rotationDampingConstant; // @0x2CC <- +0x154
|
||||
bodyPosSpring = r->rotationPosSpring; // @0x2F0 <- +0x160
|
||||
bodyNegSpring = r->rotationNegSpring; // @0x2FC <- +0x16C
|
||||
rotationSpringConstant = r->rotationSpringConstant; // @0x2C0 <- +0x148 (:2830)
|
||||
rotationDampingConstant = r->rotationDampingConstant; // @0x2CC <- +0x154 (:2833)
|
||||
rotationPosSpring = r->rotationPosSpring; // @0x2F0 <- +0x160 (:2834, already deg->rad)
|
||||
rotationNegSpring = r->rotationNegSpring; // @0x2FC <- +0x16C (:2835)
|
||||
bodyClampUpper.Multiply(rotationPosSpring, 2.0f); // @0x2D8 = 2.0 * rotPosSpring (:2884)
|
||||
bodyClampLower.Multiply(rotationNegSpring, 2.0f); // @0x2E4 = 2.0 * rotNegSpring (:2885)
|
||||
|
||||
// per-damage-type response multipliers and {Trans,PitchRoll,Yaw,Vibration}
|
||||
// curves are stored at this[0xCB..0xE0] (@0x32C..0x380); copied verbatim.
|
||||
// per-damage-type response multipliers + {Trans,PitchRoll,Yaw,Vibration} quads (:2836-2875)
|
||||
damageMultiplier[0] = r->collisionDamageMultiplier; // @0x32C
|
||||
damageMultiplier[1] = r->ballisticDamageMultiplier;
|
||||
damageMultiplier[2] = r->explosiveDamageMultiplier;
|
||||
damageMultiplier[3] = r->laserDamageMultiplier;
|
||||
damageMultiplier[4] = r->energyDamageMultiplier;
|
||||
damageResponse[0] = r->collisionDamageResponse; // @0x340..0x38F
|
||||
damageResponse[1] = r->ballisticDamageResponse;
|
||||
damageResponse[2] = r->explosiveDamageResponse;
|
||||
damageResponse[3] = r->laserDamageResponse;
|
||||
damageResponse[4] = r->energyDamageResponse;
|
||||
|
||||
// identity-init the two orientation work matrices (FUN_0040aadc) and seed
|
||||
// the eye/body negative-spring axes (FUN_00408744 clamp-box) -- see @004b3778.
|
||||
// integrator state + placement scratch: ALL zeroed by the binary (:2921-2942)
|
||||
{
|
||||
const Vector3D vzero(0.0f, 0.0f, 0.0f);
|
||||
eyePosition = eyeForce = eyeVelocity = eyeWork = vzero; // @0x1DC/0x230/0x23C/0x248
|
||||
bodyOrientation = bodyForce = bodyVelocity = bodyWork = vzero; // @0x2B4/0x308/0x314/0x320
|
||||
placePos = placeRot = vzero; // @0x28C/0x2A8
|
||||
placeQuat[0] = placeQuat[1] = placeQuat[2] = 0.0f; placeQuat[3] = 1.0f; // @0x298 identity quat (:2928)
|
||||
for (int i = 0; i < 12; ++i) workMatrix[i] = 0.0f; // @0x25C identity 3x4 (:2793/:2934)
|
||||
workMatrix[0] = workMatrix[5] = workMatrix[10] = 1.0f;
|
||||
spare0 = 0.0f; // @0x254 (:2931)
|
||||
externalPitchPtr = &spare0; // @0x258 self-pointer (:2933);
|
||||
// bt_mech tail re-points to &torso pitch
|
||||
}
|
||||
animationOffset = 0.0f; // @0x390
|
||||
animationScale = 1.0f; // @0x394
|
||||
animationPhase = 0.0f; // @0x398
|
||||
@@ -273,6 +331,21 @@ Gyroscope::Gyroscope(
|
||||
DEBUG_STREAM << " ; mech '" << r->mechJoint << "' -> " << (void*)mechJointNode;
|
||||
if (mechJointNode) DEBUG_STREAM << " type=" << (int)mechJointNode->GetJointType();
|
||||
DEBUG_STREAM << "\n" << std::flush;
|
||||
// spring data (captures the eye equilibrium = (posSpring+negSpring)/2 -- the
|
||||
// numeric values live only in the archived .MDL configs, not the EXE):
|
||||
DEBUG_STREAM << "[gyro] springK=(" << (float)springConstant.x << "," << (float)springConstant.y
|
||||
<< "," << (float)springConstant.z << ") dampK=(" << (float)dampingConstant.x << ","
|
||||
<< (float)dampingConstant.y << "," << (float)dampingConstant.z << ")\n";
|
||||
DEBUG_STREAM << "[gyro] posSpring=(" << (float)posSpring.x << "," << (float)posSpring.y
|
||||
<< "," << (float)posSpring.z << ") negSpring=(" << (float)negSpring.x << ","
|
||||
<< (float)negSpring.y << "," << (float)negSpring.z << ") -> eye equilibrium=("
|
||||
<< (float)((posSpring.x+negSpring.x)*0.5f) << "," << (float)((posSpring.y+negSpring.y)*0.5f)
|
||||
<< "," << (float)((posSpring.z+negSpring.z)*0.5f) << ")\n" << std::flush;
|
||||
DEBUG_STREAM << "[gyro] simulationFlags=0x" << std::hex << (unsigned)owner->simulationFlags
|
||||
<< std::dec << " performance "
|
||||
<< ((((owner->simulationFlags & SegmentCopyMask) == 0)
|
||||
&& ((owner->simulationFlags & MasterHeatSinkFlag) != 0)) ? "INSTALLED" : "NOT installed")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
@@ -318,12 +391,17 @@ void
|
||||
|
||||
const Vector3D zero(0.0f, 0.0f, 0.0f);
|
||||
|
||||
eyeOrientation = zero; // FUN_00408440(this+0x77, &DAT_004e0f74)
|
||||
eyeForce = zero;
|
||||
eyeAccel = zero;
|
||||
bodyPosSpring = zero; // FUN_00408e90(this+0xAD, &DAT_004e0f8c) -- zero rotation
|
||||
bodyAccel = zero;
|
||||
bodyForce = zero;
|
||||
// @004b2678 zeroes the EIGHT integrator vectors (:2190-2197): eye pos/force/
|
||||
// velocity/work + body orientation/force/velocity/work. (The old recon zeroed
|
||||
// the SPRING TARGET bodyPosSpring instead of bodyOrientation -- wrong member.)
|
||||
eyePosition = zero; // @0x1DC FUN_00408440(this+0x77, &DAT_004e0f74)
|
||||
eyeForce = zero; // @0x230
|
||||
eyeVelocity = zero; // @0x23C
|
||||
eyeWork = zero; // @0x248
|
||||
bodyOrientation = zero; // @0x2B4 FUN_00408e90(this+0xAD, &DAT_004e0f8c)
|
||||
bodyForce = zero; // @0x308
|
||||
bodyVelocity = zero; // @0x314
|
||||
bodyWork = zero; // @0x320
|
||||
|
||||
// CROSS-FAMILY: PowerWatcher (PowerWatcher : Subsystem in these headers) has
|
||||
// no slot-10 ResetToInitialState() to chain to; the base reset (FUN_004b1804)
|
||||
@@ -404,95 +482,94 @@ void
|
||||
IntegrateEyeJoint(time_slice); // FUN_004b2ec0
|
||||
IntegrateBody(time_slice); // FUN_004b30ec
|
||||
|
||||
// Per-frame skeleton write: push the integrated eye/body orientation into the
|
||||
// resolved joints. Like the torso's UpdateJoints, the binary's WriteEyeJoint
|
||||
// (@004b33e0, via thunk @004b2eac) + WriteMechJoint (@004b34ec) have NO direct
|
||||
// caller in the decomp -- they were dispatched by the engine's generic per-frame
|
||||
// joint pass. This port ticks the gyro's Performance here, so resolve that
|
||||
// dispatch to a direct call at the same cadence (== what makes the eye/body move).
|
||||
WriteEyeJoint(); // FUN_004b33e0
|
||||
WriteMechJoint(); // FUN_004b34ec
|
||||
|
||||
// NOTE (task #56, byte-verified): the binary Performance @004b275c ENDS here.
|
||||
// WriteEyeJoint/WriteMechJoint are NOT called from the gyro -- they are called
|
||||
// from the MECH master performance tail (FUN_004a9b5c, calls @0x4aaf74/0x4aaf83)
|
||||
// after the animation pass, with the death/leg-anim gates. See
|
||||
// GyroFrameJointWrite (bridge below) + the mech4.cpp dispatch.
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
// @004b2ec0 [BEST-EFFORT] -- integrate the cockpit "eye" orientation with a
|
||||
// spring toward (posSpring/negSpring) and damping, scaled by time_slice, then
|
||||
// clamp each axis into [eyeLimitLow, eyeLimitHigh]. Vector ops are the engine
|
||||
// SIMD-ish helpers; member names are inferred from the access pattern.
|
||||
// @004b2ec0 [T1 byte-exact, task #56] -- the eye TRANSLATION spring integrator.
|
||||
// Displacements are STATE minus TARGET (the config spring constants are negative
|
||||
// = restoring), both terms use springConstant, the damping step OVERWRITES the
|
||||
// force accumulator with dampingConstant (.) velocity (FUN_004086d0 is a
|
||||
// COMPONENT-WISE multiply, not a cross), and the position step has NO dt (the
|
||||
// binary passes velocity through an identity matrix). eyeForce is NOT cleared
|
||||
// per frame -- it carries last frame's damping term + damage impulses by design.
|
||||
// Equilibrium: eyePosition -> (posSpring+negSpring)/2 per axis = the authentic
|
||||
// STEADY EYE OFFSET (written to 'jointeye' translation by WriteMechJoint).
|
||||
//
|
||||
void
|
||||
Gyroscope::IntegrateEyeJoint(Scalar time_slice)
|
||||
{
|
||||
Vector3D toPos; toPos.Subtract(eyePosSpring, eyeOrientation); // FUN_00408644
|
||||
Vector3D toNeg; toNeg.Subtract(eyeNegSpring, eyeOrientation); // (using @0x218 / @0x224)
|
||||
Vector3D d1; d1.Subtract(eyePosition, negSpring); // :2376 state - target
|
||||
Vector3D d2; d2.Subtract(eyePosition, posSpring); // :2377
|
||||
|
||||
Vector3D springForce; springForce.Multiply(eyeSpringConstant, toPos); // FUN_004086d0 (this+0x1E8)
|
||||
Vector3D dampingForce; dampingForce.Multiply(eyeSpringConstant, toNeg);
|
||||
Vector3D f;
|
||||
f.Multiply(springConstant, d1); eyeForce += f; // :2378-2381 componentwise
|
||||
f.Multiply(springConstant, d2); eyeForce += f;
|
||||
|
||||
eyeAccel += springForce; // FUN_004085ec (this+0x230)
|
||||
eyeAccel += dampingForce;
|
||||
eyeWork = eyeForce; // :2383
|
||||
f.Multiply(eyeWork, time_slice); eyeVelocity += f; // :2384-2385
|
||||
|
||||
eyeWork = eyeAccel; // this+0x248
|
||||
Vector3D step; step.Multiply(eyeWork, time_slice); // FUN_004086ac
|
||||
eyeForce += step; // FUN_004085ec (this+0x23C)
|
||||
eyeForce.Multiply(dampingConstant, eyeVelocity); // :2387 OVERWRITE (componentwise)
|
||||
eyeWork = eyeForce;
|
||||
f.Multiply(eyeWork, time_slice); eyeVelocity += f; // :2388-2390
|
||||
|
||||
eyeAccel.Cross(eyeSpringConstant, eyeForce); // FUN_004086d0 cross
|
||||
eyeWork = eyeAccel;
|
||||
step.Multiply(eyeWork, time_slice);
|
||||
eyeForce += step;
|
||||
eyePosition += eyeVelocity; // :2391-2393 identity pass-through, NO dt
|
||||
|
||||
// the original clamped eyeForce against an identity orientation box
|
||||
// (FUN_00408744 with a just-built identity matrix) -- a pass-through -- then
|
||||
// integrated it into eyeOrientation.
|
||||
Vector3D delta = eyeForce; // FUN_00408744 (identity box)
|
||||
eyeOrientation += delta; // FUN_004085ec (this+0x1DC)
|
||||
|
||||
// per-axis clamp into [eyeLimitLow @0x200, eyeLimitHigh @0x20C]
|
||||
if (eyeOrientation.x > eyeLimitLow.x) eyeOrientation.x = eyeLimitLow.x;
|
||||
if (eyeOrientation.y > eyeLimitLow.y) eyeOrientation.y = eyeLimitLow.y;
|
||||
if (eyeOrientation.z > eyeLimitLow.z) eyeOrientation.z = eyeLimitLow.z;
|
||||
if (eyeOrientation.x < eyeLimitHigh.x) eyeOrientation.x = eyeLimitHigh.x;
|
||||
if (eyeOrientation.y < eyeLimitHigh.y) eyeOrientation.y = eyeLimitHigh.y;
|
||||
if (eyeOrientation.z < eyeLimitHigh.z) eyeOrientation.z = eyeLimitHigh.z;
|
||||
// per-axis clamp: min against eyeClampUpper(0x200 = posSpring), then
|
||||
// max against eyeClampLower(0x20C = negSpring)
|
||||
if (eyePosition.x > eyeClampUpper.x) eyePosition.x = eyeClampUpper.x;
|
||||
if (eyePosition.y > eyeClampUpper.y) eyePosition.y = eyeClampUpper.y;
|
||||
if (eyePosition.z > eyeClampUpper.z) eyePosition.z = eyeClampUpper.z;
|
||||
if (eyePosition.x < eyeClampLower.x) eyePosition.x = eyeClampLower.x;
|
||||
if (eyePosition.y < eyeClampLower.y) eyePosition.y = eyeClampLower.y;
|
||||
if (eyePosition.z < eyeClampLower.z) eyePosition.z = eyeClampLower.z;
|
||||
}
|
||||
|
||||
//
|
||||
// @004b30ec [BEST-EFFORT] -- the body tip integrator. Same structure as
|
||||
// IntegrateEyeJoint but driven by rotationSpringConstant / rotationDampingConstant
|
||||
// (this+0x2C0/0x2CC) against the rotationPos/NegSpring limits, accumulating into
|
||||
// bodyOrientation (this+0x2B4) and clamping into [bodyLimitLow, bodyLimitHigh].
|
||||
// @004b30ec [T1 byte-exact, task #56] -- the body ROTATION spring integrator.
|
||||
// Same shape as the eye integrator with three verified quirks: (1) BOTH spring
|
||||
// terms use rotationSpringConstant (never damping); (2) the spring force
|
||||
// components are X/Z-CROSSED: force = (k.x*d.z, k.y*d.y, k.z*d.x); (3) the
|
||||
// integration back into bodyOrientation is crossed again (ori.x += vel.z,
|
||||
// ori.z += vel.x). Damping is componentwise and UNcrossed. Clamps go min
|
||||
// against bodyClampUpper (2*rotPosSpring) then max against bodyClampLower
|
||||
// (2*rotNegSpring), in y,x,z order.
|
||||
//
|
||||
void
|
||||
Gyroscope::IntegrateBody(Scalar time_slice)
|
||||
{
|
||||
Vector3D toNeg; toNeg.Subtract(bodyOrientation, bodyNegSpring); // (this+0x2B4 - this+0x2FC)
|
||||
Vector3D toPos; toPos.Subtract(bodyOrientation, bodyPosSpring); // (this+0x2B4 - this+0x2F0)
|
||||
Vector3D dN; dN.Subtract(bodyOrientation, rotationNegSpring); // state - target
|
||||
Vector3D dP; dP.Subtract(bodyOrientation, rotationPosSpring);
|
||||
|
||||
Vector3D springForce; springForce.Multiply(rotationSpringConstant, toNeg); // (this+0x2C0)
|
||||
Vector3D dampingForce; dampingForce.Multiply(rotationDampingConstant, toPos); // (this+0x2CC)
|
||||
const Vector3D &k = rotationSpringConstant;
|
||||
Vector3D f;
|
||||
f.x = k.x * dN.z; f.y = k.y * dN.y; f.z = k.z * dN.x; // :2479-2481 X/Z crossed
|
||||
bodyForce += f;
|
||||
f.x = k.x * dP.z; f.y = k.y * dP.y; f.z = k.z * dP.x; // :2482-2484
|
||||
bodyForce += f;
|
||||
|
||||
bodyAccel += springForce; // this+0x308
|
||||
bodyAccel += dampingForce;
|
||||
bodyWork = bodyAccel; // this+0x320
|
||||
Vector3D step; step.Multiply(bodyWork, time_slice);
|
||||
bodyForce += step; // this+0x314
|
||||
bodyWork = bodyForce;
|
||||
f.Multiply(bodyWork, time_slice); bodyVelocity += f;
|
||||
|
||||
bodyAccel.Cross(rotationDampingConstant, bodyForce); // cross (this+0x2CC)
|
||||
bodyWork = bodyAccel;
|
||||
step.Multiply(bodyWork, time_slice);
|
||||
bodyForce += step;
|
||||
bodyForce.Multiply(rotationDampingConstant, bodyVelocity); // :2498 componentwise, uncrossed, OVERWRITE
|
||||
bodyWork = bodyForce;
|
||||
f.Multiply(bodyWork, time_slice); bodyVelocity += f;
|
||||
|
||||
Vector3D delta = bodyForce; // FUN_00408744 (identity box)
|
||||
bodyOrientation += delta;
|
||||
bodyOrientation.x += bodyVelocity.z; // :2504-2506 crossed again
|
||||
bodyOrientation.y += bodyVelocity.y;
|
||||
bodyOrientation.z += bodyVelocity.x;
|
||||
|
||||
if (bodyOrientation.y > bodyLimitLow.y) bodyOrientation.y = bodyLimitLow.y; // @0x2DC
|
||||
if (bodyOrientation.x > bodyLimitLow.x) bodyOrientation.x = bodyLimitLow.x; // @0x2D8
|
||||
if (bodyOrientation.z > bodyLimitLow.z) bodyOrientation.z = bodyLimitLow.z; // @0x2E0
|
||||
if (bodyOrientation.y < bodyLimitHigh.y) bodyOrientation.y = bodyLimitHigh.y; // @0x2E8
|
||||
if (bodyOrientation.x < bodyLimitHigh.x) bodyOrientation.x = bodyLimitHigh.x; // @0x2E4
|
||||
if (bodyOrientation.z < bodyLimitHigh.z) bodyOrientation.z = bodyLimitHigh.z; // @0x2EC
|
||||
if (bodyOrientation.y > bodyClampUpper.y) bodyOrientation.y = bodyClampUpper.y; // @0x2DC
|
||||
if (bodyOrientation.x > bodyClampUpper.x) bodyOrientation.x = bodyClampUpper.x; // @0x2D8
|
||||
if (bodyOrientation.z > bodyClampUpper.z) bodyOrientation.z = bodyClampUpper.z; // @0x2E0
|
||||
if (bodyOrientation.y < bodyClampLower.y) bodyOrientation.y = bodyClampLower.y; // @0x2E8
|
||||
if (bodyOrientation.x < bodyClampLower.x) bodyOrientation.x = bodyClampLower.x; // @0x2E4
|
||||
if (bodyOrientation.z < bodyClampLower.z) bodyOrientation.z = bodyClampLower.z; // @0x2EC
|
||||
}
|
||||
|
||||
//
|
||||
@@ -528,9 +605,11 @@ void
|
||||
}
|
||||
|
||||
//
|
||||
// @004b34ec [CONFIDENT] -- push the integrated orientations into the MechJoint
|
||||
// node (type 5 only): write eyeOrientation as the node rotation and bodyOrientation
|
||||
// as the node translation, again only when they differ by more than QuantiseEps.
|
||||
// @004b34ec [T1 byte-exact, task #56] -- push the integrated state into the
|
||||
// MechJoint node ('jointeye', type 5 BallTranslation ONLY): TRANSLATION <-
|
||||
// eyePosition (the steady eye offset + hit bounce; siteeyepoint rides this
|
||||
// joint), ROTATION <- bodyOrientation (the body tip), each written only when
|
||||
// it differs by more than QuantiseEps. ONE node, both channels.
|
||||
//
|
||||
void
|
||||
Gyroscope::WriteMechJoint()
|
||||
@@ -546,14 +625,16 @@ void
|
||||
static int s_gc = 0;
|
||||
if (s_glog && (s_gc % 60) == 0 && s_gc < 1200)
|
||||
{
|
||||
DEBUG_STREAM << "[gyro] WriteMechJoint eye=(" << (float)eyeOrientation.x << "," << (float)eyeOrientation.y
|
||||
<< "," << (float)eyeOrientation.z << ") body=(" << (float)bodyOrientation.x << ","
|
||||
DEBUG_STREAM << "[gyro] WriteMechJoint eyePos=(" << (float)eyePosition.x << "," << (float)eyePosition.y
|
||||
<< "," << (float)eyePosition.z << ") body=(" << (float)bodyOrientation.x << ","
|
||||
<< (float)bodyOrientation.y << "," << (float)bodyOrientation.z << ")\n" << std::flush;
|
||||
if (!(eyePosition.x == eyePosition.x) || !(bodyOrientation.x == bodyOrientation.x))
|
||||
DEBUG_STREAM << "[gyro] *** NaN DETECTED in integrator state ***\n" << std::flush;
|
||||
}
|
||||
++s_gc;
|
||||
if (!NodeRotationEquals(mechJointNode, eyeOrientation, QuantiseEps)) // FUN_004084fc
|
||||
if (!NodeRotationEquals(mechJointNode, eyePosition, QuantiseEps)) // FUN_004084fc vs value+0
|
||||
{
|
||||
SetNodeRotation(mechJointNode, eyeOrientation); // FUN_0041d11c
|
||||
SetNodeRotation(mechJointNode, eyePosition); // FUN_0041d11c = SetTRANSLATION
|
||||
}
|
||||
if (!NodeVectorEquals(mechJointNode, bodyOrientation, QuantiseEps)) // FUN_004091f4
|
||||
{
|
||||
@@ -612,13 +693,14 @@ void
|
||||
Vector3D dir(-x, -y, -z);
|
||||
magnitude *= exageration; // *(this+0x1D8)
|
||||
dir *= magnitude; // FUN_004086ac
|
||||
eyeAccel += dir; // FUN_004085ec (this+0x230)
|
||||
eyeForce += dir; // FUN_004085ec (this+0x230)
|
||||
}
|
||||
|
||||
//
|
||||
// @004b2de4 [CONFIDENT] -- as above but adds into the body accumulator
|
||||
// (bodyAccel @0x308), then forces bodyForce.x (this+0x30C) to 0 and flips the
|
||||
// sign of bodyAccel -- a one-shot torque kick.
|
||||
// @004b2de4 [CONFIDENT] -- as above but adds into the body force accumulator
|
||||
// (bodyForce @0x308), then zeroes its .y (this+0x30C) and flips the sign of
|
||||
// the whole vector -- a one-shot torque kick. (The old recon wrote a member
|
||||
// at the WRONG offset here; 0x30C is bodyForce.y in the binary layout.)
|
||||
//
|
||||
void
|
||||
Gyroscope::ApplyDamageTorque(Scalar x, Scalar y, Scalar z, Scalar magnitude)
|
||||
@@ -626,15 +708,15 @@ void
|
||||
Vector3D dir(-x, -y, -z);
|
||||
magnitude *= exageration;
|
||||
dir *= magnitude;
|
||||
bodyAccel += dir; // FUN_004085ec (this+0x308)
|
||||
bodyForce.x = 0.0f; // *(this+0x30C) = 0
|
||||
bodyAccel.Negate(bodyAccel); // *(this+0x308) = -*(this+0x308)
|
||||
bodyForce += dir; // FUN_004085ec (this+0x308)
|
||||
bodyForce.y = 0.0f; // *(this+0x30C) = 0
|
||||
bodyForce.Negate(bodyForce); // *(this+0x308) = -*(this+0x308)
|
||||
}
|
||||
|
||||
//
|
||||
// @004b2e50 [CONFIDENT] -- a purely-vertical (pitch) kick: only the first
|
||||
// component survives (y,z forced to 0), scaled by exageration, added to bodyAccel
|
||||
// and mirrored into bodyForce (this+0x30C = this+0x308).
|
||||
// component survives (y,z forced to 0), scaled by exageration, added to bodyForce
|
||||
// and mirrored into its .y (this+0x30C = this+0x308).
|
||||
//
|
||||
void
|
||||
Gyroscope::ApplyVerticalImpulse(Scalar pitch, Scalar, Scalar, Scalar magnitude)
|
||||
@@ -642,8 +724,8 @@ void
|
||||
Vector3D dir(pitch, 0.0f, 0.0f);
|
||||
magnitude *= exageration;
|
||||
dir *= magnitude;
|
||||
bodyAccel += dir; // FUN_004085ec (this+0x308)
|
||||
bodyForce.x = bodyAccel.x; // *(this+0x30C) = *(this+0x308)
|
||||
bodyForce += dir; // FUN_004085ec (this+0x308)
|
||||
bodyForce.y = bodyForce.x; // *(this+0x30C) = *(this+0x308)
|
||||
}
|
||||
|
||||
|
||||
@@ -787,3 +869,39 @@ Subsystem *CreateGyroSubsystem(Mech *owner, int id, void *seg)
|
||||
return (Subsystem *) new (Memory::Allocate(0x3D0))
|
||||
Gyroscope(owner, id, (Gyroscope::SubsystemResource *)seg, Gyroscope::DefaultData);
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// Per-frame joint-write dispatch bridge (task #56, byte-verified): the binary
|
||||
// Mech master performance FUN_004a9b5c calls WriteEyeJoint @0x4aaf74 /
|
||||
// WriteMechJoint @0x4aaf83 each frame AFTER the animation pass, preceded by
|
||||
// gyro->swayBias = mech sway accumulator, gated !deathAnimationLatched (both)
|
||||
// and legAnimationState != 0 (eye writer only; WriteEyeJoint is MULTIPLICATIVE
|
||||
// on the joint's animated rotation, so it must never run on a joint the
|
||||
// animation pass didn't just re-write). mech4.cpp calls this bridge from the
|
||||
// same spot in the port's master performance (complete-type TU rule).
|
||||
//===========================================================================//
|
||||
void GyroFrameJointWrite(Subsystem *gyro, Scalar sway_bias,
|
||||
int leg_animation_state, int death_animation_latched)
|
||||
{
|
||||
if (gyro == 0)
|
||||
return;
|
||||
Gyroscope *g = static_cast<Gyroscope *>(gyro);
|
||||
g->SetSwayBias(sway_bias); // gyro+0x3A8 = mech+0x3F0 (bytes 0x4aaf18-3c)
|
||||
if (death_animation_latched != 0) // mech+0x650 gate
|
||||
return;
|
||||
if (leg_animation_state != 0) // mech+0x3B0 gate (+ mech+0x57C, always 0)
|
||||
g->WriteEyeJoint(); // @0x4aaf74
|
||||
g->WriteMechJoint(); // @0x4aaf83
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// Post-stream gyro<->torso link (task #56, binary bt_mech tail): the binary
|
||||
// re-points gyro+0x258 to &torso pitch (torso+0x1D8) after subsystem streaming;
|
||||
// the damage-response path reads it. (Replaces the old SubProxy::linkTarget
|
||||
// landmine, which wrote through an engine base field at gyro+4.)
|
||||
//===========================================================================//
|
||||
void GyroBindExternalPitch(Subsystem *gyro, Scalar *pitch)
|
||||
{
|
||||
if (gyro != 0)
|
||||
static_cast<Gyroscope *>(gyro)->BindExternalPitch(pitch);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user