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) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-16 14:11:36 -05:00
co-authored by Claude Opus 4.8
parent 21378ec132
commit 9dcb4752de
5 changed files with 57 additions and 69 deletions
+10 -67
View File
@@ -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).
}
}