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:
co-authored by
Claude Opus 4.8
parent
21378ec132
commit
9dcb4752de
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user