Audio: footstep pulse VERIFIED end-to-end to the matcher; volume chain traced (task #50)

Footstep chain progress (each step empirically verified):
- The FootStep watcher is an AudioMatchOf<Logical> (AudioLogicalTrigger extends
  AudioMatchOf, NOT AudioTriggerOf): authored config match=1 -> Start(2.0) on a
  DirectPatchSource.  [trigcfg]/[matchcfg] ctor dumps added.
- The pulse was pinned at 1: the decay lived in IntegrateMotion, which the
  current gait path NEVER CALLS.  Moved to Mech::PerformAndWatch (provably
  per-frame) with a time-based 150ms window sized to the ~10Hz watcher poll.
  The matcher now fires per stride (14/run, was 1).
- Every footstep Start is DROPPED at the renderer: the source's volumeScale is
  0 (five VolumeAudioHandler sends of exactly 0 = inert/one-shot primes).  The
  volume arrives through the authored control chain (AudioControlMultiplier
  inputs ctl 100+; one 0 input pins the product).  Chain scales identified on
  this entity: LocalVelocity (live), LocalAcceleration (live), Myomers.
  SpeedEffect (=1.0 healthy; one authored scale maps [0,1]->[1,0] INVERTED),
  UnstablePercentage (INERT PAD -- always 0!), HeatSink.CurrentTemperature.
  PRIME SUSPECT: an UnstablePercentage-fed multiplier input primes 0 once and
  never updates (the pad never changes), pinning footstep volume at 0.
- SF2 numbering fact recorded: 38 presets in AUDIO1 / 5 in AUDIO2 have
  wPreset != file index (gap at preset 77); my table keys by wPreset.  The
  184x ProgramButton01 (bank1 patch83) storm at ~7/s needs an identity check
  against index-numbering (it may be the wrong sample for that patch id).

Diagnostics added (BT_AUDIO_SPATIAL/BT_ATTRBIND_LOG): [trigcfg]/[matchcfg]
ctor dumps, [matchfire] with component ptr+class, [volset] VolumeAudioHandler,
[mult0] multiplier zero-products, scale sends with authored boundaries,
[fswatch] dedicated footstep watcher tracer (g_btFootStepAddr), SetupPatch
ENTRY with bank/patch/state, Simulation::DebugAudioWatcherCount.

NEXT: back UnstablePercentage with a real (live) member -- it is the mech's
stability 0..1 (the stabilityAlarm/gyro family) -- or confirm via a one-run
chain dump which multiplier input pins the FS source; then the footstep
transient should clear the 0.3 drop gate at walking speed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-15 23:52:06 -05:00
co-authored by Claude Opus 4.8
parent ba3040ea09
commit 1d019e8109
10 changed files with 68 additions and 12 deletions
+4
View File
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "munga.h"
#pragma hdrstop
@@ -793,6 +794,9 @@ void
}
Check(audioComponentSocket.GetCurrent());
if (getenv("BT_AUDIO_SPATIAL") && mult_value <= 0.0f) { static int s_mx=0; if (s_mx++<300)
DEBUG_STREAM << "[mult0] mult=" << (void*)this << " -> tgt=" << (void*)audioComponentSocket.GetCurrent()
<< " in[" << index << "]=" << control_value << " product=" << mult_value << "\n" << std::flush; }
audioComponentSocket.GetCurrent()->ReceiveControl(
outputControlID,
mult_value
+2
View File
@@ -663,6 +663,8 @@ void
Check(this);
Clamp(control_value, MinAudioVolume, MaxAudioVolume);
volumeScale = control_value / (MaxAudioVolume - MinAudioVolume);
if (getenv("BT_AUDIO_SPATIAL")) { static int s_vh=0; if (s_vh++<300)
DEBUG_STREAM << "[volset] src=" << (void*)this << " vol=" << volumeScale << "\n" << std::flush; }
}
//
+20 -4
View File
@@ -305,6 +305,14 @@ template <class T>
MemoryStream_Read(stream, &controlValueOff);
triggerOn = False;
if (getenv("BT_ATTRBIND_LOG")) { static int s_tc=0; if (s_tc++<80)
DEBUG_STREAM << "[trigcfg] attrPtr=" << (void*)attributePointer
<< " thresh=" << attributeValueThreshold
<< " inverse=" << (int)inverseTrigger
<< " onID=" << (int)controlIDOn << "/" << controlValueOn
<< " offID=" << (int)controlIDOff << "/" << controlValueOff
<< "\n" << std::flush; }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -631,10 +639,8 @@ template <class T> void
Check(&audioComponentSocket);
Check(audioComponentSocket.GetCurrent());
if (getenv("BT_AUDIO_SPATIAL") && control_value <= 0.0f) {
static int s_sc=0; if (s_sc++<40)
DEBUG_STREAM << "[spatial] scale->0 attrPtr=" << (void*)attributePointer
<< " raw=" << current_value << " ctlID=" << (int)controlID
<< " ctl=" << control_value << "\n" << std::flush; }
static int s_sc=0; if (s_sc++<400)
DEBUG_STREAM << "[spatial] scale->0 attrPtr=" << (void*)attributePointer << " comp=" << (void*)audioComponentSocket.GetCurrent() << " raw=" << current_value << " ctlID=" << (int)controlID << " ctl=" << control_value << " aB=[" << attributeValueBoundary1 << "," << attributeValueBoundary2 << "]" << " cB=[" << controlValueBoundary1 << "," << controlValueBoundary2 << "]\n" << std::flush; }
audioComponentSocket.GetCurrent()->ReceiveControl(
controlID,
control_value
@@ -731,6 +737,12 @@ template <class T>
MemoryStream_Read(stream, &attributeMatchValue);
MemoryStream_Read(stream, &controlID);
MemoryStream_Read(stream, &controlValue);
if (getenv("BT_ATTRBIND_LOG")) { static int s_mc=0; if (s_mc++<80)
DEBUG_STREAM << "[matchcfg] attrPtr=" << (void*)attributePointer
<< " match=" << attributeMatchValue
<< " ctlID=" << (int)controlID << " ctlVal=" << controlValue
<< "\n" << std::flush; }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -777,6 +789,10 @@ template <class T> void
if (current_value == attributeMatchValue)
{
if (getenv("BT_AUDIO_SPATIAL")) { static int s_mf=0; if (s_mf++<40)
DEBUG_STREAM << "[matchfire] attrPtr=" << (void*)attributePointer << " comp=" << (void*)audioComponentSocket.GetCurrent() << " compClass=" << (int)audioComponentSocket.GetCurrent()->GetClassID()
<< " val=" << current_value << " -> ctl " << (int)controlID
<< "/" << controlValue << "\n" << std::flush; }
Check(&audioComponentSocket);
Check(audioComponentSocket.GetCurrent());
#if 1
+7
View File
@@ -266,6 +266,13 @@ template <class T> void
if (getenv("BT_AUDIO_SPATIAL")) { // poll-rate probe: total watcher polls + change events
static long s_polls=0; if ((++s_polls % 2000)==0)
DEBUG_STREAM << "[watchpoll] total polls=" << s_polls << "\n" << std::flush;
extern void *g_btFootStepAddr; // DIAG: uncapped tracer on THE footstep watcher
if ((void*)attributePointer == g_btFootStepAddr) {
static long s_fsp=0;
if ((++s_fsp % 120)==0 || !(currentValue == *(T*)attributePointer))
DEBUG_STREAM << "[fswatch] poll#" << s_fsp << " cur=" << (int)*(int*)&currentValue
<< " mem=" << *(int*)attributePointer << "\n" << std::flush;
}
}
if (!(currentValue == *(T*)attributePointer))
{
+5
View File
@@ -138,6 +138,11 @@ void
ALenum sourceState;
alGetSourcei(sourceSet.sources[i], AL_SOURCE_STATE, &sourceState);
if (getenv("BT_AUDIO_SPATIAL")) { static int s_se=0; if (s_se++<200)
DEBUG_STREAM << "[audio] SetupPatch ENTRY bank=" << (int)bankID << " patch=" << (int)patchID << " src=" << sourceSet.sources[i]
<< " file=" << (info.file?info.file:"?")
<< " state=" << sourceState << " (INITIAL=" << AL_INITIAL << ")" << "\n" << std::flush; }
if (sourceState == AL_INITIAL)
{
alSourcei(sourceSet.sources[i],AL_BUFFER,AL_getBuffer(info.bufferIndex));
+4
View File
@@ -398,3 +398,7 @@ void VideoComponent::ReceiveControl(VideoControlID /*control_ID*/, Scalar /*cont
void VideoComponent::Execute()
{
}
// DIAG (audio footstep chain): the live &mech->footStep, exported so the
// engine watcher poll can trace THE footstep watcher without per-run pointers.
void *g_btFootStepAddr = 0;
+1
View File
@@ -925,6 +925,7 @@ Mech::Mech(
collisionTemporaryState = 0;
footStep = 0; // FootStep pulse (audio trigger)
footStepDecay = 0;
{ 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
// the radar, vs the config maximum_range=4000 edge)
+6 -2
View File
@@ -847,8 +847,12 @@ protected:
// stride clip (each walk/run/reverse alternation == one foot plant) and the
// 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 trigger polls (1 = contact)
int footStepDecay; // frames until the pulse clears
int footStep; // the Logical the matcher polls (1 = contact)
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
// well before the next stride -- match=1 fires on the
// CHANGE to 1, so footStep must return to 0 in between)
// 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,
+2 -2
View File
@@ -261,9 +261,9 @@ void
if (state >= 1 && state <= 0x17)
{
footStep = 1;
footStepDecay = 20; // ~1/3s: outlives the watcher poll cadence (still < stride period)
footStepDecay = 150; // ms: ~1.5 poll periods ON, OFF well before the next stride
if (getenv("BT_AUDIO_LOG")) { static int s_fs=0; if (s_fs++<40)
DEBUG_STREAM << "[audio] footstep pulse (clip " << state << ") addr=" << (void*)&footStep << "\n" << std::flush; }
DEBUG_STREAM << "[audio] footstep pulse (clip " << state << ") leftover=" << footStepDecay << "\n" << std::flush; }
}
}
+17 -4
View File
@@ -292,10 +292,8 @@ Logical
// Choose this frame's forward-cycle slew rate.
forwardCycleRate = airborneSelect ? airborneCycleRate : groundCycleRate; // 0x344<-0x5bc/0x5b8
// FootStep pulse decay (see SetBodyAnimation): clear the contact flag a few
// frames after the plant so the AudioLogicalTrigger re-arms for the next step.
if (footStepDecay > 0 && --footStepDecay == 0)
footStep = 0;
// (FootStep pulse decay moved to PerformAndWatch -- this integrator does not
// run on the current gait path, so a decay here never fired.)
if (IsDisabled()) // FUN_0049fb54
{
@@ -5205,6 +5203,21 @@ void
// (AudioLogicalTrigger FootStep, AudioMotionScale/Trigger LocalVelocity, ...)
// never executed -- only the PUSHED StateIndicator watchers fired. That's
// why footsteps/motion-scaled sounds were silent while state sounds worked. [T0]
// FootStep pulse decay (see SetBodyAnimation): clear the contact flag ~150ms
// after the plant so the FootStep matcher (fires on the CHANGE to 1) sees a 0
// before the next stride re-pulses. Lives HERE (not IntegrateMotion, which the
// current gait path never calls) so it provably ticks every mech frame, right
// before the watcher poll below reads it. Time-based -- tick-rate immune.
if (footStepDecay > 0)
{
footStepDecay -= (int)(dt * 1000.0f);
if (footStepDecay <= 0)
{
footStepDecay = 0;
footStep = 0;
}
}
if (getenv("BT_AUDIO_LOG")) { static int s_wd=0; if ((s_wd++ % 300)==0)
DEBUG_STREAM << "[audio] mech watcher poll: delayed=" << (int)AreWatchersDelayed() << " audioWatchers=" << DebugAudioWatcherCount()
<< " simFlags=0x" << std::hex << (unsigned)simulationFlags << std::dec