ONE Explosion per salvo -- the port detonated every visual round, amplifying explosion sound + smoke + audio demand by the missile count

The user challenged the 7% clock story ("i think its not just a 7% discrepancy.
did you base your analysis in the decomp") -- correctly.  The stop-path trace
it forced found the real amplifier, and it is byte-grounded:

THE ARCADE (byte-verified in the #95 work): FireWeapon @004bcc60 spawns ONE
Missile entity per trigger -- no loop, missileCount never read at fire.  One
Missile -> one detonation (@004be078) -> ONE Explosion entity: one 4-voice
sound + one authored smoke package per salvo.  The cluster's N-fold nature is
delivered as DAMAGE (burstCount), not as N explosions.

THE PORT: the N-round visual ripple (a deliberate [T3] port visual; its rounds
are damage-0 tracers) called BTSpawnRoundDetonation at EVERY round's contact --
N full Explosion entities per salvo, N x BigExpInt x 4 voices, N x the
explosion PFX, N=2..20.  Measured in a 100s solo missile bench: 559 explosion
sound setups; BigExpInt 208.  On observer nodes the stale-point mirror bug
(fixed in da70bd5) DOUBLED the train again.

One infidelity, three field symptoms:
  * Oracle: "prolonged smoke and explosion including sound" -- a staggered
    train of 6-20 overlapping BigExpInt instances per salvo reads as one long
    explosion+roar (Rajel correctly tied it to #84)
  * Ronin: missile-impact smoke "so much it functioned as a smoke screen"
  * #32: the saturating audio class was 1005 Static3DPatchSource requested=4
    -- each surplus detonation reserved 4 more voices

FIX: BTProjectile carries salvoLead; both detonation sites (target contact +
world/terrain burst) gate on it.  Master salvo: lead = round 0 (the damage
carrier).  Mirror salvo: round 0, so observers see exactly one explosion per
salvo at the live target.  AC/laser single rounds default lead=1 (one round =
one explosion, authentic).  Non-lead rounds keep their small tracer end-puffs.

MEASURED A/B (same bench): 80 of 96 contacting rounds no longer spawn the
package; 16 leads ~= the salvo count.  BigExpInt setups 208 -> 72 (~18
packages for ~16 salvos + deaths = one per salvo).  LaserExplosion unchanged
in kind.  Splash was already lead-only (splashBurst) -- damage totals move
NOWHERE in this change.

Also corrected the rack-tube comment that claimed "the binary's Missile
entities each launch from their own rack tube... their 12 detonations" -- there
are no 12 binary missiles; the ripple is ours.

The 28Hz audio-clock calibration from the previous commit stands, but is
demoted to what it is: a small systemic correction, not the explanation for
the field reports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-02 12:00:25 -05:00
co-authored by Claude Opus 5
parent 6dbeb3d266
commit 9b1353bfbf
3 changed files with 34 additions and 13 deletions
+26 -9
View File
@@ -837,6 +837,18 @@ struct BTProjectile {
int weaponSubsys; // firing weapon's roster index (-1 = unthreaded) --
// resolves the weapon's ExplosionModelFile (mslhit/
// acanhit) in the messmgr, task #7 bundling
int salvoLead; // ONE detonation per salvo (2026-08-02, byte-grounded:
// FireWeapon @004bcc60 spawns ONE Missile per trigger --
// no loop -- so the arcade made ONE Explosion entity
// (one 4-voice sound + one smoke package) per salvo.
// Our N-round visual ripple is a port addition [T3];
// detonating EVERY round amplified explosion audio +
// impact smoke + Static3DPatchSource demand by the
// missile count (2-20x): Oracle's 'prolonged smoke and
// explosion including sound', Ronin's smoke screen, and
// a large share of #32's class-1005 saturation. Only
// the lead round spawns the Explosion; the rest keep
// their small tracer end-puffs.
int splashBurst; // SALVO-LEAD cluster count for splash (task #62 fix):
// >0 ONLY on the first round of a missile salvo -- the
// baseBurst for ONE splash event = the whole cluster
@@ -1309,7 +1321,7 @@ void
BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos,
Scalar speed, Scalar damage, const Vector3D *launch_velocity, int guided,
int weapon_subsys, int splash_burst, int muzzle_seg, int damage_type,
Scalar thrust_accel, Scalar thrust_burn)
Scalar thrust_accel, Scalar thrust_burn, int salvo_lead)
{
// MUZZLE (muzzle wave, 2026-07-12): the passed muzzle is now the weapon's
// AUTHENTIC mount segment (GetMuzzlePoint reads the real segmentIndex --
@@ -1438,14 +1450,17 @@ void
p.shooter = (Entity *)shooter;
p.weaponSubsys = weapon_subsys;
p.splashBurst = splash_burst; // >0 only on a salvo-lead round
p.salvoLead = salvo_lead; // 1 = spawns the Explosion at contact
p.accel = (thrust_accel > 0.0f) ? thrust_accel : 0.0f; // #84 thruster
p.burnLeft = (thrust_burn > 0.0f) ? thrust_burn : 0.0f;
// RACK-TUBE SPREAD [T3, physically grounded]: the binary's Missile
// entities each launch from their own rack tube (per-tube authored
// MuzzlePosition); our single muzzle superimposed a whole salvo onto
// ONE trajectory -- 12 rounds read as one round, and their 12
// detonations stacked into one frame. Approximate the tube offsets
// RACK-TUBE SPREAD [T3 visual]. ⚠ CORRECTED 2026-08-02: the binary
// fires ONE Missile entity per trigger (FireWeapon @004bcc60 -- no
// loop, byte-verified in the #95 work); there are no "12 binary
// missiles". The N-round ripple below is a PORT VISUAL for the
// cluster; its rounds are damage-0 tracers, and only the salvo LEAD
// spawns the Explosion package at contact (salvoLead), matching the
// arcade's one-explosion-per-salvo economy. Approximate the tube offsets
// with a small deterministic per-slot cone (+-~2.5 deg) + lateral
// muzzle offset; the seeker re-converges them onto the target so the
// volley arrives as the demo's RIPPLE of impacts.
@@ -1587,7 +1602,8 @@ static void
// puff cluster, no damage
{
Point3D hp; hp.x = hx; hp.y = hy; hp.z = hz;
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, hp);
if (p.salvoLead) // ONE Explosion per salvo (@004bcc60)
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, hp);
// SPLASH (task #62): the binary missile detonates on ANY
// geometry and still runs SplashDamage (the +0x360 gate sits
// inside the collision branch @part_013.c:10045, which fires
@@ -1682,11 +1698,12 @@ static void
if (getenv("BT_PROJ_LOG"))
DEBUG_STREAM << "[projectile] DET at(" << hitPos.x << ","
<< hitPos.y << "," << hitPos.z << ")"
<< " dmg=" << p.damage
<< " dmg=" << p.damage << " lead=" << p.salvoLead
<< " tgt=" << (void *)p.target
<< " aim(" << p.targetPos.x << "," << p.targetPos.y
<< "," << p.targetPos.z << ")" << std::endl;
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, hitPos);
if (p.salvoLead) // ONE Explosion per salvo (@004bcc60)
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, hitPos);
Entity *tgt = p.target;
// Deliver to the projectile's target mech -- the launcher set p.target
// from the shooter's 0x388 slot (the picked victim; any peer mech in