From 9dcb4752de686de700400be210120debc29833a9 Mon Sep 17 00:00:00 2001 From: arcattack Date: Thu, 16 Jul 2026 14:11:36 -0500 Subject: [PATCH] Audio Phase 4c (AUDIO_FIDELITY F17/F19): impact scaling + the authored footstep feed F17 CollisionSpeed (binary id 24 @0x4B4): real member captured as |worldLinearVelocity| when the contact accumulator arms (0->1) -- the authored AttackVolume [0.9,1] / Brightness [0.7,1] scales over impact speed [0,25] now make harder hits sound louder and brighter. ReduceButton (id 46 @0x340): real watchable member (the keyboard rig never presses it). UnstablePercentage stays deferred: its sway/overspeed model @0x3F0 is the known gyro-ledger gap (live writer unexported); binding without the model would be a stand-in. F19 footstep feed (the invention is dead, long live the authored chain): new [motionscalecfg/motiontrigcfg] traces recovered the authored configs -- EVERY motion watcher extracts |linearMotion| (motionValue=3); the footstep volume mixer is fed by LocalAcceleration [0,10] -> ctl100 (per-stride kick) + LocalVelocity [0,0.6] -> ctl101 (0.4 base while moving). The port never wrote Mover::localAcceleration, so ctl100 read 0 and the old mech2.cpp step-intensity broadcast (patch-sniffing, invented curve) fought the live authored scale. Now: localAcceleration.linear = d(published velocity)/dt -- EXACTLY the binary's derivation ((avgVel - prev)/avgDt into +0x1e4, part_012.c:15186-15195) -- and the broadcast is REMOVED. Regression (30s, walk throttle): stable; footfalls deliver through the wholly-authored chain with per-stride VARYING gains (0.61/0.72/0.62 -- real step dynamics, impossible under the old constant-curve invention). Co-Authored-By: Claude Opus 4.8 (1M context) --- engine/MUNGA/AUDWTHR.cpp | 12 ++++++ game/reconstructed/mech.cpp | 7 +++- game/reconstructed/mech.hpp | 8 ++++ game/reconstructed/mech2.cpp | 77 +++++------------------------------- game/reconstructed/mech4.cpp | 22 +++++++++++ 5 files changed, 57 insertions(+), 69 deletions(-) diff --git a/engine/MUNGA/AUDWTHR.cpp b/engine/MUNGA/AUDWTHR.cpp index e4acd62..2f4fcb6 100644 --- a/engine/MUNGA/AUDWTHR.cpp +++ b/engine/MUNGA/AUDWTHR.cpp @@ -135,6 +135,12 @@ AudioMotionTrigger::AudioMotionTrigger( MemoryStream_Read(stream, &motionType); MemoryStream_Read(stream, &motionValue); + if (getenv("BT_ATTRBIND_LOG")) { static int s_mt=0; if (s_mt++<40) + DEBUG_STREAM << "[motiontrigcfg] attrPtr=" << (void*)attributePointer + << " motionType=" << (int)motionType << " (0=linear,1=angular)" + << " motionValue=" << (int)motionValue << " (0=X,1=Y,2=Z,3=len)" + << "\n" << std::flush; } + PrimeWatcher(); } @@ -284,6 +290,12 @@ AudioMotionScale::AudioMotionScale( MemoryStream_Read(stream, &motionType); MemoryStream_Read(stream, &motionValue); + if (getenv("BT_ATTRBIND_LOG")) { static int s_ms=0; if (s_ms++<40) + DEBUG_STREAM << "[motionscalecfg] attrPtr=" << (void*)attributePointer + << " motionType=" << (int)motionType << " (0=linear,1=angular)" + << " motionValue=" << (int)motionValue << " (0=X,1=Y,2=Z,3=len)" + << "\n" << std::flush; } + PrimeWatcher(); } diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 283250b..78d1672 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -705,7 +705,7 @@ const Mech::IndexEntry ATTRIBUTE_ENTRY(Mech, MaxAcceleration, attrPad), // 0x15 ATTRIBUTE_ENTRY(Mech, CollisionState, collisionState), // 0x16 real StateIndicator (audio impact/scrape watcher) ATTRIBUTE_ENTRY(Mech, CollisionNormal, attrPad), // 0x17 - ATTRIBUTE_ENTRY(Mech, CollisionSpeed, attrPad), // 0x18 + ATTRIBUTE_ENTRY(Mech, CollisionSpeed, collisionSpeed), // 0x18 (F17: |impact velocity|, binary @0x4B4) ATTRIBUTE_ENTRY(Mech, CollisionMaterialType, attrPad), // 0x19 ATTRIBUTE_ENTRY(Mech, CurrentSpeed, legCycleSpeed), // 0x1a (existing @0x348) ATTRIBUTE_ENTRY(Mech, MaxRunSpeed, reverseStrideLength), // 0x1b (existing @0x34c = run/top speed) @@ -727,7 +727,7 @@ const Mech::IndexEntry ATTRIBUTE_ENTRY(Mech, TestButton4, attrPad), // 0x2b ATTRIBUTE_ENTRY(Mech, TestButton5, attrPad), // 0x2c ATTRIBUTE_ENTRY(Mech, TestButton6, attrPad), // 0x2d - ATTRIBUTE_ENTRY(Mech, ReduceButton, attrPad), // 0x2e + ATTRIBUTE_ENTRY(Mech, ReduceButton, reduceButton), // 0x2e (F17: cab REDUCE, binary @0x340) ATTRIBUTE_ENTRY(Mech, RadarRange, radarRange), // 0x2f (radar scale) ATTRIBUTE_ENTRY(Mech, RadarLinearPosition, radarLinearPosition), // 0x30 (Point3D* -> localOrigin) ATTRIBUTE_ENTRY(Mech, RadarAngularPosition, radarAngularPosition),// 0x31 (Quaternion* -> localOrigin) @@ -946,6 +946,9 @@ Mech::Mech( distanceToMissile = FLT_MAX; incomingLockNext = 0; distanceToMissileNext = FLT_MAX; + // (F17) impact-speed scale + cab REDUCE button + collisionSpeed = 0.0f; + reduceButton = 0; ZeroBlock(this + 0xca, 6); // this[0xca..0xcf] = 0 ZeroBlock(this + 0x153, 6); // this[0x153..0x158] = 0 diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index bd21217..19018c0 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -880,6 +880,14 @@ protected: Scalar distanceToMissile; // 0x38 DistanceToMissile (FLT_MAX = none) int incomingLockNext; // accumulator (missiles OR into it) Scalar distanceToMissileNext; // accumulator (missiles MIN into it) + // (AUDIO_FIDELITY F17) CollisionSpeed (binary id 24 @0x4B4): |impact + // velocity| captured when the contact accumulator arms (0->1) -- the + // authored AttackVolume [0.9,1] / Brightness [0.7,1] scales over + // impact speed [0,25] read it, so impacts scale with how hard you hit. + // ReduceButton (id 46 @0x340): the pod cab's REDUCE button; a real, + // watchable member -- the keyboard rig simply never presses it. + Scalar collisionSpeed; // 0x18 CollisionSpeed + int reduceButton; // 0x2e ReduceButton // --- raw / engine-shim members touched by the ctor slice ------------- void *vtable; // installed Mech vtable ptr diff --git a/game/reconstructed/mech2.cpp b/game/reconstructed/mech2.cpp index 0e60623..cb32393 100644 --- a/game/reconstructed/mech2.cpp +++ b/game/reconstructed/mech2.cpp @@ -269,73 +269,16 @@ void if (getenv("BT_AUDIO_LOG")) { static int s_fs=0; if (s_fs++<40) DEBUG_STREAM << "[audio] footstep pulse (clip " << state << ")\n" << std::flush; } - // STEP-INTENSITY SEND [T3]: the authored footstep audio graph mixes its - // volume + brightness from two mixer inputs (ctl 100 walk / 101 run, - // reached through an entity-registered AudioControlSplitter) that the - // object stream only ZERO-initializes -- the runtime feed was BT game - // code (lost), using the engine's Entity::AudioSocketIterator broadcast - // (the only game-facing path to those anonymous components). Reconstruct - // it: on each plant, broadcast the step intensity from the live ground - // speed (the same 0..40 -> 0..1 curve family the authored velocity scales - // use), on the walking input for walk-family clips and the running input - // for run-family clips. Mixer slots are stateful; the other input is - // zeroed so gait changes don't stack. Components with matching input - // ids elsewhere (velocity-fed multiplier slots) are re-driven every frame - // by their own scales, so the broadcast cannot durably disturb them. - { - Scalar speed = localVelocity.linearMotion.Length(); - Scalar intensity = speed * (1.0f / 25.0f); // full stomp by run speed (~25 u/s) - if (intensity > 1.0f) intensity = 1.0f; - if (intensity < 0.55f) intensity = 0.55f; // a mech step is never subtle; floor well over the 0.3 drop gate - int runFamily = (state >= 0x0a && state <= 0x0f); // wrr/wrl/rrr/rrl/rwr/rwl - Entity::AudioSocketIterator it(this); - Component *c; - while ((c = it.ReadAndNext()) != NULL) - { - // Feed ONLY the volume/brightness mix stages WHOSE TARGET IS A - // FOOTSTEP SOURCE. Two earlier bugs shaped this filter: a blind - // broadcast Start-fired sounds through Start-outputting mixers, and - // an output-only filter still cranked OTHER sounds' volume mixers - // (the always-on ambience pulsed to full step intensity -- the - // 'stuck button'). Identify footstep sources by their authored - // patch: FootFallInt/Ext live at bank2 patches 37/38/39 and bank1 - // 106/107 (audiopresets.cpp, from the AUDIO1/2.RES soundfonts). - int cid = (int)c->GetClassID(); - AudioComponent *tgt; - AudioControlID out; - if (cid == (int)RegisteredClass::AudioControlMixerClassID) - { - out = ((AudioControlMixer *)c)->GetOutputControlID(); - tgt = ((AudioControlMixer *)c)->GetTargetComponent(); - } - else if (cid == (int)RegisteredClass::AudioControlMultiplierClassID) - { - out = ((AudioControlMultiplier *)c)->GetOutputControlID(); - tgt = ((AudioControlMultiplier *)c)->GetTargetComponent(); - } - else - continue; - if (out != VolumeAudioControlID && out != BrightnessAudioControlID) - continue; - if (tgt == 0) - continue; - int tcid = (int)tgt->GetClassID(); - if (tcid != 1001 && tcid != 1002 && tcid != 1005) // Direct/Dyn3D/Static3D patch sources - continue; - PatchLevelOfDetail *lod = Cast_Object(PatchLevelOfDetail*, - ((AudioSource *)tgt)->GetAudioResource()->PeekAudioLevelOfDetail()); - if (lod == 0) - continue; - int b = lod->GetBankID(), pp = lod->GetPatchID(); - Logical isFootfall = - (b == 2 && pp >= 37 && pp <= 39) || (b == 1 && (pp == 106 || pp == 107)); - if (!isFootfall) - continue; - AudioComponent *ac = (AudioComponent *)c; - ac->ReceiveControl((AudioControlID)(runFamily ? 101 : 100), intensity); - ac->ReceiveControl((AudioControlID)(runFamily ? 100 : 101), 0.0f); - } - } + // (AUDIO_FIDELITY F19) the old STEP-INTENSITY SEND is GONE. Its + // premise ("the mixer feed was lost game code") was wrong: the feed is + // WHOLLY AUTHORED -- LocalAcceleration |linear| [0,10] -> ctl100 and + // LocalVelocity |linear| [0,0.6] -> ctl101 scale watchers drive the + // footstep volume mixer (0.4 base while moving + the per-stride + // acceleration kick). The port's gap was never publishing + // localAcceleration; mech4.cpp now derives it exactly as the binary + // does (d(averaged velocity)/dt, part_012.c:15186-15195), and the + // invented broadcast fought that live chain (it zeroed the sibling + // input the authored scale had just written). } } diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index a3aec76..4b1c3f0 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -3692,8 +3692,23 @@ void // velocity the same way; this filter reconstructs that stage. { Scalar alpha = dt / (dt + 0.25f); + Scalar fwdPrev = fwdSpeedFiltered; + Scalar vertPrev = vertSpeedFiltered; fwdSpeedFiltered += alpha * (fwdSpeed - fwdSpeedFiltered); vertSpeedFiltered += alpha * (worldLinearVelocity.y - vertSpeedFiltered); + // (AUDIO_FIDELITY F19) publish localAcceleration EXACTLY as the + // binary does: the derivative of the (averaged) published + // velocity (part_012.c:15186-15195 -- (avgVel - prev)/avgDt into + // localAcceleration.linear). The AUTHORED footstep chain reads + // |localAcceleration.linear| [0,10] -> mixer ctl100 (the + // per-stride kick) on top of |localVelocity| [0,0.6] -> ctl101 + // (the 0.4 moving base); with this member never written the + // kick input read 0 and step volume lost its dynamics. + if (dt > 1.0e-4f) + localAcceleration.linearMotion = Vector3D( + 0.0f, + (vertSpeedFiltered - vertPrev) / dt, + -((fwdSpeedFiltered - fwdPrev) / dt)); } localVelocity.linearMotion = Vector3D(0.0f, vertSpeedFiltered, -fwdSpeedFiltered); localVelocity.angularMotion = Vector3D(0.0f, turn * authTurnRate, 0.0f); @@ -5781,7 +5796,14 @@ void // impact, so accumulate InitialHit(1) -- the decomp's dominant `else` branch; // upgrade to the full 1-vs-2 split once 0x240/0x244 are reconstructed. [T3] if (collisionTemporaryState == 0) + { collisionTemporaryState = 1; + // (AUDIO_FIDELITY F17) capture the impact speed for the authored + // CollisionSpeed volume/brightness scales ([0,25] -> harder hits + // sound louder + brighter). |worldLinearVelocity| at the arming + // contact = the impact speed. + collisionSpeed = worldLinearVelocity.Length(); + } }