wreck smoke dies WITH the wreck; the audio/effect clock is calibrated to the byte-proven 28

USER REPORT: "when you destroy a mech, it continues to smoke for a very long
time after the mech disappears... up to 30secs to a minute."

MEASURED (new [pfx] timed census with per-def particle attribution): the
death plume (psfx 1 DDTHSMK) was re-armed on a 10s cadence as a WORLD effect
(ownerTag=0) -- so neither the burial transition nor the respawn cleanup
(StopAllEntityEffects) could touch it.  The last-armed window kept emitting
over the empty spot for up to 10s after the hulk vanished, plus 6+-2s authored
particle lives, and back-to-back windows stretched the visible tail to the
reported range.  The re-arm itself is a marked PORT ADDITION [T3]; the binary
fired DDTHSMK once -- its 10s window + particle lives ended right around the
~17s hulk sink.  The authored design: the smoke dies WITH the burial.

FIX: the plume is spawned ATTACHED (BTStartPfxAttached, tagged to the dying
entity -- it also rides the sinking hulk now), and the burial transition calls
BTStopEntityPfx alongside the respawn cleanup that already did.  Verified over
a 175s run containing FOUR wreck events (the enemy kill + three player bay-
fire deaths): every plume's emission ends at its wreck's burial or respawn,
particles fade within ~8-11s, no orphan emitters remain.

THE COUPLING (user relay of Oracle: "prolonged smoke and explosion INCLUDING
SOUND... same lever?") -- CONFIRMED as a family: the binary's audio layer
times in FRAME COUNTS converted by the engine frame-rate global:
    AudioTime::Seconds_To_Frames == fmul [0x52140c]=28.0; fadd 0.5; round
    (@0x42c611, @0x42dd86 -- the 0x42xxxx cluster of the ~90 global consumers)
Our engine's DefaultRendererRate said 30: every audio duration, sequence
delay and compression window ran ~7% fast.  Calibrated to 28 [T1].  NOT yet
explained by this: a truly SUSTAINED/looping explosion sound (7% is not
"prolonged") -- the effect stop-path trace stays open on #114/#51.

Tooling: BTPfxParticle carries defIndex; [pfx] census (BT_PFX_LOG, 2s) prints
emitters + particles PER EFFECT SLOT with a timestamp; wrecksmoke.sh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-02 11:25:49 -05:00
co-authored by Claude Opus 5
parent 2df1369168
commit 6dbeb3d266
5 changed files with 88 additions and 4 deletions
+36
View File
@@ -745,6 +745,7 @@ struct BTPfxEmitter
struct BTPfxParticle
{
D3DXVECTOR3 pos, vel, accel;
int defIndex; // census attribution (#114 / wreck-smoke tail)
float age, life;
float rad, exp, dexp;
float colorWarp, alphaWarp; // def-level warps, carried per particle
@@ -875,6 +876,39 @@ void BTStartPfxFrame(int effect_number, float x, float y, float z,
gBTPfxEmitters.push_back(e);
}
// #114/wreck-smoke census (BT_PFX_LOG): every emitter start + a 2s population
// line, so "what is still smoking 40s after the kill" is a log fact.
static void BTPfxCensus()
{
if (!getenv("BT_PFX_LOG")) return;
static unsigned long s_at = 0;
unsigned long now = GetTickCount();
if (now - s_at < 2000) return;
s_at = now;
int perDef[BT_PFX_SLOTS]; memset(perDef, 0, sizeof(perDef));
int emitters = 0;
for (size_t i = 0; i < gBTPfxEmitters.size(); ++i)
if (gBTPfxEmitters[i].active)
{
++emitters;
int di = (int)(gBTPfxEmitters[i].def - gBTPfxDefs);
if (di >= 0 && di < BT_PFX_SLOTS) ++perDef[di];
}
int partDef[BT_PFX_SLOTS]; memset(partDef, 0, sizeof(partDef));
for (size_t i = 0; i < gBTPfxParticles.size(); ++i)
{
int di = gBTPfxParticles[i].defIndex;
if (di >= 0 && di < BT_PFX_SLOTS) ++partDef[di];
}
DEBUG_STREAM << "[pfx] t=" << (now / 1000) << "s census: emitters=" << emitters
<< " particles=" << (int)gBTPfxParticles.size();
for (int d = 0; d < BT_PFX_SLOTS; ++d)
if (perDef[d] > 0) DEBUG_STREAM << " {E" << d << ":" << perDef[d] << "}";
for (int d = 0; d < BT_PFX_SLOTS; ++d)
if (partDef[d] > 0) DEBUG_STREAM << " {p" << d << ":" << partDef[d] << "}";
DEBUG_STREAM << std::endl;
}
void BTStartPfx(int effect_number, float x, float y, float z)
{
BTStartPfxFrame(effect_number, x, y, z, 0, 0, 0);
@@ -964,6 +998,7 @@ static void BTPfxSpawn(const BTPfxEmitter &e)
// laser bursts during missile volleys)
return;
BTPfxParticle p;
p.defIndex = (int)(e.def - gBTPfxDefs); // census attribution
// Sample in the effect's LOCAL frame, then orient into the world through
// the emitter's basis (the victim's localToWorld rows). The position
// jitter pv is an isotropic scatter about the base offset -> symmetric
@@ -1181,6 +1216,7 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
}
}
}
BTPfxCensus();
for (size_t ei = 0; ei < gBTPfxEmitters.size(); ++ei)
{
BTPfxEmitter &e = gBTPfxEmitters[ei];