Merge BT411 audio-fidelity + combat work (4e72f0c..abed41e) into BT412

Brings the post-fork BT411 line forward via a local-path merge (never
touches the BT411 gitea remote): the full audio-fidelity system (engine
AUD*/L4AUD* + audiopresets.cpp + ~600 content wavs + AUDIO_FIDELITY.md),
missiles/rear-fire/HUD/gyro/gait tasks (#66-68), FOGDAY.EGG, and
refreshed context docs -- 91 commits, ~688 files clean.

Only 5 files overlapped the steamification; resolved keeping BOTH:
- L4NET.CPP: took BT411's task-#50 fix (don't close the game listener on
  console loss) over the seam's adaptation of the old buggy close; sends
  stay on NetTransport_Get().
- L4NETTRANSPORT.cpp: folded BT411's TCP_NODELAY latency fix into
  WinsockNetTransport::Connect (the seam already had retry + nonblocking).
- mechmppr.cpp: combined the device_owns_input gating with BT411's
  task-#68 look-behind, gating the lookBehind write too.
- .gitignore / CMakeLists.txt / mech4.cpp: trivial / auto-merged
  (deviceOwnsInput gating preserved).

Verified: clean build (default + implicitly the Steam TU untouched);
solo front-end mode; loopback MP through the seam (mesh completes, both
tick, replication works, no NODELAY warnings).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-16 19:34:54 -05:00
co-authored by Claude Fable 5
696 changed files with 6416 additions and 422 deletions
+33
View File
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "munga.h"
#pragma hdrstop
@@ -290,6 +291,11 @@ AudioControlSend::AudioControlSend(
MemoryStream_Read(stream, &control_ID);
MemoryStream_Read(stream, &control_value);
if (getenv("BT_ATTRBIND_LOG")) { static int s_sd=0; if (s_sd++<80)
DEBUG_STREAM << "[sendcfg] tgt=" << (void*)audio_component
<< " ctl=" << (int)control_ID << "/" << control_value
<< " entity=" << (void*)entity << "\n" << std::flush; }
Check(entity);
entity->AddAudioComponent(this);
@@ -472,6 +478,9 @@ void
while ((audio_component = iterator.ReadAndNext()) != NULL)
{
Check(audio_component);
if (getenv("BT_AUDIO_SPATIAL")) { static int s_sp2=0; if (s_sp2++<200)
DEBUG_STREAM << "[split] " << (void*)this << " -> tgt=" << (void*)audio_component
<< " ctl=" << (int)control_ID << "/" << control_value << "\n" << std::flush; }
audio_component->ReceiveControl(control_ID, control_value);
}
}
@@ -631,6 +640,10 @@ void
}
Check(audioComponentSocket.GetCurrent());
if (getenv("BT_AUDIO_SPATIAL")) { static int s_mix=0; if (s_mix++<80)
DEBUG_STREAM << "[mix] mixer=" << (void*)this << " -> tgt=" << (void*)audioComponentSocket.GetCurrent()
<< " outCtl=" << (int)outputControlID << " in[" << index << "]=" << control_value
<< " sum=" << mixed_value << "\n" << std::flush; }
audioComponentSocket.GetCurrent()->ReceiveControl(
outputControlID,
mixed_value
@@ -793,6 +806,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
@@ -840,6 +856,11 @@ AudioControlSmoother::AudioControlSmoother(
MemoryStream_Read(stream, &initial_fill_value);
audioControlAverage.SetSize(number_of_samples, initial_fill_value);
if (getenv("BT_ATTRBIND_LOG")) { static int s_sm=0; if (s_sm++<40)
DEBUG_STREAM << "[smoothcfg] this=" << (void*)this << " ctlID=" << (int)control_ID
<< " samples=" << (int)number_of_samples << " fill=" << initial_fill_value
<< "\n" << std::flush; }
AudioControlSmootherX(
audio_component,
entity,
@@ -939,6 +960,14 @@ void
{
audioControlAverage.Add(control_value);
if (getenv("BT_AUDIO_SPATIAL")) { static int s_sa=0;
if ((controlID == 100 || controlID == 101) && s_sa++ < 3000)
DEBUG_STREAM << "[smooth] t=" << (GetTickCount() % 1000000)
<< " this=" << (void*)this << " ctl=" << (int)controlID
<< " in=" << control_value
<< " avg=" << audioControlAverage.CalculateOlympicAverage()
<< "\n" << std::flush; }
Check(audioComponentSocket.GetCurrent());
audioComponentSocket.GetCurrent()->ReceiveControl(
controlID,
@@ -1315,6 +1344,10 @@ void
nextSampleTime += sampleDuration;
Check(audioComponentSocket.GetCurrent());
if (getenv("BT_AUDIO_SPATIAL")) { static int s_sh=0; if (s_sh++<60)
DEBUG_STREAM << "[snh] this=" << (void*)this << " -> tgt=" << (void*)audioComponentSocket.GetCurrent()
<< " ctlID=" << (int)audioControlID << " val=" << currentValue
<< " range=[" << minValue << "," << maxValue << "]\n" << std::flush; }
audioComponentSocket.GetCurrent()->ReceiveControl(
audioControlID,
currentValue
+12
View File
@@ -232,6 +232,12 @@ class AudioControlMixer:
public AudioComponent
{
public:
// DIAG/game-facing: expose the authored output control so game-side control
// broadcasts (the foot-plant intensity send) can target only volume/brightness
// mix stages and never Start-outputting stages.
AudioControlID GetOutputControlID() const { return outputControlID; }
AudioComponent *GetTargetComponent() { return audioComponentSocket.GetCurrent(); }
//
//--------------------------------------------------------------------
// Construction, Destruction, Testing
@@ -307,6 +313,12 @@ class AudioControlMultiplier:
public AudioComponent
{
public:
// DIAG/game-facing: expose the authored output control so game-side control
// broadcasts (the foot-plant intensity send) can target only volume/brightness
// mix stages and never Start-outputting stages.
AudioControlID GetOutputControlID() const { return outputControlID; }
AudioComponent *GetTargetComponent() { return audioComponentSocket.GetCurrent(); }
//
//--------------------------------------------------------------------
// Construction, Destruction, Testing
+91 -3
View File
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "munga.h"
#pragma hdrstop
@@ -94,8 +95,15 @@ void
}
headEntitySocket.Add(entity);
alDistanceModel(AL_LINEAR_DISTANCE);
alDopplerFactor(0.3f);
// FIDELITY (AUDIO_FIDELITY.md F3/F10): the engine computes the AUTHORED
// distance-attenuation curve (AUDIO.INI amplitude_rolloff knee/exponent ->
// AudioLocation::distanceVolumeScale) and the authored doppler-cents model.
// Disable OpenAL's own models so they can't double-apply / fight them:
// AL_LINEAR_DISTANCE made far battle audio fade to zero on a straight line
// (-6 dB vs authored at 300u), and AL doppler ran with the wrong constants
// AND a sign-inverted velocity feed (approaching sources pitched DOWN).
alDistanceModel(AL_NONE);
alDopplerFactor(0.0f);
#if 0
//
@@ -131,8 +139,39 @@ void
//
// Increment frame counter
//
audioFrameCount = Now().ticks;
// AUDIO CLOCK CALIBRATION FIX: AudioTime consumers (sequence event timing,
// compression curves) assume audioFrameCount advances at the renderer's
// calibrationRate (DefaultRendererRate = 30 frames/sec:
// Seconds_To_Frames = s * rate). The WinTesla port set raw Now().ticks here
// (~hundreds/sec), running sequences ~18x off the authored timing -- the
// AudioControlSequence events carrying the real footstep volumes never
// landed where authored. Convert ticks -> calibrated frames properly.
{
double tps = (double)SystemClock::GetTicksPerSecond();
if (tps <= 0.0) tps = 1000.0; // GetTickCount ms fallback (static not yet measured)
audioFrameCount = (AudioFrameCount)(
(double)Now().ticks
* (double)application->GetAudioRenderer()->GetCalibrationRate()
/ tps);
}
Verify(audioFrameCount < LONG_MAX);
if (getenv("BT_AUDIO_SPATIAL")) { static long s_hx=0; if ((++s_hx % 300)==0)
DEBUG_STREAM << "[audioclock] frame=" << audioFrameCount << " execs=" << s_hx << "\n" << std::flush; }
// (task #50, AUDIO_FIDELITY F13) service the authored release fades
// registered by PatchLevelOfDetail::StopNote (dB-linear note-off ramps).
{
extern void PRESET_serviceReleaseFades(float elapsed_seconds);
static long s_lastFadeTicks = 0;
long now_ticks = Now().ticks;
if (s_lastFadeTicks != 0 && now_ticks > s_lastFadeTicks)
{
double tps = (double)SystemClock::GetTicksPerSecond();
if (tps <= 0.0) tps = 1000.0;
PRESET_serviceReleaseFades((float)((now_ticks - s_lastFadeTicks) / tps));
}
s_lastFadeTicks = now_ticks;
}
//set current listener orientation
Vector3D headVelocity;
@@ -140,6 +179,32 @@ void
headVelocity.MultiplyByInverse(this->GetHeadEntity()->GetWorldLinearVelocity(), this->GetHeadEntity()->localToWorld);
alListener3f(AL_VELOCITY, -headVelocity.x, -headVelocity.y, -headVelocity.z);
// LIVE-PLAYING DUMP (BT_AUDIO_DUMP): once a second list every playing AL
// source with its sample name / gain / pitch / loop -- catches "mystery
// sounds" (wrong sample, wrong pitch, chopped) red-handed.
if (getenv("BT_AUDIO_DUMP")) {
static long s_dumpTick = 0;
if ((++s_dumpTick % 45) == 0) {
extern ALuint *g_buffers; extern int g_numBuffers; extern const char *g_bufferNames[512];
for (ALuint sid = 1; sid <= 40; ++sid) {
if (!alIsSource(sid)) continue;
ALint st = 0; alGetSourcei(sid, AL_SOURCE_STATE, &st);
if (st != AL_PLAYING) continue;
ALint buf = 0, looping = 0; ALfloat gain = 0, pitch = 0;
alGetSourcei(sid, AL_BUFFER, &buf);
alGetSourcei(sid, AL_LOOPING, &looping);
alGetSourcef(sid, AL_GAIN, &gain);
alGetSourcef(sid, AL_PITCH, &pitch);
const char *nm = "?";
for (int b = 0; b < g_numBuffers && b < 512; ++b)
if ((ALint)g_buffers[b] == buf) { nm = g_bufferNames[b] ? g_bufferNames[b] : "?"; break; }
DEBUG_STREAM << "[playing] src=" << sid << " " << nm
<< " gain=" << gain << " pitch=" << pitch
<< " loop=" << looping << "\n" << std::flush;
}
}
}
#if 0
//
// Get the entity
@@ -398,6 +463,29 @@ void
}
}
//
//#############################################################################
//#############################################################################
//
void
AudioComponent::PrimeWatchers(int passes)
{
Check(this);
for (int pass = 0; pass < passes; ++pass)
{
ChainIteratorOf<Component*> iterator(&audioWatcherSocket);
Component *component;
while ((component = iterator.ReadAndNext()) != NULL)
{
Check(component);
// recurse gate-free: an AudioComponent child (mixer/smoother/
// splitter) pumps ITS chain; an attribute watcher re-reads +
// re-sends. One pass per level keeps total work = passes x chain.
component->PrimeWatchers(1);
}
}
}
//
//#############################################################################
//#############################################################################
+8
View File
@@ -454,6 +454,14 @@ public:
void
ExecuteWatchers();
// (task #50, AUDIO_FIDELITY F19) gate-free watcher pump for the transient
// cold-start prime: ExecuteWatchers above is frame-gated
// (DefaultAudioFrameDelay), so N calls in one tick collapse to one.
// Recurses through the watcher CHAIN (source <- mixer <- smoother <-
// scale), each hop gate-free.
virtual void
PrimeWatchers(int passes);
void
Execute();
+5
View File
@@ -162,6 +162,11 @@ protected:
AudioLevelOfDetail*
GetAudioLevelOfDetail();
public:
// (task #50) public read of the active LOD -- the game-side footstep
// intensity send identifies footstep sources by their patch bank/id.
AudioLevelOfDetail* PeekAudioLevelOfDetail() { return GetAudioLevelOfDetail(); }
private:
//
//-----------------------------------------------------------------------
+45 -2
View File
@@ -174,6 +174,10 @@ void
if (audio_source->IsAudioSourceClipped(GetAudioHead()))
{
if (getenv("BT_AUDIO_SPATIAL")) { static int s_cl=0; if (s_cl++<40)
DEBUG_STREAM << "[spatial] CLIPPED src=" << (void*)audio_source
<< " headPos=(" << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.x
<< "," << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.z << ")\n" << std::flush; }
//
// If it is a transient source then ignore request
//
@@ -181,7 +185,7 @@ void
{
#ifdef LAB_ONLY
sourceClippedCount++;
#endif
#endif
return;
}
audio_source_priority = audio_source->GetAudioSourcePriority();
@@ -190,7 +194,25 @@ void
else
{
audio_source_priority = audio_source->GetAudioSourcePriority();
// (task #50, AUDIO_FIDELITY F19) COLD-START PRIME: an idle source's own
// watcher socket only executes at Start attempts, so its authored
// AudioControlSmoothers (footstep volume: N=30/15, fill 0) warmed ONE
// sample per attempt -- and the transient drop gate below (vol < 0.3)
// rejected the first ~25 footfalls (~10-20 s of silent steps) before
// the average could cross the gate. On a Start request, pump the
// source's watchers a full smoother window so the volume chain is
// evaluated at its true steady state; the smoother keeps its authored
// smoothing role for live variation once the source is playing.
if (message->controlID == StartAudioControlID)
{
audio_source->PrimeWatchers(30);
}
audio_source_volume_scale = audio_source->CalculateSourceVolumeScale();
if (getenv("BT_AUDIO_SPATIAL")) { static int s_vs=0; if ((s_vs++ % 120)==0)
DEBUG_STREAM << "[spatial] request src=" << (void*)audio_source
<< " vol=" << audio_source_volume_scale
<< " headPos=(" << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.x
<< "," << GetAudioHead()->GetHeadEntity()->localOrigin.linearPosition.z << ")\n" << std::flush; }
}
//
@@ -199,17 +221,38 @@ void
// Then return
//--------------------------------------------------------------------------
//
// DIAG (BT_AUDIO_NODROP): deliver low-volume transient starts anyway (at a
// floor volume) to isolate volume-feed problems from the rest of the chain.
if (getenv("BT_AUDIO_NODROP") &&
message->controlID == StartAudioControlID &&
audio_source->GetAudioRenderType() == TransientAudioRenderType &&
audio_source_volume_scale < LowAudioVolumeThreshold)
{
DEBUG_STREAM << "[spatial] NODROP forcing start src=" << (void*)audio_source
<< " vol=" << audio_source_volume_scale << "\n" << std::flush;
audio_source->ReceiveControl(VolumeAudioControlID, 0.7f);
audio_source_volume_scale = 0.7f;
}
if (
message->controlID == StartAudioControlID &&
audio_source->GetAudioRenderType() == TransientAudioRenderType &&
audio_source_volume_scale < LowAudioVolumeThreshold
)
{
if (getenv("BT_AUDIO_SPATIAL")) { static int s_dr=0; if (s_dr++<40)
DEBUG_STREAM << "[spatial] DROP transient start t=" << (GetTickCount() % 1000000)
<< " src=" << (void*)audio_source
<< " vol=" << audio_source_volume_scale
<< " (below threshold " << LowAudioVolumeThreshold << ")\n" << std::flush; }
#ifdef LAB_ONLY
sourceClippedCount++;
#endif
#endif
return;
}
if (getenv("BT_AUDIO_SPATIAL") && message->controlID == StartAudioControlID) {
static int s_st=0; if (s_st++<40)
DEBUG_STREAM << "[spatial] START request src=" << (void*)audio_source
<< " vol=" << audio_source_volume_scale << "\n" << std::flush; }
//
//--------------------------------------------------------------------------
+17
View File
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "munga.h"
#pragma hdrstop
@@ -67,6 +68,9 @@ void
{
Check(this);
Check(audio_component);
if (getenv("BT_AUDIO_SPATIAL")) { static int s_ev=0; if (s_ev++<200)
DEBUG_STREAM << "[seqev] tgt=" << (void*)audio_component
<< " ctl=" << (int)audioControlID << "/" << audioControlValue << "\n" << std::flush; }
audio_component->ReceiveControl(audioControlID, audioControlValue);
}
@@ -189,6 +193,16 @@ AudioControlSequence::AudioControlSequence(
audioControlEventSocket.Add(audio_control_event);
}
if (getenv("BT_ATTRBIND_LOG")) { static int s_sq=0; if (s_sq++<60) {
DEBUG_STREAM << "[seqcfg] seq=" << (void*)this << " tgt=" << (void*)audio_component
<< " looped=" << (int)is_looped << " div=" << (int)divisions_per_beat
<< " tempo=" << (int)tempo << " events=" << (int)number_of_control_events << " ";
{ SChainIteratorOf<AudioControlEvent*> it(&audioControlEventSocket);
AudioControlEvent *e; int n=0;
while ((e = it.ReadAndNext()) != NULL && n++ < 24)
DEBUG_STREAM << *e; }
DEBUG_STREAM << "\n" << std::flush; } }
AudioControlSequenceX(
audio_component,
entity,
@@ -378,6 +392,9 @@ Logical
void
AudioControlSequence::StartSequence()
{
if (getenv("BT_ATTRBIND_LOG")) { static int s_ss=0; if (s_ss++<40)
DEBUG_STREAM << "[seqstart] seq=" << (void*)this << "\n" << std::flush; }
Check(this);
//
+9
View File
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "munga.h"
#pragma hdrstop
@@ -416,6 +417,12 @@ AudioControlValue
volume_scale;
volume_scale = volumeScale * volumeMixScale * volumeCompressionScale;
if (getenv("BT_AUDIO_SPATIAL") && volume_scale <= 0.0f) {
static int s_z=0; if (s_z++<40)
DEBUG_STREAM << "[spatial] vol=0 breakdown src=" << (void*)this
<< " volumeScale=" << volumeScale
<< " mixScale=" << volumeMixScale
<< " compression=" << volumeCompressionScale << "\n" << std::flush; }
Clamp(volume_scale, MinAudioVolume, MaxAudioVolume);
return volume_scale;
}
@@ -656,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; }
}
//
+46
View File
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "munga.h"
#pragma hdrstop
@@ -134,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();
}
@@ -283,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();
}
@@ -873,6 +886,31 @@ AudioStateWatcher::AudioStateWatcher(
AudioWatcherOf<StateIndicator>(stream, entity)
{
Check_Pointer(attributePointer);
// BRING-UP GUARD [T3, temporary]: an AudioStateWatcher binds to a StateIndicator
// attribute BY NAME. The Mech's own state indicators (SimulationState, Animation/
// ReplicantAnimationState, CollisionState) are real; but audio also binds state
// attrs on subsystems that are not fully reconstructed yet (GeneratorState,
// CondenserState, Torso MotionState, ...). Those resolve to an unconstructed
// object -- a null/garbage vtable at +0 or a debug-fill (0xCDCDCDCD) watcher chain
// at +0x18 -- and AddAudioWatcher would AV in SChainOf::Add. Skip the register
// (that subsystem's state audio stays silent) until the subsystem is built; this
// is SELF-CLEARING (a real StateIndicator passes). See docs: audio subsystem wave.
// Validate the AUDIO SOCKET at +0x18 (what AddAudioWatcher touches), NOT the
// object's +0 vtable: a real StateIndicator has a vtable at +0, but the binary's
// 0x54 subsystem alarm (GaugeAlarm54) is non-polymorphic there (a raw header) yet
// has a real, constructed SChainOf socket at +0x18. A skip means the socket is
// unconstructed (null / debug-fill 0xCDCDCDCD) -- the inert pad or a subsystem not
// yet reconstructed. Registering there would AV in SChainOf::Add.
{
unsigned chain = *(unsigned*)((char*)attributePointer + 0x18);
if (chain == 0 || chain == 0xCDCDCDCD)
{
if (getenv("BT_AUDIO_LOG"))
DEBUG_STREAM << "[audiostate] skip watcher on unbuilt StateIndicator "
<< attributePointer << " (chain=" << (void*)chain << ")\n" << std::flush;
return;
}
}
Cast_Object(StateIndicator*, attributePointer)->AddAudioWatcher(this);
}
@@ -936,6 +974,14 @@ AudioStateTrigger::AudioStateTrigger(
MemoryStream_Read(stream, &excludeTransition);
MemoryStream_Read(stream, &excludeState);
if (getenv("BT_ATTRBIND_LOG")) { static int s_stc=0; if (s_stc++<120)
DEBUG_STREAM << "[statecfg] attrPtr=" << (void*)attributePointer
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
<< " trigState=" << triggerState << " inv=" << (int)inverseTrigger
<< " ctl=" << (int)controlID << "/" << controlValue
<< " excl=" << (int)excludeTransition << "/" << excludeState
<< "\n" << std::flush; }
PrimeWatcher();
}
+64
View File
@@ -1,4 +1,5 @@
#pragma once
#include <cstdlib>
#include "watcher.h"
#include "audio.h"
@@ -304,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 << " comp=" << (void*)audioComponentSocket.GetCurrent()
<< " thresh=" << attributeValueThreshold
<< " inverse=" << (int)inverseTrigger
<< " onID=" << (int)controlIDOn << "/" << controlValueOn
<< " offID=" << (int)controlIDOff << "/" << controlValueOff
<< "\n" << std::flush; }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -357,6 +366,10 @@ template <class T> void
Check_Pointer(attribute_ptr);
Scalar current_value = ExtractInterestingValue(attribute_ptr);
if (getenv("BT_AUDIO_SPATIAL") && current_value > 0.0f) { static int s_tg=0; if (s_tg++<120)
DEBUG_STREAM << "[trigger] attrPtr=" << (void*)attributePointer
<< " val=" << current_value << " thresh=" << attributeValueThreshold
<< " armed=" << (int)!triggerOn << "\n" << std::flush; }
if (triggerOn)
{
if (
@@ -432,6 +445,30 @@ public:
);
~AudioScaleOf();
//
//--------------------------------------------------------------------
// Execute -- scales send EVERY poll (task #50, AUDIO_FIDELITY F19).
//
// The base watcher gate is a BITWISE compare (Motion::operator== is
// memcmp) that existed to skip truly static values. In the original,
// scale-watched attributes (velocities, accelerations, temperatures)
// were noisy physics floats that practically changed every poll, so
// scales streamed per-poll values into their authored consumers -- the
// footstep AudioControlSmoothers (N=30/15, fill 0) are SIZED for that
// cadence. Our reconstruction's math can be deterministic (the gait
// integrator lands on bit-identical derived values during smooth
// acceleration), which froze the gate and starved the smoothers (the
// 10-20 s footstep warm-up). Sending unconditionally restores the
// original's practical behavior; triggers/matchers keep the change
// gate (their semantics are edge-based).
//--------------------------------------------------------------------
//
void
Execute()
{
GrabCurrentValue();
}
//
//--------------------------------------------------------------------
// BuildFromPage
@@ -508,6 +545,13 @@ template <class T>
MemoryStream_Read(stream, &controlValueBoundary1);
MemoryStream_Read(stream, &controlValueBoundary2);
MemoryStream_Read(stream, &exponent);
if (getenv("BT_ATTRBIND_LOG")) { static int s_scf=0; if (s_scf++<160)
DEBUG_STREAM << "[scalecfg] attrPtr=" << (void*)attributePointer
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
<< " ctlID=" << (int)controlID
<< " aB=[" << attributeValueBoundary1 << "," << attributeValueBoundary2 << "]"
<< " cB=[" << controlValueBoundary1 << "," << controlValueBoundary2 << "]\n" << std::flush; }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -625,6 +669,16 @@ 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++<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; }
if (getenv("BT_AUDIO_SPATIAL") && (controlID == 100 || controlID == 101)) {
static int s_fs2=0; if (s_fs2++<2000)
DEBUG_STREAM << "[fsscale] t=" << (GetTickCount() % 1000000)
<< " attrPtr=" << (void*)attributePointer
<< " comp=" << (void*)audioComponentSocket.GetCurrent()
<< " ctl" << (int)controlID << " raw=" << current_value
<< " out=" << control_value << "\n" << std::flush; }
audioComponentSocket.GetCurrent()->ReceiveControl(
controlID,
control_value
@@ -721,6 +775,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; }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -767,6 +827,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
+11
View File
@@ -11,6 +11,17 @@ public:
virtual void Execute();
// (task #50, AUDIO_FIDELITY F19) gate-free watcher pump for the audio
// transient cold-start prime. Default: run Execute() N times (attribute
// watchers re-read + re-send each pass). AudioComponent overrides it to
// RECURSE through its watcher chain, bypassing the per-component
// audio-frame gate that otherwise collapses the passes to one.
virtual void PrimeWatchers(int passes)
{
for (int pass = 0; pass < passes; ++pass)
Execute();
}
static Derivation *GetClassDerivations();
static SharedData DefaultData;
+60 -8
View File
@@ -11,6 +11,34 @@
#include "line.h"
#include "app.h"
#include "notation.h"
#include <math.h>
//
// EXACT axis-angle rotation composition -- matches the 1995 BT binary's angular
// integrator (FUN_00409f58): build a unit rotation quaternion from the rotation
// VECTOR `rotVec` (angle = |rotVec|, axis = rotVec/angle) as { axis*sin(angle/2),
// cos(angle/2) } and Hamilton-multiply it onto `base`. The dead-reckoner previously
// did `out.Add(base, rotVec)` -- adding a scaled angular-velocity vector to the heading
// quaternion. That is only a small-angle approximation: fine per-frame (tiny angle),
// but over a long replicant dead-reckon gap it DIVERGES (the heading drifts to ~180deg
// then snaps -- the spinning-peer hesitation). This composition is exact for any angle
// and stays on the unit sphere.
//
static void ExactAngularProject(Quaternion &out, const Quaternion &base, const Vector3D &rotVec)
{
const Scalar ang = rotVec.Length();
if (ang > 1.0e-6f)
{
const Scalar h = 0.5f * (Scalar)fmodf((float)ang, 6.2831853f); // half of angle mod 2pi
const Scalar s = (Scalar)(sinf((float)h) / ang); // sin(angle/2)/angle
const Quaternion dq(rotVec.x * s, rotVec.y * s, rotVec.z * s, (Scalar)cosf((float)h));
out.Multiply(base, dq); // base (X) dq
}
else
{
out = base;
}
}
//#############################################################################
//############################### Mover #################################
@@ -395,10 +423,8 @@ Logical
//-------------------------------
//
position_delta.Multiply(updateVelocity.angularMotion, time_slice);
projectedOrigin.angularPosition.Add(
updateOrigin.angularPosition,
position_delta
);
ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add)
updateOrigin.angularPosition, position_delta);
projectedVelocity = updateVelocity;
Check_Fpu();
@@ -460,10 +486,8 @@ Logical
updateVelocity.angularMotion,
time_slice
);
projectedOrigin.angularPosition.Add(
updateOrigin.angularPosition,
position_delta
);
ExactAngularProject(projectedOrigin.angularPosition, // was .Add (diverging vector-add)
updateOrigin.angularPosition, position_delta);
//
//-----------------------------------
@@ -1113,6 +1137,34 @@ BoxedSolid*
return solid;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// The "static world" tail of FindBoxedSolidHitBy on its own: ray-test ONLY the
// zone's static solid tree (world structures -- garages, walls, props), NOT the
// tangible movers/doors. Same tree the mech's walk collides against, so a shot
// designated through this lands on exactly the geometry that blocks the mech.
// FindBoundingBoxHitBy clips line->length to the hit distance (HitByBounded ->
// line->length = enter), so the caller reads the entry point via line->FindEnd.
//
BoxedSolid*
Mover::FindStaticSolidHitBy(
Line *line
)
{
Check(this);
Check(line);
InterestManager *interest_mgr = application->GetInterestManager();
Check(interest_mgr);
InterestZone *zone = interest_mgr->GetInterestZone(interestZoneID);
Check(zone);
BoxedSolidTree* tree = zone->GetCollisionRoot();
Check(tree);
BoxedSolid *result = (BoxedSolid*)tree->FindBoundingBoxHitBy(line);
Check_Fpu();
return result;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolidCollisionList*
+10
View File
@@ -300,6 +300,16 @@ public:
Line *line,
Entity *except_by
);
// STATIC-WORLD-ONLY ray query: the "test against the static world" tail of
// FindBoxedSolidHitBy factored out -- ray-tests ONLY the zone's static solid
// tree (the world structures: garages, walls, props), skipping the tangible
// movers/doors. Used by the weapon boresight pick so a shot lands on the SAME
// static geometry that already blocks the mech's walk (mechs are picked
// separately, with their damage zones/lock). Clips line->length to the entry.
BoxedSolid*
FindStaticSolidHitBy(
Line *line
);
BoxedSolidCollisionList*
CollideCenterOfMotion(
Line *line,
+11
View File
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "munga.h"
#pragma hdrstop
@@ -486,6 +487,14 @@ void
//#############################################################################
// Watcher Support
//
int
Simulation::DebugAudioWatcherCount()
{
SChainIteratorOf<Component*> iterator(audioWatcherSocket);
return (int)iterator.GetSize();
}
void
Simulation::ExecuteWatchers()
{
@@ -496,6 +505,8 @@ void
// Audio
{
SChainIteratorOf<Component*> iterator(audioWatcherSocket);
if (getenv("BT_AUDIO_SPATIAL")) { static int s_ec=0; if ((s_ec++ % 600)==0)
DEBUG_STREAM << "[watchpoll] sim=" << (void*)this << " audioSocket size=" << iterator.GetSize() << "\n" << std::flush; }
while ((watcher = iterator.ReadAndNext()) != NULL)
{
watcher->Execute();
+3
View File
@@ -247,6 +247,9 @@ public:
void
ExecuteWatchers();
int // DEBUG (BT_AUDIO_LOG): how many audio watchers are registered on this sim
DebugAudioWatcherCount();
private:
SChainOf<Component*>
audioWatcherSocket;
+40 -15
View File
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "munga.h"
#pragma hdrstop
@@ -95,21 +96,45 @@ AttributeWatcher::AttributeWatcher(
attributePointer = simulation->GetAttributePointer(attribute_name);
#if DEBUG_LEVEL>0
if (attributePointer == NULL)
{
Dump(attribute_name);
}
#else
if (attributePointer == NULL)
{
DEBUG_STREAM <<
"AttributeWatcher::AttributeWatcher - attribute " <<
attribute_name <<
"\n";
Fail("AttributeWatcher::AttributeWatcher - attribute not found\n");
}
#endif
if (getenv("BT_ATTRBIND_LOG"))
{
extern int g_curAudioWatcherClass;
DEBUG_STREAM << "[attrbind] class=" << g_curAudioWatcherClass
<< " subsys=[" << subsystem_name
<< "] attr=[" << attribute_name << "] -> ptr=" << attributePointer
<< " vtbl=" << (attributePointer ? *(void**)attributePointer : (void*)0)
<< "\n" << std::flush;
}
// BRING-UP GUARD [T3, temporary]: audio references attributes on subsystems that
// are not fully reconstructed yet (GeneratorState/On, CondenserState, ReportLeak,
// Torso SpeedOfTorsoHorizontal/MotionState, Reservoir/Avionics/ControlsMapper ...).
// GetAttributePointer returns NULL for those, and the original engine Fail()s
// (fatal) -- which would abort every audio-enabled run. Redirect NULL to a shared
// inert zero pad so scalar/vector watchers read 0 (that sound stays silent) instead
// of crashing; state watchers on it are skipped by the AudioStateWatcher guard.
// Self-clearing: a registered attribute resolves to its real member. Remove once
// the subsystem attribute tables are reconstructed (the audio subsystem wave).
if (attributePointer == NULL)
{
static char s_missingAttrPad[64] = {0};
// TYPED pad: ConfigureActivePress is the held-configure-button INDEX whose
// authored idle is -1 (none) -- NINE audio triggers gate the configure-mode
// ticker on it with threshold -1 across the configurable subsystems. The
// zero pad read as "button 0 held" and started the ticker at load (the
// eternal 2.7/s chirp). Redirect this attribute to a -1 pad; others keep 0.
static int s_configureIdlePad = -1;
CString configure_name("ConfigureActivePress");
if (attribute_name == configure_name)
attributePointer = &s_configureIdlePad;
else
attributePointer = s_missingAttrPad;
if (getenv("BT_AUDIO_LOG"))
DEBUG_STREAM << "[attrnull] " << subsystem_name << "." << attribute_name
<< " not reconstructed -> "
<< (attributePointer == (void*)&s_configureIdlePad ? "-1 pad" : "inert pad")
<< "\n" << std::flush;
}
Check_Pointer(attributePointer);
#endif
+30 -1
View File
@@ -1,4 +1,5 @@
#pragma once
#include <cstdlib>
#include "cmpnnt.h"
#include "slot.h"
@@ -155,14 +156,18 @@ private:
void
InitializeCurrentValue();
protected:
//
//-----------------------------------------------------------------------
// GrabCurrentValue
// GrabCurrentValue -- protected (task #50): AudioScaleOf's per-poll
// Execute override calls it directly (see AUDWTHR.h).
//-----------------------------------------------------------------------
//
void
GrabCurrentValue();
private:
//
//-----------------------------------------------------------------------
// DumpValue
@@ -262,8 +267,32 @@ template <class T> void
Check(this);
Check_Pointer(attributePointer);
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;
}
extern void *g_btAccelAddr; // DIAG: poll-vs-change split on the accel attr
if ((void*)attributePointer == g_btAccelAddr) {
static long s_ap=0, s_ac=0;
int changed = !(currentValue == *(T*)attributePointer);
if (changed) ++s_ac;
if ((++s_ap % 120)==0 || (changed && (s_ac % 30)==0))
DEBUG_STREAM << "[accwatch] t=" << (GetTickCount() % 1000000)
<< " poll#" << s_ap << " changes=" << s_ac
<< " memY=" << ((float*)attributePointer)[1]
<< " curY=" << ((float*)&currentValue)[1] << "\n" << std::flush;
}
}
if (!(currentValue == *(T*)attributePointer))
{
if (getenv("BT_AUDIO_SPATIAL")) { static int s_chg=0; if (s_chg++<60)
DEBUG_STREAM << "[watchpoll] CHANGE attrPtr=" << (void*)attributePointer << "\n" << std::flush; }
#if DEBUG_LEVEL>0
if (dumpValue)
{