Audio: kill the per-footfall static -- smooth the PUBLISHED velocity (task #50)

User pinpointed it: the per-stride 'static' was NOT the footstep sample -- it
was the ambient engine hum being interrupted in time with the footfalls.
Diagnosis (live tape): EnginePower01 was caught in a rapid Play->Stop cycle
(80x/session).  The idle-hum is gated by authored LocalVelocity triggers (hum
ON in speed [1,20], OFF above 20) -- and the port's localVelocity is a RAW
per-frame position derivative that pulses with every stride (gait acceleration
+ ground-snap bob).  At walking speed |v| straddles the 20 band edge, so the
trigger Start/Stop-flapped the hum on every foot plant; at run |v| >> 20 keeps
it off (matching 'goes away at higher speeds').

Fix: ~0.25s exponential filter on the PUBLISHED localVelocity.linearMotion
(fwd + vertical) -- kills the stride ripple, tracks real speed changes within
a couple strides.  The physics worldLinearVelocity (StaticBounce, collision
damage pricing) is untouched; update records + the impact gate get the
smoother value (both benefit).

VERIFIED: EnginePower 1 play / 0 stops for the whole run (was 80 stop-cycles);
FootFall cycles normally per stride; gear shifts/windup lifecycle intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-16 09:07:36 -05:00
co-authored by Claude Opus 4.8
parent b8908b965e
commit c68307ad53
3 changed files with 20 additions and 1 deletions
+2
View File
@@ -925,6 +925,8 @@ Mech::Mech(
collisionTemporaryState = 0;
footStep = 0; // FootStep pulse (audio trigger)
footStepDecay = 0;
fwdSpeedFiltered = 0.0f; // smoothed published velocity (see mech4 derive)
vertSpeedFiltered = 0.0f;
{ extern void *g_btFootStepAddr; g_btFootStepAddr = &footStep; } // DIAG: watcher-poll tracer target
radarRange = 1000.0f; // radar display scale (SetTargetRange is stubbed;
// 1km default zoom so contacts within ~500m show on
+4
View File
@@ -848,6 +848,10 @@ protected:
// per-frame motion tick decays it back to 0 a few frames later, giving one
// clean rising edge per step. [T2]
int footStep; // the Logical the matcher polls (1 = contact)
Scalar fwdSpeedFiltered; // smoothed |v| published via localVelocity (the raw
Scalar vertSpeedFiltered; // per-frame position derivative pulses per stride and
// flapped the audio's speed triggers -- engine-hum
// restarts on every footfall)
int footStepDecay; // MILLISECONDS until the pulse clears (time-based:
// the watcher polls ~10Hz and strides re-pulse ~2Hz,
// so the pulse must be one poll-period wide but end
+14 -1
View File
@@ -3639,7 +3639,20 @@ void
Scalar fwdSpeed = (Scalar)sqrtf(
worldLinearVelocity.x * worldLinearVelocity.x +
worldLinearVelocity.z * worldLinearVelocity.z);
localVelocity.linearMotion = Vector3D(0.0f, worldLinearVelocity.y, -fwdSpeed);
// SMOOTH the PUBLISHED velocity (localVelocity feeds the audio speed
// triggers, the update records, and the impact gate -- NOT the physics
// worldLinearVelocity above). The raw per-frame derivative pulses per
// stride (gait acceleration + ground-snap bob): at walking speed the
// oscillation straddled the authored idle-hum band edge (speed 20) and
// Start/Stop-flapped the engine ambience on every footfall (heard as a
// static tick per step). ~0.25s exponential filter kills the stride
// ripple while tracking real speed changes within a couple of strides.
{
Scalar alpha = dt / (dt + 0.25f);
fwdSpeedFiltered += alpha * (fwdSpeed - fwdSpeedFiltered);
vertSpeedFiltered += alpha * (worldLinearVelocity.y - vertSpeedFiltered);
}
localVelocity.linearMotion = Vector3D(0.0f, vertSpeedFiltered, -fwdSpeedFiltered);
localVelocity.angularMotion = Vector3D(0.0f, turn * authTurnRate, 0.0f);
// MASTER twin of [replmov]: the velocity this mech PUBLISHES vs its own