From c51d64a6b85f6eb8f92da06f2e06ef5cb1a45764 Mon Sep 17 00:00:00 2001 From: arcattack Date: Thu, 16 Jul 2026 15:51:10 -0500 Subject: [PATCH] Gyro tail + audio (task #66): the INSTABILITY MODEL -- mech+0x3F0 reconstructed Byte-decoded the un-exported master-perf block @0x4aad3d-0x4aaf14 (capstone; the same gap that held the F5 footstep code) [T1]: instab = (min(|AccelerationLastFrame| / maxUnstableAcceleration, 1) x unstableAccelerationEffect)^2 instab += clamp((demand - legCycleSpeed)/demand, <=1) # "gun the engine" x unstableGunTheEngineEffect # (demand clamped to # [gimpStride(neg), runMax2]) if (legAnimationState == 4 /*turn-in-place*/) instab += unstableStopedTurnEffect clamp to 1 -> mech+0x3F0 -> gyro->swayBias (gyro+0x3A8) Every piece resolves to already-reconstructed structure: the model-record tuning block rec+0x80..0x94 (ctor copy @0x4a2593 -- the six authored Unstable* fields, previously parked in the Wword scratch bank, now real members); AccelerationLastFrame@0x82c = the snapshot of the ring-derived localAcceleration (copied at the perf tail @0x4ab142); CurrentSpeed@0x348 = legCycleSpeed; the trn state 4 gate; and the gyro feed lands in the EXISTING swayBias member + GyroscopeSimulation consumer (task #56) -- GyroFrameJointWrite's "0.0f model TBD" argument is now the live value. UnstablePercentage (binary id 52 @0x3F0) binds the real member: the LAST dead audio attribute is live -- the authored instability alarm (start thresh 0.01 + volume = the fraction) sounds under hard maneuvers, and the cockpit ambient sway now scales with reckless driving. Live verification (30s, 0.6-throttle run): instab 0.38 during the walk->run chase (cyc 22.2 vs demand 33.6), settling to 0.002-0.12 per-stride ripple at steady run; attribute binds with live float values; [instab] trace under BT_GYRO_TRACE. unstableSuperStopEffect/unstableHighVelocityEffect writers remain unlocated (plausibly the airborne perf variant) -- noted on members. Co-Authored-By: Claude Opus 4.8 (1M context) --- context/cockpit-view.md | 2 +- docs/AUDIO_FIDELITY.md | 5 +++-- game/reconstructed/mech.cpp | 19 ++++++++++------ game/reconstructed/mech.hpp | 19 ++++++++++++++++ game/reconstructed/mech4.cpp | 43 +++++++++++++++++++++++++++++++++++- 5 files changed, 77 insertions(+), 11 deletions(-) diff --git a/context/cockpit-view.md b/context/cockpit-view.md index f787911..46a79f7 100644 --- a/context/cockpit-view.md +++ b/context/cockpit-view.md @@ -5,7 +5,7 @@ status: living source_sections: "task #55 (2026-07-10); reference/decomp part_007.c/part_013.c/part_014.c/part_011.c; engine/MUNGA_L4/bgfload.cpp, L4VIDRND.cpp; game/reconstructed/btl4vid.cpp" related_topics: [rendering, bgf-format, gauges-hud, subsystems, decomp-reference] key_terms: [material-ramp, siteeyepoint] -open_questions: ["gyro tail: gait jolt/rumble [T3 gates], sway model, torso-pitch eye writer, damageForce fill", "exact authored frame colour: the unlit constant (0.13,0.12,0.15) is T3 tuned-to-footage (BT_COP_FRAME overrides; the ramp-index approach + BT_COP_RAMP_L are RETIRED)"] +open_questions: ["gyro tail: gait jolt/rumble [T3 gates], torso-pitch eye writer, damageForce fill (sway model DONE: the mech instability model @0x3F0 feeds swayBias -- task #66)", "exact authored frame colour: the unlit constant (0.13,0.12,0.15) is T3 tuned-to-footage (BT_COP_FRAME overrides; the ramp-index approach + BT_COP_RAMP_L are RETIRED)"] --- # First-Person Cockpit: the *_cop Canopy + the Authentic Eyepoint diff --git a/docs/AUDIO_FIDELITY.md b/docs/AUDIO_FIDELITY.md index d0338a2..36efa79 100644 --- a/docs/AUDIO_FIDELITY.md +++ b/docs/AUDIO_FIDELITY.md @@ -3,8 +3,9 @@ > **STATUS (2026-07-16): Phases 1-4 SHIPPED** (commits fc7f311, 691e885, 0ca7d26, > cc2b109..0e2401f). Fixed: F1-F5, F7, F9-F13, F15, F16, F18-F20, F23, KB items; > F6 fixed (ReportLeak -> coolantActive@0x138, binary table [T1]); F14 partial -> (attenuation baked; static fc/Q deferred); F17 partial (CollisionSpeed + -> ReduceButton real; UnstablePercentage deferred on the gyro sway model); +> (attenuation baked; static fc/Q deferred); F17 FIXED (CollisionSpeed + +> ReduceButton real; UnstablePercentage live -- the instability model @0x3F0 is +> byte-decoded and reconstructed, task #66); > F8/F21/F22 open (voice budget unhit / attack polish / HRTF option). > attrnull = 0: every authored audio attribute binds a real member. diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index e1ca0d4..441ad76 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -733,7 +733,7 @@ const Mech::IndexEntry ATTRIBUTE_ENTRY(Mech, RadarAngularPosition, radarAngularPosition),// 0x31 (Quaternion* -> localOrigin) ATTRIBUTE_ENTRY(Mech, RearFiring, attrPad), // 0x32 ATTRIBUTE_ENTRY(Mech, RequestDuckAnimation, attrPad), // 0x33 - ATTRIBUTE_ENTRY(Mech, UnstablePercentage, attrPad), // 0x34 + ATTRIBUTE_ENTRY(Mech, UnstablePercentage, unstablePercentage), // 0x34 (task #66: the instability alarm; binary id 52 @0x3F0) ATTRIBUTE_ENTRY(Mech, SuperStop, attrPad), // 0x35 // (AUDIO_FIDELITY F7) real missile-alarm backing: binary ids 54/56 @0x3fc/ // 0x400 [T1 table @0x50c0b0/0x50c0d0]; the authored beeper (match 1/0) + @@ -939,6 +939,9 @@ Mech::Mech( accelPrevFwdMean = 0.0f; accelPrevVertMean = 0.0f; accelPrevPos = localOrigin.linearPosition; + // (task #66) instability accumulator: the binary reset zeroes 0x3F0 + // (part_012.c:9420); the tuning members are set from the model record below. + unstablePercentage = 0.0f; fwdSpeedFiltered = 0.0f; // smoothed published velocity (see mech4 derive) vertSpeedFiltered = 0.0f; { extern void *g_btFootStepAddr; g_btFootStepAddr = &footStep; } // DIAG: watcher-poll tracer target @@ -1391,12 +1394,14 @@ Mech::Mech( << " superStop=" << airborneCycleRate << " throttleAdj=" << forwardThrottleScale << std::endl; Wword(0x12f) = model->relativeMechValue; // rec+0x70 -> mech+0x4bc - Wword(0x1e1) = model->maxUnstableAcceleration; // rec+0x80-0x94 -> mech+0x784..0x798 - Wword(0x1e2) = model->unstableAccelerationEffect; // (the six Unstable* effects) - Wword(0x1e3) = model->unstableGunTheEngineEffect; - Wword(0x1e4) = model->unstableSuperStopEffect; - Wword(0x1e5) = model->unstableHighVelocityEffect; - Wword(0x1e6) = model->unstableStopedTurnEffect; + // (task #66) the six Unstable* tuning fields are REAL members now (the + // instability model consumes them); binary rec+0x80-0x94 -> mech+0x784..0x798. + maxUnstableAcceleration = model->maxUnstableAcceleration; + unstableAccelerationEffect = model->unstableAccelerationEffect; + unstableGunTheEngineEffect = model->unstableGunTheEngineEffect; + unstableSuperStopEffect = model->unstableSuperStopEffect; + unstableHighVelocityEffect = model->unstableHighVelocityEffect; + unstableStopedTurnEffect = model->unstableStopedTurnEffect; // Angle fields converted from degrees to radians. Wword(0x159) = (int)(model->lookLeftAngle * DegreesToRadians); // +0x50 diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index 732d3b0..7d27edb 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -879,6 +879,25 @@ protected: Scalar accelPrevFwdMean; Scalar accelPrevVertMean; Point3D accelPrevPos; // raw position memory for the ring feed + // (task #66, the gyro-tail INSTABILITY MODEL -- byte-decoded from the + // master perf @0x4aad3d-0x4aaf14 [T1]): + // instab = (min(|AccelerationLastFrame| / maxUnstableAcceleration, 1) + // x unstableAccelerationEffect)^2 + // instab += clamp((demand - legCycleSpeed)/demand, <=1) ["gun the + // engine": commanded speed the legs haven't reached] + // x unstableGunTheEngineEffect + // if (legAnimationState == 4 /*trn*/) instab += unstableStopedTurnEffect + // clamp to 1 -> mech+0x3F0, then gyro->swayBias (gyro+0x3A8) and the + // UnstablePercentage audio attribute (binary id 52 @0x3F0) read it. + // The six tuning fields come from the model record +0x80..0x94 (ctor + // @0x4a2593-0x4a25d5); previously parked in the Wword scratch bank. + Scalar unstablePercentage; // binary @0x3F0 (reset writes 0) + Scalar maxUnstableAcceleration; // @0x784 <- rec+0x80 + Scalar unstableAccelerationEffect; // @0x788 <- rec+0x84 + Scalar unstableGunTheEngineEffect; // @0x78C <- rec+0x88 + Scalar unstableSuperStopEffect; // @0x790 <- rec+0x8C (writer not yet located; airborne perf?) + Scalar unstableHighVelocityEffect; // @0x794 <- rec+0x90 (writer not yet located) + Scalar unstableStopedTurnEffect; // @0x798 <- rec+0x94 // Radar/map gauge attributes (binary @0x404/0x408/0x40c/0x3f8). The map // widget reads position/angle as POINTERS into the mech's live origin, so // radarLinearPosition/radarAngularPosition point at localOrigin.{linear, diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 7665728..0f149a0 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -5244,13 +5244,54 @@ void // if (GetInstance() == MasterInstance) { + // (task #66) INSTABILITY MODEL -- byte-decoded from the binary master + // perf @0x4aad3d-0x4aaf14 [T1] (the un-exported gap; capstone disasm). + // Runs HERE, right before the gyro dispatch, exactly as the binary + // does: it reads LAST frame's acceleration snapshot (the tail updates + // localAcceleration afterwards -- "AccelerationLastFrame", the same + // vector the binary copies to +0x82c after this point). + { + // term 1: acceleration sway, squared + Scalar a = localAcceleration.linearMotion.Length(); + a = (maxUnstableAcceleration > 0.0f) ? a / maxUnstableAcceleration : 0.0f; + if (a > 1.0f) a = 1.0f; + a *= unstableAccelerationEffect; + unstablePercentage = a * a; + // term 2: "gun the engine" -- commanded speed the legs haven't + // reached, as a fraction of the command (negative when coasting + // past the demand -- the binary does not floor it) + MechControlsMapper *im = MappingMapper(); + Scalar u = (im != 0) ? im->speedDemand : 0.0f; + if (u > reverseSpeedMax2) u = reverseSpeedMax2; // @0x7a0 run cap + if (u < gimpStrideLength) u = gimpStrideLength; // @0x350 (negative reverse cap) + Scalar r = 0.0f; + if (u > 0.0f) + r = (u - legCycleSpeed) / u; + else if (u < 0.0f) + { + r = u - legCycleSpeed; + r = ((r <= 0.0f) ? -r : r) / (-u); + } + if (r > 1.0f) r = 1.0f; + unstablePercentage += r * unstableGunTheEngineEffect; + // term 3: turning in place (trn, leg state 4) + if (legAnimationState == 4) + unstablePercentage += unstableStopedTurnEffect; + if (unstablePercentage > 1.0f) unstablePercentage = 1.0f; + if (getenv("BT_GYRO_TRACE")) { static long s_it=0; if ((++s_it % 120)==0) + DEBUG_STREAM << "[instab] " << unstablePercentage + << " (|a|=" << localAcceleration.linearMotion.Length() + << " demand=" << u << " cyc=" << legCycleSpeed + << " legState=" << legAnimationState << ")\n" << std::flush; } + } + extern void GyroFrameJointWrite(Subsystem *, Scalar, int, int); static int s_gyrodbg = 0; if (getenv("BT_GYRO_LOG") && s_gyrodbg++ == 0) DEBUG_STREAM << "[gyro] dispatch: gyro=" << (void *)gyroSubsystem << " legAnim=" << legAnimationState << " death=" << deathAnimationLatched << "\n" << std::flush; - GyroFrameJointWrite(gyroSubsystem, 0.0f /* mech+0x3F0 model TBD */, + GyroFrameJointWrite(gyroSubsystem, unstablePercentage, legAnimationState, deathAnimationLatched); } else if (getenv("BT_GYRO_LOG"))