diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 78d1672..d0525d6 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -930,7 +930,8 @@ Mech::Mech( collisionState.SetState(0); // NoCollisionState collisionTemporaryState = 0; footStep = 0; // FootStep pulse (audio trigger) - footStepDecay = 0; + footStepRootJoint = 0; // (F5) resolved on first contact eval + footStepRootResolved = 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 diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index 19018c0..a6702a9 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -854,11 +854,15 @@ protected: 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 - // well before the next stride -- match=1 fires on the - // CHANGE to 1, so footStep must return to 0 in between) + // (AUDIO_FIDELITY F5) footStep is the authored CONTACT LEVEL, not a + // clip-transition pulse: per frame (leg gait state not 0/1), + // footStep = (jointlocal.y <= *clip footStepThreshold) -- binary + // disasm @0x4a9e80-0x4a9eb6 (leg) / @0x4aba86-0x4abab9 (body); the + // threshold is the ANI header word[2] SelectSequence now captures. + // States 0/1 RETAIN the previous value (binary skip). The old 150 ms + // pulse + decay members are retired. + Joint *footStepRootJoint; // cached "jointlocal" (binary mech+0x5C8) + int footStepRootResolved; // 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/mech2.cpp b/game/reconstructed/mech2.cpp index cb32393..898f368 100644 --- a/game/reconstructed/mech2.cpp +++ b/game/reconstructed/mech2.cpp @@ -262,24 +262,20 @@ void // knockdown/death (those get collision/fall audio, not a step). Raise the // Logical the FootStep AudioLogicalTrigger polls; IntegrateMotion decays it // back to 0 a few frames later so each step is a clean rising edge. [T2] - if (state >= 1 && state <= 0x17) - { - footStep = 1; - 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 << ")\n" << std::flush; } - - // (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). - } + // (AUDIO_FIDELITY F5) the old per-transition footStep PULSE is GONE: + // footStep is the authored CONTACT LEVEL, evaluated per frame in + // Mech::PerformAndWatch from jointlocal.y vs the clip's authored + // threshold (ANI hdr[2]) -- steps now fire at actual root-height + // contact crossings, not at clip boundaries with a fixed width. + // (F19) the old STEP-INTENSITY SEND is also 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 derives it exactly as the binary does + // (d(averaged velocity)/dt, part_012.c:15186-15195), and the invented + // broadcast fought that live chain. } // diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 4b1c3f0..71290d5 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -5274,18 +5274,32 @@ 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) + // (AUDIO_FIDELITY F5) footStep is the authored CONTACT LEVEL (binary + // disasm @0x4a9e80-0x4a9eb6): while the leg channel's gait state is not + // Standing(0)/1, footStep = (jointlocal.y <= *clip threshold); states + // 0/1 RETAIN the previous value. The FootStep matcher fires on the + // CHANGE to 1 -- one clean rising edge per actual foot plant, timed by + // the clip's authored root-height crossing (replaces the 150 ms + // transition pulse, which fired at clip boundaries with a fixed width). + if (legAnimationState != 0 && legAnimationState != 1) { - footStepDecay -= (int)(dt * 1000.0f); - if (footStepDecay <= 0) + if (!footStepRootResolved) { - footStepDecay = 0; - footStep = 0; + footStepRootResolved = 1; + footStepRootJoint = ResolveJoint("jointlocal"); + } + Scalar *fs_thresh = legAnimation.footStepThreshold; + if (footStepRootJoint != 0 && fs_thresh != 0) + { + int contact = + (footStepRootJoint->GetTranslation().y <= *fs_thresh) ? 1 : 0; + if (getenv("BT_AUDIO_LOG")) { static int s_fc=0; + if (contact != footStep && s_fc++ < 40) + DEBUG_STREAM << "[audio] footstep contact " << footStep << "->" << contact + << " rootY=" << footStepRootJoint->GetTranslation().y + << " thresh=" << *fs_thresh << " legState=" << legAnimationState + << "\n" << std::flush; } + footStep = contact; } } diff --git a/game/reconstructed/mechrecon.hpp b/game/reconstructed/mechrecon.hpp index def87fb..a67b112 100644 --- a/game/reconstructed/mechrecon.hpp +++ b/game/reconstructed/mechrecon.hpp @@ -270,6 +270,11 @@ struct SequenceController // --- clip data parsed by SelectSequence (keyframe accessors read by mech3) --- int keyframeCount; // frameCount (binary +0x14) int jointCount; // animated joints (binary +0x18) + // (AUDIO_FIDELITY F5) the ANI header's footstep CONTACT threshold -- the + // clip-authored root height below which the foot is planted. The engine's + // own AnimationInstance captures the same word (JMOVER.cpp:1415); the + // binary's per-frame perf compares jointlocal.y against it (+0x20). + Scalar *footStepThreshold; // &hdr[2] (binary +0x20) int *jointIndices; // keyframe->skeleton map (binary +0x1c) Scalar *keyframeTimes; // frameTimes[frameCount] (binary +0x24) void *keyframeBase; // keyframe pose data base (binary +0x28) @@ -286,10 +291,10 @@ struct SequenceController unsigned cbArg2, cbArg3; // finished-callback context words (binary +0x4c/0x50) SequenceController() - : keyframeCount(0), jointCount(0), jointIndices(0), keyframeTimes(0), - keyframeBase(0), keyframeCursor(0), keyframeData(0), currentFrame(0), - currentTime(0.0f), jointSubsystem(0), owner(0), clipResource(0), - cbCode(0), cbArg2(0), cbArg3(0) {} + : keyframeCount(0), jointCount(0), footStepThreshold(0), jointIndices(0), + keyframeTimes(0), keyframeBase(0), keyframeCursor(0), keyframeData(0), + currentFrame(0), currentTime(0.0f), jointSubsystem(0), owner(0), + clipResource(0), cbCode(0), cbArg2(0), cbArg3(0) {} void Init(Mech *owner_mech); // @00427768 (ctor logic) void SelectSequence(int clip_id, void *cb, unsigned a2, unsigned a3); // @004277a8 diff --git a/game/reconstructed/seqctl.cpp b/game/reconstructed/seqctl.cpp index 1a6ab31..9c57aee 100644 --- a/game/reconstructed/seqctl.cpp +++ b/game/reconstructed/seqctl.cpp @@ -149,6 +149,10 @@ void int *hdr = (int *)desc->resourceAddress; // resource data (binary: *(desc+0x3c)) keyframeCount = hdr[0]; // @0x14 frameCount jointCount = hdr[1]; // @0x18 jointCount + // (AUDIO_FIDELITY F5) hdr[2] = the clip's authored footstep contact + // threshold (root height); previously skipped. Same capture as the + // engine's AnimationInstance (JMOVER.cpp:1415). + footStepThreshold = (Scalar *)(hdr + 2); // @0x20 int *p = hdr + 3; // jointIndices follow hdr[0..2] jointIndices = p; // @0x1c (jointCount ints)