//===========================================================================//
// File:	mech4.cpp                                                           //
// Project: BattleTech           Brick: Entity Manager                        //
// Contents: Mech per-frame simulation: motion integration, move-and-collide, //
//           weapon-impact damage routing, cockpit-gauge feed                 //
//           -- fourth implementation slice                                   //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
//  --/--/95  ??   Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C in
// all/part_012.c, cluster 0x004ab188-0x004ac064) cross-referenced with the
// mech.hpp/mech2.cpp member maps and the surviving damage-state keyword table
// at .rdata:0050de74.
//
// The decompiler tagged every function in this window as file=? .  Attribution
// to mech4.cpp is by RANGE (this is the upper half of the 0x4a8054-0x4ac868
// gap, just below where heat.cpp's HeatableSubsystem family begins at
// 0x4ac530 / 0x4ac644) and by COHESION: these are the Mech's PER-FRAME runtime
// path, the consumers of the gait clips that mech2.cpp/mech3.cpp produced.
//
//      @004ab188  Mech::DeadReckonPose       (63 bytes; helper)
//      @004ab1c8  Mech::IntegrateMotion      (615 bytes; mentioned by mech2.cpp)
//      @004ab430  Mech::ReplicantPerformance (1432 bytes; REPLICANT-only interior --
//                         dead-reckon + ground snap, no collisions; wrapped by the
//                         perf @004ab9d8 = PTR_LAB_0050c0e8.  CORRECTED by the
//                         ground-model-decode workflow: NOT "Simulate"; the MASTER
//                         per-frame performance is FUN_004a9b5c = PTR_LAB_0050c0f4.)
//      @004abb40  Mech::ProcessCollision     (1284 bytes; vtable slot +0x3c -- the
//                         override of the engine protected virtual
//                         Mover::ProcessCollision.  CORRECTED: the earlier draft
//                         misread it as a weapon sweep "ResolveWeaponImpact".)
//      @004ac04c  Mech::SetDuckedCollisionTemplate  (23 bytes; template maxY <- 0x51c)
//      @004ac064  Mech::SetStandingCollisionTemplate(23 bytes; template maxY <- 0x518;
//                         earlier "heat gauge feeder" labels were wrong -- this+0x2ec
//                         is the engine Mover collisionTemplate)
//      @004ac194  Mech::LookupDamageState    (61 bytes; static keyword parse)
//
// These are Mech METHODS.  The Mech class declaration is owned by
// mech.cpp / mech.hpp; this file declares no header of its own.  Member
// offsets it touches are documented in the "Mech runtime member map" block
// below for the mech.hpp owner to fold in (see report).  The decomp addresses
// the object as int words, so this+0xNN there == byte offset 0xNN here.
//
//---------------------------------------------------------------------------//
// NOT recovered here (sit in this address window but are NOT Mech methods):
//   @004a9b5a-@004ab188   the ~5.6 KB "undefined" gap IS the MASTER per-frame
//                         performance FUN_004a9b5c (PTR_LAB_0050c0f4, installed
//                         as activePerformance for non-replicant mechs, ctor
//                         part_012.c:9947-9956) -- decoded from the raw asm by
//                         the ground-model-decode workflow; its ground/collision
//                         half is reconstructed as AuthenticGroundAndCollide +
//                         the real Mech::ProcessCollision below (task #15).
//   @004ac07c,@004ac0bc,@004ac144,@004ac1d4,@004ac22c,@004ac274  -- methods of
//                         a HeatableSubsystem-family class (owner-Mech ptr at
//                         this[0x34]/+0xD0, inner heat object at +0xE0; vtable
//                         0050e210).  These belong to heat.cpp, NOT Mech.
//   @004ac4fc            free lerp helper.   @004ac530/@004ac644  HeatableSubsystem
//                         ctors -- heat.cpp.
//
//---------------------------------------------------------------------------//
// Helper / engine routine name mapping used below:
//      FUN_0049fb54  Mech::IsDisabled() -> Logical
//      FUN_004a5678  Mech::AdvanceBodyAnimation(dt, loop)          [mech2.cpp]
//      FUN_004a5bf8  Mech::AdvanceBodyAnimationAirborne(dt, loop)  [mech2.cpp]
//      FUN_004ab188  Mech::DeadReckonPose(dt)
//      FUN_004ab1c8  Mech::IntegrateMotion(dt, loop) -> Logical (disabled?)
//      FUN_004086ac  Vector::Scale(out, in, scalar)
//      FUN_00409f58  ReconQuatIntegrate(out, base, omega)
//      FUN_00408644  Vector::Subtract(out, a, b)
//      FUN_00408614  Vector::AddScaled(out, a, b, t)
//      FUN_004085ec  Vector::Add(out, a, b)
//      FUN_00408848  Vector::Lerp(out, a, w0, b, w1)
//      FUN_00408440  Assign/Copy(dst, src)              (struct or string copy)
//      FUN_00408644/00408614  see above
//      FUN_0040a7f4  ReconQuatIdentity(q, &kIdentity)
//      FUN_0040aadc  ReconMatrix::Identity(m)
//      FUN_0040ab44  Matrix34::FromQuaternion(m, q)
//      FUN_0040a938  Matrix34::SetRotation(m, q)
//      FUN_0040a4d8  ReconQuatSlerp(out, a, b, t)
//      FUN_0040e36c  BoundingBoxTreeNode::FindSmallestNodeContainingColumn
//                    (BOXTREE.CPP:503; CORRECTED -- earlier "Terrain::CellAt")
//      FUN_0040e5f0  BoundingBoxTreeNode::FindBoundingBoxUnder(point, &height)
//                    (BOXTREE.CPP:867; the ground-height query; h=-1 = miss;
//                    CORRECTED -- earlier "Terrain::HeightAt" / "heightfield")
//      FUN_00433ed4  InterestManager::GetInterestZone (zone -> collision tree root)
//      FUN_00421b6c / 00421b2c  Mover::UpdateLocalMotion / UpdateWorldMotion
//                    (CORRECTED -- earlier "BeginPose/EndPose")
//      FUN_0043ade4  FilteredScalar::Push(filter, sample)
//      FUN_0043ae47  FilteredScalar::Sum(filter)
//      FUN_0043ae0b  FilteredScalar::Average(filter)
//      FUN_00422ff8  Mover::StaticBounce (MOVER.CPP:1421; CORRECTED -- earlier
//                    "Mech::ComputeImpactDamage")
//      FUN_0041a1a4  ReconIsDerived(classID)
//      FUN_0041db7c  DamageMessage::DamageMessage(&msg)  (ctor/clear)
//      FUN_00420ea4  CString::CString(dst, src)
//      FUN_0041bbd8  AlarmIndicator::SetLevel(alarm, n)
//      FUN_004d4b58  Strcmp(a, b) -> 0 if equal
//
// Read-only constants resolved from CODE literal pools (see section_dump):
//      DAT_0052140c        ticks-per-second timing scale (runtime global)
//      _DAT_004ab9c8 = 1.0f     _DAT_004ab9cc = 0.2f
//      _DAT_004ab9d0 = -1.0f    _DAT_004ab9d4 = 0.0f
//      _DAT_004ac044 ~= -1.0e-4f (back-face/behind cull threshold)
//      _DAT_004ac048 = 0.0f
//      &DAT_004e0f74 = "" / zero vector ;  &DAT_004e0fd4 = quaternion identity
//

#include <bt.hpp>
// LBE4ControlsManager (the fire buttonGroup push, task #5) -- needs the
// btl4app/btl4mode forward-decl order btl4mppr.cpp uses.
#if !defined(BTL4APP_HPP)
#	include <btl4app.hpp>
#endif
#if !defined(BTL4MODE_HPP)
#	include <btl4mode.hpp>
#endif
#if !defined(L4CTRL_HPP)
#	include <l4ctrl.hpp>
#endif
#pragma hdrstop

#if !defined(MECH_HPP)
#	include <mech.hpp>		// Mech class -- owned by mech.cpp slice
#endif
#if !defined(MECHMPPR_HPP)
#	include <mechmppr.hpp>	// MechControlsMapper -- the real-controls bridge/consumption
#endif
#if !defined(APP_HPP)
#	include <app.hpp>
#endif
#if !defined(SUBSYSTM_HPP)
#	include <subsystm.hpp>	// Subsystem -- the per-frame roster entry (PerformAndWatch)
#endif
#if !defined(EXPLODE_HPP)
#	include <EXPLODE.hpp>	// Explosion::Make / MakeMessage -- bring-up fire effect
#endif
#if !defined(DAMAGE_HPP)
#	include <DAMAGE.hpp>	// Damage -- the per-hit damage payload
#endif
#if !defined(MECHDMG_HPP)
#	include <mechdmg.hpp>	// Mech__DamageZone (complete type -- Zone()->structureLevel)
#endif
// AUTHENTIC GROUND MODEL (task #15, ground-model-decode): complete engine types
// for the probe/snap/response block + the real ProcessCollision override.
#include <BOXTREE.hpp>		// BoundingBoxTreeNode::FindBoundingBoxUnder / ...ContainingColumn
#include <BOXSOLID.hpp>		// BoxedSolid / BoxedSolidCollision / BoxedSolidCollisionList
#include <cultural.hpp>		// CulturalIcon::IsStoppingCollisionVolume / GetClassDerivations
#include <hostmgr.hpp>		// HostManager::GetEntityPointer (band-effect attacker resolve)
#include <messmgr.hpp>		// SubsystemMessageManager (task #7 consolidated delivery)
#include <mechweap.hpp>		// MechWeapon::GetExplosionResourceID (per-round detonation)
#include <mislanch.hpp>		// MissileLauncher::ClassDerivations (splash-radius gate)
#if !defined(PLAYER_HPP)
#	include <player.hpp>	// Player::VehicleDeadMessage -- the death->respawn notification (task #52)
#endif
#if !defined(EMITTER_HPP)
#	include <emitter.hpp>	// Emitter/PPC beam state (the per-weapon beam render walk)
#endif

static const Scalar kBehindCull = -1.0e-4f;		// _DAT_004ac044


//###########################################################################
// BASE-REGION LAYOUT LOCK (P3 STEP-6 audit -- the shared P3/P5/gyro de-risk).
//
// Compile-time proof of the ground-truth layout (cdb `dt btl4!Mover/JointedMover/
// Mech`).  The 1995 raw offsets IntegrateMotion/Simulate still use land ON these
// engine-base fields; these asserts pin exactly where they are, so the base-region
// reconciliation (see btbuild/P3_LOCOMOTION.md "BASE-REGION RECONCILIATION") is
// verifiable and any raw-offset stomp becomes provable at compile time.  Friend of
// Mech (mech.hpp) for inherited-member offsetof access.
//###########################################################################
struct MechBaseLayoutCheck
{
	// engine Mover/JointedMover base -- the fields the stale raw offsets corrupt:
	static_assert(offsetof(Mech, localToWorld)         == 0x0C8, "localToWorld@0xC8");
	static_assert(offsetof(Mech, localOrigin)          == 0x0F8, "localOrigin@0xF8 (raw this+0x100 stomps here)");
	static_assert(offsetof(Mech, projectedOrigin)      == 0x250, "projectedOrigin@0x250 (raw this+0x260 stomps here)");
	static_assert(offsetof(Mech, previousOrigin)       == 0x26C, "previousOrigin@0x26C (raw this+0x26c stomps here)");
	static_assert(offsetof(Mech, projectedVelocity)    == 0x288, "projectedVelocity@0x288 (raw this+0x298 stomps here)");
	static_assert(offsetof(Mech, collisionVolumeCount) == 0x2D4, "collision cluster @0x2D4 (raw this+0x2d4 stomps here)");
	static_assert(offsetof(Mech, collisionLists)       == 0x2E4, "collisionLists@0x2E4 (the P5 teardown victim)");
	static_assert(offsetof(Mech, segmentTable)         == 0x2F0, "segmentTable@0x2F0");
	// reconstructed Mech relocated members -- the CORRECT reconciliation targets.
	// (These sit BEFORE legAnimation/bodyAnimation in declaration order, so they do
	// NOT shift when those controllers grow to the real SequenceController size.)
	static_assert(offsetof(Mech, torsoAimTarget)       == 0x3E0, "torsoAimTarget@0x3E0 (raw this+0x2a4 should be this)");
	static_assert(offsetof(Mech, netOrientation)       == 0x3EC, "netOrientation@0x3EC (raw this+0x2d4 should be this)");
	// NOTE: arrivalTime/simTime/spinRate/the gait-accumulator members are declared
	// AFTER legAnimation/bodyAnimation, so they shift with the SequenceController
	// growth.  They are accessed BY NAME only (never at a raw external offset), so
	// their absolute offset is not load-bearing and is intentionally NOT locked.
};


//###########################################################################
//##################   Mech runtime member map (offsets)   ##################
//###########################################################################
//
// For the mech.hpp owner.  Continues the mech.hpp / mech2 maps; "?" flags an
// uncertain semantic.  Scalar unless noted.
//
//   @0x010 simTime              (Time)   current frame time (Mover base)
//   @0x028 movementFlags        (Word)   base motion flags; &0xC==4 => "arrived"
//   @0x040 movementMode         (int)    gait/jump selector (mech2)
//   @0x100 maxSpeed             (Scalar) (mech.hpp; here read as a vector w)
//   @0x260 motionDelta          (Vector) accumulated per-frame world translation
//   @0x26c worldPose            (Quaternion) integrated body orientation (this[0x9b])
//   @0x298 torsoAimCurrent      (mech.hpp) reused as angular-impulse accumulator
//   @0x2a0 spinRate                       = -bodyCycleDistance/dt
//   @0x2a4 torsoAimTarget       (mech.hpp) snapshot of netOrientation @0x2d4
//   @0x2d4 netOrientation       (mech.hpp)
//   @0x2e0 arrivalTime          (Time)    dead-reckon target timestamp
//   @0x2e8 collisionVolume      (BoxedSolid*)  ENGINE Mover member (1995 MOVER.HPP:
//                               370).  CORRECTED by the ground decode -- earlier
//                               "physicsBody/weapon sweep" reading was wrong;
//                               vtable+0x1c on it is BoxedSolid::ProcessCollision.
//   @0x2ec collisionTemplate    (BoxedSolid*)  ENGINE Mover member.  The "heat
//                               gauge / groundRef" conflict is resolved: the duck
//                               swappers write template->maxY(+0xC).
//   @0x2f0 containedByNode      (BoundingBoxTreeNode*)  ENGINE Mover member --
//                               the cached collision-tree node (GetMoverCollisionRoot).
//   @0x2f8 lastCollisionList    (BoxedSolidCollisionList*)  ENGINE Mover member.
//   @0x344 forwardCycleRate     (mech2)   set each frame from 0x5b8/0x5bc
//   @0x3f4 airborneSelect       (int)     0 => use groundCycleRate, else airborne
//   @0x44c collisionTemporaryState (int)  CORRECTED (was "ammoState"): zeroed
//                               pre-collision-list each frame (@4aa741); the
//                               ProcessCollision state tail writes 1/2.  DEFERRED
//                               together (see BINARY-TAIL-DEFERRED markers).
//   @0x4b8 templateBottomLift   (mech.hpp)  ctor: 0.05 x volume X width
//   @0x518 standingTemplateMaxY (mech.hpp)  CORRECTED (was "heatLevel")
//   @0x51c duckedTemplateMaxY   (mech.hpp)  CORRECTED (was "heatCapacity"); 0.6 x standing
//   @0x580 jumpCapable          (mech3)
//   @0x598 motionEventName      (mech2)
//   @0x5a4 motionEventArmed     (mech2)
//   @0x5b8 groundCycleRate      (mech3)
//   @0x5bc airborneCycleRate    (mech3)
//   @0x778 creationTime         (mech.hpp)
//   @0x77c motionEventPending   (int)     queued footstep/turn event flag
//   @0x7e0 telemetryFilter[5]   (mech.hpp this[0x1f8..0x204]) 15-sample filters;
//                               fed headPitch/torsoTwist/turretBase/legAngle + dt
//   @0x81c prevTele[4]          this[0x207..0x20a] last filtered angle samples
//   @0x1dc..0x1ec aimRate[4]    this[0x77..0x7b] telemetry-derived angular rates
//                               (cockpit/aim feed: d(angle)/dt across the filter)
//


//###########################################################################
//###########################################################################
//					DeadReckonPose
//
// @004ab188
//
// Blend the net (server) orientation @0x2d4 toward the live body pose by a
// fraction of dt and fold the result onto the world position quaternion at
// @0x26c.  Tiny leaf called once per frame from IntegrateMotion.
//###########################################################################
//###########################################################################
void
	Mech::DeadReckonPose(Scalar fraction)
{
	Vector3D scaled;
	Vector::Scale(&scaled, &netOrientation, fraction);		// FUN_004086ac(.,this+0x2d4,frac)
	ReconQuatIntegrate(						// FUN_00409f58
		&worldPose,									// was raw 0x26c (stomped previousOrigin)
		&worldPoseBase,								// was raw 0x138 (stomped updateOrigin)
		&scaled);
}


//###########################################################################
//###########################################################################
//					IntegrateMotion
//
// @004ab1c8   (named by mech2.cpp's banner as Mech::IntegrateMotion)
//
// Advance the *displayed-motion* (channel-B) body gait one frame and integrate
// the resulting cycle distance into the world transform.  Picks the airborne
// body updater when (movementMode==3||4) && jumpCapable, else the ground one.
// Also fires the queued footstep / motion event when the mech "arrives".
// Returns the IsDisabled() result captured at entry (the caller branches on it).
//###########################################################################
//###########################################################################
Logical
	Mech::IntegrateMotion(Scalar time_slice, int loop)
{
	// Choose this frame's forward-cycle slew rate.
	forwardCycleRate = airborneSelect ? airborneCycleRate : groundCycleRate;	// 0x344<-0x5bc/0x5b8

	// (FootStep pulse decay moved to PerformAndWatch -- this integrator does not
	// run on the current gait path, so a decay here never fired.)

	if (IsDisabled())								// FUN_0049fb54
	{
		ReconQuatIdentity(&angularAccum, &kIdentityQuat);		// was raw this+0x298 (stomped projectedVelocity)
		return True;
	}

	// Time remaining to the dead-reckon arrival target (or elapsed since
	// creation if we have already arrived).
	Scalar	dr;
	Logical	arrived;
	if ((movementFlags & 0xC) == 4 && simTime < arrivalTime)	// 0x28, 0x10, 0x2e0
	{
		dr      = (Scalar)(arrivalTime - *(Scalar *)&creationTime) / DAT_0052140c;
		arrived = True;
	}
	else
	{
		dr      = (Scalar)(simTime - *(Scalar *)&creationTime) / DAT_0052140c;
		arrived = False;
	}

	// Fire any queued motion event (footstep / turn) on arrival.
	if (motionEventPending)							// 0x77c
	{
		if ((movementFlags & 0xC) == 4)
		{
			FUN_00408644((Scalar *)&motionEventVector,	// was raw 0x598/0x12c/0x100
				(Scalar *)&motionSourceB, (Scalar *)&motionSourceA);
			motionEventArmed   = 1;					// 0x5a4
			motionEventPending = 0;
		}
		else
		{
			Assign((void *)&motionDelta, (void *)&motionSourceB);	// was raw 0x260 <- 0x12c
			motionEventPending = 0;
		}
	}

	// Advance the body gait (airborne flavour while jumping).
	Scalar cycleDistance;
	if ((MovementMode() == 3 || MovementMode() == 4) && jumpCapable)	// 0x40, 0x580
		cycleDistance = AdvanceBodyAnimationAirborne(time_slice, loop);	// FUN_004a5bf8
	else
		cycleDistance = AdvanceBodyAnimation(time_slice, loop);			// FUN_004a5678

	// The raw velocity vector (0x298) = {0, 0, -cycleDistance/dt}; the world-step
	// FUN_00408744 reads all three components, so 0x2a0 (== angularAccum[2]) MUST be
	// set here -- the earlier draft wrote only the separate `spinRate`@0x508 and left
	// angularAccum[2] stale (a latent bug, never exercised while this fn was dead).
	spinRate                     = -cycleDistance / time_slice;	// 0x2a0 mirror (telemetry)
	((Scalar *)&angularAccum)[2] = -cycleDistance / time_slice;	// 0x2a0 velocity.z (forward)
	((Scalar *)&angularAccum)[1] = 0.0f;						// 0x29c velocity.y
	((Scalar *)&angularAccum)[0] = 0.0f;						// 0x298 velocity.x

	DeadReckonPose(dr);									// FUN_004ab188
	Assign((void *)&torsoAimTarget, (void *)&netOrientation);	// was raw 0x2a4 <- 0x2d4

	// Build the per-frame world-translation increment and fold it onto
	// motionDelta and worldPose (declared members; were raw 0x260 / 0x26c).
	Matrix34	bodyFrame;
	ReconMatrix::Identity(&bodyFrame);						// FUN_0040aadc
	Matrix34::FromQuaternion(&bodyFrame, &motionDelta);		// FUN_0040ab44 (was raw 0x260)

	Vector3D	worldStep;
	FUN_00408744(&worldStep, (Scalar *)&angularAccum, &bodyFrame);	// was raw 0x298

	Vector3D	tmp;
	Assign(&tmp, &kZeroVector);							// &DAT_004e0f74
	Vector::AddScaled(&tmp, &tmp, &worldStep, time_slice);
	Vector::Add((Scalar *)&motionDelta, (Scalar *)&motionDelta, &tmp);	// was raw 0x260

	Assign(&tmp, &kZeroVector);
	Vector::AddScaled(&tmp, &tmp, (Scalar *)&torsoAimTarget, time_slice);	// was raw 0x2a4

	Quaternion	prevPose = worldPose;					// was raw 0x26c
	ReconQuatIntegrate(&worldPose, &prevPose, &tmp);	// was raw 0x26c

	return arrived;
}


//###########################################################################
//###########################################################################
//					Simulate     (per-frame move-and-collide + telemetry)
//
// @004ab430
//
// The Mech's main per-frame tick.  Integrate body motion, slerp the live
// orientation / torso toward the dead-reckon target, drop the body onto the
// terrain (query terrain height at the new cell and correct the vertical),
// notify the renderer, then push the head/torso/turret/leg angles through the
// 15-sample telemetry FilteredScalars and emit the per-axis angular RATES used
// by the cockpit and the aiming reticle.
//###########################################################################
//###########################################################################
void
	Mech::Simulate(Scalar time_slice)
{
	ReconQuatIdentity(&aimRate, &kIdentityQuat);		// clear aim/torso (was raw this+0x1dc -> localAcceleration)

	Logical arrived = IntegrateMotion(time_slice, 1);		// FUN_004ab1c8

	if (!arrived)
	{
		// Live integration branch.
		if (*(int *)(this + 0x5a4) == 1)				// motionEventArmed
		{
			Vector3D ev = *(Vector3D *)(this + 0x598);
			Scalar   span = (_DAT_004ab9cc /*0.2*/ -
				(Scalar)(*(int *)(this + 0x10) - *(int *)(this + 0x14)) / DAT_0052140c)
				+ time_slice;
			if (span <= time_slice)
				*(int *)(this + 0x5a4) = 0;
			else
				Vector::Scale(&ev, &ev, time_slice / span);
			Vector::Add((Scalar *)(this + 0x260), (Scalar *)(this + 0x260), &ev);
			*(int *)(this + 0x264) = *(int *)(this + 0x104);
			Matrix34::SetRotation((Matrix34 *)(this + 0x100), (Quaternion *)(this + 0x260));
			FUN_00408644((Scalar *)(this + 0x598), (Scalar *)(this + 0x598), &ev);
		}
		else
		{
			*(int *)(this + 0x264) = *(int *)(this + 0x104);
			Matrix34::SetRotation((Matrix34 *)(this + 0x100), (Quaternion *)(this + 0x260));
		}
		Assign((void *)(this + 0x1f4), (void *)(this + 0x298));		// legAngle  <- accumulator
		Assign((void *)(this + 0x1d0), (void *)(this + 0x2a4));		// headPitch <- aim target
	}
	else
	{
		// Dead-reckon / arrived branch: slerp orientation & torso to target.
		Scalar t = time_slice /
			((Scalar)(*(int *)(this + 0x2e0) - *(int *)(this + 0x10)) / DAT_0052140c + time_slice);
		ReconQuatSlerp((Quaternion *)(this + 0x10c), (Quaternion *)(this + 0x10c),
			(Quaternion *)(this + 0x26c), t);						// FUN_0040a4d8
		FUN_00408848((Scalar *)(this + 0x1d0), (Scalar *)(this + 0x1d0),
			_DAT_004ab9c8 - t, (Scalar *)(this + 0x2a4), t);		// headPitch lerp
		if (*(int *)(this + 0x5a4) == 1)
		{
			Vector3D ev = *(Vector3D *)(this + 0x598);
			Scalar   span = (_DAT_004ab9cc -
				(Scalar)(*(int *)(this + 0x10) - *(int *)(this + 0x14)) / DAT_0052140c)
				+ time_slice;
			if (span <= time_slice)
				*(int *)(this + 0x5a4) = 0;
			else
				Vector::Scale(&ev, &ev, time_slice / span);
			Vector::Add((Scalar *)(this + 0x260), (Scalar *)(this + 0x260), &ev);
			*(int *)(this + 0x264) = *(int *)(this + 0x104);
			Assign((void *)(this + 0x100), (void *)(this + 0x260));
			FUN_00408644((Scalar *)(this + 0x598), (Scalar *)(this + 0x598), &ev);
		}
		else
		{
			*(int *)(this + 0x264) = *(int *)(this + 0x104);
			Assign((void *)(this + 0x100), (void *)(this + 0x260));
		}
		FUN_00408848((Scalar *)(this + 0x1f4), (Scalar *)(this + 0x1f4),
			_DAT_004ab9c8 - t, (Scalar *)(this + 0x298), t);		// legAngle lerp
	}

	// --- Drop onto terrain (skip if the "no-collide" flag 0x40 is set) ------
	if ((*(byte *)(this + 0x29) & 0x40) == 0)
	{
		(*(void (**)(Mech *))(*(int **)this + 13))(this);		// vtable slot 13 (build world xform)
		int  world   = *(int *)(DAT_004efc94 + 0x30);
		int  grid    = FUN_00433ed4(world + 0x14);				// World::Terrain
		int *cellTab = *(int **)(grid + 0x30);
		*(int *)(this + 0x2f0) = FUN_0040e36c(*cellTab, *(int *)(this + 0x2e8));	// Terrain::CellAt
	}
	else
	{
		Matrix34::FromQuaternion((Matrix34 *)(this + 0xd0), (Quaternion *)(this + 0x100));
	}

	{
		// Query terrain height at the current planar cell; correct Z so the
		// feet rest on the ground.
		struct { int x; uint xf; int z; } probe;
		probe.x = *(int *)(this + 0x100);
		probe.z = *(int *)(this + 0x108);
		Scalar  baseH = *(Scalar *)(*(int *)(this + 0x2ec /*cell*/) + 8) + (Scalar)*(int *)(this + 0x104);
		Scalar  height;
		FUN_0040e5f0(*(int *)(this + 0x2f0), (int)&probe, &height);	// Terrain::HeightAt
		if (height != _DAT_004ab9d0 /*-1*/)
		{
			height -= *(Scalar *)(*(int *)(this + 0x2ec) + 8);
			*(Scalar *)(this + 0x104) -= height;
			*(Scalar *)(*(int *)(this + 0x2e8) + 8) -= height;
			*(Scalar *)(*(int *)(this + 0x2e8) + 0xc) -= height;
		}
		(void)baseH;
	}

	ReconBeginPose((int)this);						// FUN_00421b6c

	// --- Telemetry filters: push the four cockpit angles + dt, then emit the
	//     per-axis angular rates (cockpit / reticle feed). -------------------
	FilteredScalar *f0 = (FilteredScalar *)(this + 0x7e0);	// this[0x1f8]
	FilteredScalar::Push(f0,                               *(Scalar *)(this + 0x1cc));	// headPitch [0x73]
	FilteredScalar::Push((FilteredScalar *)(this + 0x7ec),*(Scalar *)(this + 0x1c8));	// aim Y     [0x72]
	FilteredScalar::Push((FilteredScalar *)(this + 0x7f8),*(Scalar *)(this + 0x1c4));	// turretBase[0x71]
	FilteredScalar::Push((FilteredScalar *)(this + 0x804),*(Scalar *)(this + 0x1d4));	// aim/leg   [0x75]
	FilteredScalar::Push((FilteredScalar *)(this + 0x810), time_slice);					// dt        [0x76]

	Scalar sHead = FilteredScalar::Sum(f0);
	Scalar aTurr = FilteredScalar::Average((FilteredScalar *)(this + 0x7f8));
	Scalar aHead = FilteredScalar::Average(f0);
	Scalar aLeg  = FilteredScalar::Average((FilteredScalar *)(this + 0x804));
	Scalar aDt   = FilteredScalar::Average((FilteredScalar *)(this + 0x810));

	ReconQuatIdentity(&aimRate, &kIdentityQuat);		// was raw this+0x1dc (dead Simulate tail)

	if (_DAT_004ab9d4 /*0*/ < aDt)
	{
		// rate = (filtered_now - last_sample) / filtered_dt
		*(Scalar *)(this + 0x1e4) = (sHead - *(Scalar *)(this + 0x81c)) / aDt;	// [0x79]
		*(Scalar *)(this + 0x1e0) = (aHead - *(Scalar *)(this + 0x828)) / aDt;	// [0x78]
		*(Scalar *)(this + 0x1dc) = (aTurr - *(Scalar *)(this + 0x820)) / aDt;	// [0x77]
		*(Scalar *)(this + 0x1ec) = (aLeg  - *(Scalar *)(this + 0x824)) / aDt;	// [0x7b]
	}
	*(Scalar *)(this + 0x81c) = sHead;					// this[0x207]
	*(Scalar *)(this + 0x828) = aHead;					// this[0x20a]
	*(Scalar *)(this + 0x820) = aTurr;					// this[0x208]
	*(Scalar *)(this + 0x824) = aLeg;					// this[0x209]

	ReconEndPose((int)this);							// FUN_00421b2c
}


//###########################################################################
//###########################################################################
//					PerformAndWatch   (BRING-UP drivable locomotion)
//
//  NOT in the shipped binary at this vtable slot -- this is a reconstruction
//  override for Tier-2 bring-up.  The engine simulation director calls the
//  virtual Simulation::PerformAndWatch(till, stream) on every entity every
//  frame (the stasis / sub-system / activePerformance machinery lives INSIDE
//  the base Mover::PerformAndWatch, which we deliberately bypass here).  We
//  read the player drive input, integrate the engine-base localOrigin (the
//  REAL Mover position/orientation), and rebuild localToWorld -- the matrix
//  the BTL4VideoRenderer root renderable AND the chase camera are bound to, so
//  the body walks across the terrain and the camera tracks it by construction.
//
//  This intentionally does NOT run the (reconstructed, still-unsafe) Simulate /
//  subsystem chain; wiring real animation-driven gait + collision is a later
//  step.  Single local player assumption (bring-up): heading is a file static.
//###########################################################################
//###########################################################################

// Player drive input, owned by the launcher (btbuild/btl4main.cpp).
// throttle/turn = the virtual-control OUTPUTS integrated below from the raw key
// state (keyFwd/keyBack/keyLeft/keyRight) -- see the launcher header comment.
struct BTDriveInput { float throttle; float turn; int forced; int fire; int fireForced; float forcedThrottle;
                      int keyFwd; int keyBack; int keyLeft; int keyRight; int allStop; };
extern BTDriveInput gBTDrive;

// Locomotion tuning (bring-up; hand-picked, not yet derived from the .ani
// [RootTranslation] clip speed -- that animation-driven path comes with the
// real gait wiring).  Units are world units/sec and radians/sec.
static const Scalar kDriveMaxSpeed = 30.0f;		// full-throttle forward speed
static const Scalar kDriveTurnRate = 1.2f;		// full-deflection yaw rate
// (kWeaponRange removed: the damage gate now reads the AUTHENTIC per-weapon
//  targetWithinRange -- effectiveRange @0x328 = (1-damage) x the authored
//  WeaponRange: BLH lasers 500 / missiles 800 / PPCs 900 m.)

// Single local-player drive state (bring-up).
static Scalar gDriveHeading   = 0.0f;			// yaw about world up (Y)

// AUTHENTIC TARGETING (task #36): the reticle slew state, in reticle/dpl2d
// coordinates (centered origin, +y down, unit = half viewport height).  The
// pod slewed this with the stick free-aim channel; the dev box uses the MOUSE
// (absolute cursor -> client -> reticle) or the BT_AIM="x y" harness.  Read by
// the HUD Draw (crosshair position) and the pick-ray each sim frame.
float gBTAimX = 0.0f;
float gBTAimY = 0.0f;
// The HUD designator feed (mech4 -> btl4vid Draw): the locked target's
// HOTBOX point (its top-centre; the recovered Execute frames x+-4 around it,
// +1 above / -11.5 below) + the lock state (0 = none, 2 = locked).
int   gBTHudLockState    = 0;
float gBTHudLockWorld[3] = { 0, 0, 0 };
// Recovered-Execute instrument feeds (task #37): the compass heading, the
// torso-twist tape (deflection over the per-mech twist limit), and the
// weapon-group display mask (the Reticle element mask's low nibble).
float gBTHudHeading    = 0.0f;
float gBTHudTwist      = 0.0f;
float gBTHudTwistLimit = 0.0f;
int   gBTHudGroupMask  = 0xF;
int   gBTHudPrimary    = 1;		// PrimaryHudOn (element mask 0x20): full HUD vs simple X
// BT_GOTO beeline harness outputs (consumed by the mapper bridge, mechmppr.cpp)
int   gBTGotoActive   = 0;
float gBTGotoTurn     = 0.0f;
float gBTGotoThrottle = 1.0f;
static int    gDriveSeeded    = 0;
static Scalar gDriveLogAccum  = 0.0f;			// 1 Hz position log throttle
static Scalar gTickLogAccum   = 0.0f;			// 1 Hz subsystem-tick log throttle
static int    gTickFirstLogged = 0;				// one-shot first-frame dispatch report
static Scalar gTargetLogAccum = 0.0f;			// 1 Hz targeting log throttle

// Firing (bring-up): the weapon-effect renderables (real emitter beams / projectile
// tracers) are not built in the port yet, but the engine DOES render Explosion
// entities -- so a shot is shown as an explosion spawned at the target.  We resolve
// the "explode" effect resource once and rate-limit shots with a cooldown.
// Fire cadence: grounded on the recovered laser data -- DischargeTime=0.2s
// (beam-on) + a short recharge.  The EXACT recharge is not cleanly recoverable
// yet (RechargeRate feeds VoltageCurve/energyCoefficient, best-effort constants,
// and the voltage source isn't linked -> the port force-charges), so this is
// DischargeTime + ~0.1s, faster/snappier than the old 0.8s bring-up guess.
static const Scalar kFireCooldown  = 0.3f;		// damage-block cadence (bring-up stand-in)
static const Scalar kBeamOnTime    = 0.2f;		// laser beam-on = ERx laser DischargeTime
// DECODED per-laser cycle: DischargeTime(0.2) + RechargeRate(2.0, ER-medium) = 2.2s.
// (Recovered: RechargeRate is literally the recharge in seconds -- charge-curve
// constants C1=1.0/C2=1e-4, generator RatedVoltage=1e4, seek 0.8 -> curve arg 0.2,
// -ln(0.2)=ln5 cancels the exponential-charge-to-0.8 ln5 factor exactly.)
static const Scalar kPortRecharge  = 2.2f;		// one gun port's full fire+recharge cycle
static Scalar       gFireCooldown = 0.0f;		// counts down
static Scalar       gBeamCooldown = 0.0f;		// independent cooldown for the VISUAL beam
static const Scalar kMuzzleHeight  = 7.0f;		// gun height above the mech origin (torso)
static const Scalar kMuzzleForward = 3.0f;		// muzzle offset ahead of the mech centre
static int          gExplodeReady = 0;			// 0=untried 1=ok -1=failed
static ResourceDescription::ResourceID gExplodeRes = ResourceDescription::NullResourceID;
static int          gShotCount    = 0;

// --- CRASH/STAGGER STATE (task #15 knockdown; see AuthenticGroundAndCollide) ----
// The binary suppresses the mech's motion while it is CRASHED (spec: localVelocity
// = {0,0,-adv/dt} is ZEROED while crashed) so a knocked-down mech cannot creep back
// into the obstacle and re-trigger every frame.  We reproduce that: while the crash
// clip (legState 0x20) plays, hold the velocity at zero.
// The knockdown must also only fire on a FRESH impact -- the frame the mech first
// strikes something -- NOT every frame it grinds against an obstacle it is already
// pressed into.  Otherwise holding throttle into a wall/mech re-knocks-down every
// recovery (the stuttery leg flip-flop the user sees).  gWasBlocked tracks last
// frame's contact so a continuous press just BLOCKS (stand/walk-in-place) and only a
// separate-then-re-approach produces a new knockdown.
// Block hysteresis: a knockdown fires only when the mech has been OUT of contact
// for kBlockHysteresis seconds (a genuine fresh approach).  A 1-frame edge is too
// brittle -- a glancing / sliding contact alternates blocked/not-blocked (the frame
// rejection pushes the mech out each blocked frame), which would flicker the edge
// back to "fresh" and re-knockdown.  The window bridges those gaps so any sustained
// OR intermittent press just BLOCKS; only separating for real re-arms the knockdown.
static Scalar       gBlockCooldown  = 0.0f;		// seconds since the last blocking contact (counts down)
static const Scalar kBlockHysteresis = 0.4f;

// E8 weapon triggers (bring-up): pulsed per player frame; read by the weapon
// sims so CheckFireEdge sees rising edges.  Non-static (the weapons extern
// them).  WEAPON GROUPS (task #43): three KEYBOARD fire channels -- an interim
// pod-like split standing in for the authentic ConfigureMappables/ChooseButton
// mapper channels: 1/SPACE = lasers, 2 = PPCs, 3/CTRL = missiles.
int                 gBTWeaponTrigger  = 0;	// channel 1: lasers   (key 1 / SPACE)
int                 gBTPPCTrigger     = 0;	// channel 2: PPCs     (key 2)
int                 gBTMissileTrigger = 0;	// channel 3: missiles (key 3 / CTRL)
static int          gBTLaserKey       = 0;	// raw key states (set by the keyboard poll)
static int          gBTPPCKey         = 0;
static int          gBTMissileKey     = 0;
static int          gBTPinkyKey       = 0;	// key '4' = the pod's 4th fire button (Pinky 0x45)
int                 gBTModeCycle      = 0;	// 'M' edge: cycle the control mode (mapper consumes)
float               gBTTwistAxis      = 0.0f;	// Q/E torso-twist deflection (assisted-mode stick X)
int                 gBTTorsoRecenter  = 0;	// 'X' edge: pulse the authentic torso recenter (mapper consumes)
static int          gBTConfigKey      = 0;	// task #6: HOLD 'G' = the weapon-configure button
static int          gBTGenSelKey      = 0;	// task #12: F5..F8 = SelectGeneratorA..D, F9 = mode toggle
											//           (0 idle; else the message id 4..8)
static int          gBTValveKey       = 0;	// task #13: 'C' = MoveValve on the selected condenser

// Damage: each shot dispatches a REAL Entity::TakeDamageMessage to the target.  Now
// that the damage zones are constructed (mech.cpp Pass-3 zone build), the engine base
// Entity::TakeDamageMessageHandler routes damageZones[zone]->TakeDamage -> the
// reconstructed Mech__DamageZone::TakeDamage (the real armor/structure model).  We aim
// a VALID zone index (the Mech cylinder-lookup resolver for an unaimed/-1 hit is the
// STEP-6 reconstruction).  Death is driven by the REAL model (Mech::IsDisabled, the
// vital-zone/leg/torso destroyed query FUN_0049fb54), NOT a hit counter.
static const Scalar kShotDamage   = 12.0f;	// per-burst damage amount (units = armor points)
static int          gEnemyDestroyed = 0;

// BRING-UP: the spawned target/enemy mech (defined in btplayer.cpp).  The player
// mech locks onto it as its current target each frame (see targeting step below).
extern Entity *gEnemyMech;
// gauge scoring wave: producers (btplayer.cpp) -- feed the scoreboard from combat.
extern void BTPostDamageScore(Entity *victim, Scalar damage);	// per-hit SCORE (ScoreInflicted)
extern void BTPostKillScore(Entity *victim, Scalar damage);		// KILL (+ MP death)

// Mech target slots (verified vs the binary's weapon/fire path, part_013.c):
//   mech+0x37c  Point3D  current target world position  (range/aim source)
//   mech+0x388  Entity*  current target entity          (HasActiveTarget gate)
//   mech+0x38c  int      targeted subsystem index (-1 = whole mech / none)
// These are undocumented in the reconstructed mech.hpp member map; the fire path
// (Emitter/MechWeapon) reads exactly these, so we write them directly.
#define MECH_TARGET_POS(m)		(*(Point3D *)((char *)(m) + 0x37c))
#define MECH_TARGET_ENTITY(m)	(*(Entity **)((char *)(m) + 0x388))
#define MECH_TARGET_SUBIDX(m)	(*(int     *)((char *)(m) + 0x38c))

// task #8 bridge: the weapon-side damage submission (mechweap.cpp) reads the
// owner's designated-zone slot; the raw target-slot block lives in this TU.
int BTMechTargetZone(void *mech)
{
	return MECH_TARGET_SUBIDX(mech);
}

// Bring-up body-animation player (file scope so OnBodyAnimFinished can re-arm it).
// The engine AnimationInstance walks the mech's joint subsystem from a baked .ani
// clip in btl4.res; we advance it each moving frame so the legs CYCLE.
static AnimationInstance *gBodyAnim     = 0;
static int               gBodyAnimReady = 0;	// 0=untried 1=ok -1=failed
static Scalar            gBodyAnimLog   = 0.0f;
static int               gBodyAnimLoops = 0;	// count completed cycles (sanity)
static int               gCurrentGait   = -1;	// P3 STEP 2: -1=none 0=walk 1=run (bound clip)
static const Scalar      kRunThrottle   = 0.5f;	// |throttle| >= this -> run clip, else walk clip

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Mech::OnBodyAnimFinished -- AnimationInstance finished-callback.
//   AnimationInstance::Animate invokes (moverToAnimate->*finishedCallback) when a
//   clip runs off its last keyframe (JMOVER.cpp ~1592).  The real game uses this to
//   chain/loop gaits; for bring-up we LOOP the same clip by re-arming it at frame 0.
//   Dropping the sub-frame carryover is imperceptible.  Returns extra root movement
//   contributed this call (0 -- the next frame's Animate carries the gait forward).
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Scalar
	Mech::OnBodyAnimFinished(
		ResourceDescription::ResourceID animation_number,
		Scalar /*carryover*/, Logical /*animate_joints*/)
{
	++gBodyAnimLoops;
	if (gBodyAnim)
	{
		gBodyAnim->SetAnimation(
			animation_number,
			reinterpret_cast<JointedMover::AnimationCallback>(&Mech::OnBodyAnimFinished));
	}
	return 0.0f;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// BTResolveWeaponMuzzle -- the faithful FUN_004b9948 (MechWeapon::GetMuzzlePoint)
// muzzle resolve: look up the weapon's mount segment (index, from the subsystem's
// inherited this+0xdc slot) in the owner Mech's segment table and transform it to
// world.  Lives here (a complete-Mech TU with the segment API); mechweap.cpp treats
// `owner` as a raw pointer so it calls this via a void* bridge instead of the Mech API.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	BTResolveWeaponMuzzle(void *ownerMech, int segIndex, Point3D &out)
{
	Mech *m = (Mech *)ownerMech;
	if (m == 0) { out = Point3D(0.0f, 0.0f, 0.0f); return; }
	EntitySegment *seg = m->GetSegment(segIndex);		// owner+0x300 table, GetNth(index)
	if (seg != 0)
	{
		AffineMatrix mw;
		mw.Multiply(seg->GetSegmentToEntity(), m->localToWorld);	// segment -> world (== mech4 gun-port path)
		out = mw;										// Point3D = matrix W_Axis translation
	}
	else
		out = m->localOrigin.linearPosition;			// safe non-garbage fallback (owner origin)
}

// First vital damage-zone index (Mech__DamageZone::vitalDamageZone is protected; Mech has access).
int
	Mech::FirstVitalZone() const
{
	for (int k = 0; k < damageZoneCount; ++k)
		if (Zone(k)->vitalDamageZone)
			return k;
	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Port-side tracked-projectile service (WAVE 7 Phase B -- flying missiles/rounds).
//   The 1995 Projectile/Missile WORLD-ENTITY classes cannot be revived byte-exact:
//   the 2007 engine Entity base is 0x1BC bytes vs the 1995 binary's 0x300, so the
//   reconstruction's raw base-offset reads (velocity@0x1dc, roster@0x124, motion@
//   0x250) read garbage on the engine (the same 0x638-vs-0x854 gap the Mech has,
//   but the entity integrator depends on those offsets).  So a fired projectile is
//   a PORT reconstruction (like the beam renderer): seeded from the launcher's fire
//   with the decomp's real muzzle / launch velocity / per-shot damage, it flies
//   toward the target (tracer via BTPushBeam) and delivers the weapon's damage on
//   impact through the SAME Entity::TakeDamage path as the beam.  The byte-exact
//   world-entity Missile (Projectile : Mover, MP-replicable) is the deeper follow-up.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct BTProjectile {
	Point3D		pos;
	Vector3D	vel;		// world velocity (authored MuzzleVelocity, steered per frame)
	Scalar		speed;		// |vel| held constant through the steer
	Scalar		traveled;
	Scalar		range;
	Entity	   *target;
	Point3D		targetPos;
	Scalar		aimOffsetY;	// vertical aim offset vs the target's origin (live re-lead)
	Scalar		damage;		// <= 0 -> VISUAL-ONLY round (replicant-side salvo mirror)
	int			guided;		// 1 = missile (seeker loft + steering); 0 = ballistic (straight)
	Entity	   *shooter;	// the firing mech (messmgr explosion bundling at impact)
	int			weaponSubsys;	// firing weapon's roster index (-1 = unthreaded) --
								// resolves the weapon's ExplosionModelFile (mslhit/
								// acanhit) in the messmgr, task #7 bundling
	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
								// (matches the ONE arcade cluster missile).  0 on every
								// other round (a straight tracer / non-lead volley round)
								// so splash is NOT re-applied + floored-at-1 per round.
	int			active;
};
static BTProjectile gProjectiles[64];

extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float);

//###########################################################################
// Zone-effect bridge (mechdmg has no `application` access): route the damage-
// band effect through the AUTHENTIC RendererManager::StartEntityEffect chain
// (the @004d097c dispatcher; the AudioRenderer hears the same broadcast).
// Returns 0 when the manager isn't up so the caller can fall back.
//###########################################################################
int
	BTStartZoneEffect(Mech *mech, void *zone, int resource)
{
	if (mech == 0 || zone == 0 || resource <= 0
		|| application == 0 || application->GetRendererManager() == 0)
		return 0;
	application->GetRendererManager()->StartEntityEffect(
		(Entity *)mech, (DamageZone *)zone,
		(ResourceDescription::ResourceID)resource);
	return 1;
}

//###########################################################################
// Segment world-transform bridge (the @004d097c per-zone effect dispatcher +
// the attached-emitter follow in L4VIDEO's PFX layer).  Resolves the entity's
// segment (by index) to its world position + 3x3 basis rows; falls back to
// the mech origin at torso height when the segment doesn't resolve.  Returns
// 0 for a non-mech / unregistered entity (the follow then keeps its last
// frame -- and the emitter dies with its authored window anyway).
//###########################################################################
int
	BTResolveSegmentWorld(void *entity, int seg_index, float *pos3, float *rows9)
{
	extern int BTIsRegisteredMech(Entity *e);
	if (entity == 0 || !BTIsRegisteredMech((Entity *)entity))
		return 0;
	Mech *m = (Mech *)entity;

	Point3D p = m->localOrigin.linearPosition;
	p.y += kMuzzleHeight;								// fallback: torso height
	if (seg_index >= 0)
	{
		EntitySegment::SegmentTableIterator it(m->segmentTable);
		EntitySegment *seg;
		while ((seg = it.ReadAndNext()) != NULL)
		{
			if (seg->GetIndex() == seg_index)
			{
				AffineMatrix mw;
				mw.Multiply(seg->GetSegmentToEntity(), m->localToWorld);
				p = mw;									// Point3D = matrix translation
				break;
			}
		}
	}
	pos3[0] = (float)p.x; pos3[1] = (float)p.y; pos3[2] = (float)p.z;

	UnitVector ax, ay, az;
	m->localToWorld.GetFromAxis(X_Axis, &ax);
	m->localToWorld.GetFromAxis(Y_Axis, &ay);
	m->localToWorld.GetFromAxis(Z_Axis, &az);
	rows9[0] = (float)ax.x; rows9[1] = (float)ax.y; rows9[2] = (float)ax.z;
	rows9[3] = (float)ay.x; rows9[4] = (float)ay.y; rows9[5] = (float)ay.z;
	rows9[6] = (float)az.x; rows9[7] = (float)az.y; rows9[8] = (float)az.z;
	return 1;
}

//###########################################################################
// MUZZLE FLASH (task #61) -- the GENUINE shipped projectile-gun muzzle effect.
//
// BTDPL.INI documents psfx 6 (external) / 14 (internal) = DAFC.PFX as "the
// effect used on all projectile guns": an orange fire-smoke blast
// (btfx:firesmoke1, maxIssue 25 / releasePeriod ~0.2s -> the emitter auto-
// expires after one ~0.2s burst = a per-shot muzzle flash).  The AC is the
// only weapon that gets it -- lasers show their beam, missiles their launch.
// NOT the cut MUZFLASH.BGF card (that asset is orphaned; the shipped flash is
// this particle).  Disasm of ProjectileWeapon::FireWeapon (@0x4bc104) shows no
// direct spawn there, so the fire edge is the faithful hook; the effect,
// asset, and gun-port attach point are all confirmed shipped [T1 asset/INI].
//
// Attached to the gun-port SEGMENT (BTResolveSegmentWorld re-resolves the
// muzzle position + mech body frame each frame): DAFC sprays -Z, and the mech
// faces -Z, so the fire-smoke blows forward out the barrel.
//###########################################################################
void
	BTFlashMuzzle(void *ownerMech, int seg_index, float mx, float my, float mz)
{
	if (ownerMech == 0)
		return;
	float pos[3], rows[9];
	if (!BTResolveSegmentWorld(ownerMech, seg_index, pos, rows))
		return;
	extern void BTStartPfxAttached(int, void *, int, float, float, float, const float *);
	// psfx 6 = DAFC (external); the day table maps both 6 and 14 to DAFC.PFX,
	// so the external slot serves every viewer.
	BTStartPfxAttached(6, ownerMech, seg_index, mx, my, mz, rows);
	if (getenv("BT_MUZZLE_LOG"))
		DEBUG_STREAM << "[muzzle] DAFC flash seg=" << seg_index
			<< " at(" << mx << "," << my << "," << mz << ")\n" << std::flush;
}

//###########################################################################
// PER-ROUND DETONATION (the binary's Missile::MoveAndCollide @004bef78: every
// round spawns ITS OWN ExplosionModelFile at its impact point, resource
// missile+0x33c -- a volley RIPPLES fireballs across its arrival frames, the
// demo look).  Resolved from the firing weapon's roster entry (weapon+0x3E4);
// runs on BOTH nodes (the replicant visual salvo passes its launcher's index
// too).  The messmgr's one bundled explosion remains (direct-fire semantics +
// the cross-pod damage stream); among a rippled volley it is invisible.
//###########################################################################
static void
	BTSpawnRoundDetonation(Entity *shooter, int weapon_subsys, const Point3D &at)
{
	extern int BTIsRegisteredMech(Entity *e);
	if (shooter == 0 || weapon_subsys < 0 || !BTIsRegisteredMech(shooter))
		return;
	Mech *sm = (Mech *)shooter;
	if (weapon_subsys >= sm->GetSubsystemCount())
		return;
	Subsystem *w = sm->GetSubsystem(weapon_subsys);
	if (w == 0 || !w->IsDerivedFrom(*MechWeapon::GetClassDerivations()))
		return;
	ResourceDescription::ResourceID res = ((MechWeapon *)w)->GetExplosionResourceID();
	if (res <= 0)
		return;
	Origin o;
	o.linearPosition  = at;
	o.angularPosition = EulerAngles(Radian(0.0f), Radian(0.0f), Radian(0.0f));
	Explosion::MakeMessage m(
		Explosion::MakeMessageID, sizeof(Explosion::MakeMessage),
		(Entity::ClassID)RegisteredClass::ExplosionClassID, EntityID::Null,
		res, Explosion::DefaultFlags, o,
		sm->GetEntityID(), sm->GetEntityID());
	Explosion *e = Explosion::Make(&m);
	if (e)
		Register_Object(e);
}

//###########################################################################
// SPLASH DAMAGE (task #62) -- Explosion::SplashDamage @0042fad0 / engine
// EXPLODE.cpp:50-254 [T0].  A detonating projectile's blast damages every mech
// within its authored SplashRadius.  Damage MODEL (T0 EXPLODE.cpp:209-246,
// decomp part_004.c:856-911): the per-burst AMOUNT + damage TYPE pass through
// UNCHANGED; the distance falloff is applied to the BURST COUNT --
//     bursts = round( baseBurst / dist^1.25 ),  floored at 1
// (arcade exponent 1.25 -- part_004.c:865 = 0x3ff40000; the WinTesla source
// EXPLODE.cpp:209 drifted to 1.2f -- we use the arcade value).  So a mech
// inside the radius takes the round's damage; a point-blank one takes extra
// bursts.  Base burst = the incoming Damage's burstCount (Damage+0x2c).
//
// The SplashRadius is the ROUND's GameModel resource +0x50.  ONLY missiles
// splash: in the arcade a MissileLauncher spawns a Missile ENTITY whose Perform
// (part_013.c:9887) is the sole SplashDamage caller (@10097); the AC's tracer is
// not a Missile and never splashes.  The Missile's model id comes from the
// launcher's linked AmmoBin (ammoModelFile @0x1e8, part_013.c:8778), NOT the
// launcher's ExplosionModelFile; the Missile ctor reads SplashRadius from that
// model's type-0xf record +0x50 (@10184).  So the port gates on MissileLauncher
// and resolves radius via launcher -> AmmoBin -> round model -> +0x50.
//
// PORT: the binary enumerates interest-zone movers; reconstructed mechs aren't
// reliably registered there, so we walk the live-mech registry
// (BTGetTargetCandidates, which already excludes the shooter -- no self-splash)
// -- guaranteed populated + cheaper.  The DIRECT victim is excluded (it already
// took the direct hit; matches EXPLODE.cpp:179 excluding entityHit) so it is
// not double-damaged.  Delivery reuses the direct-hit path (messmgr bundle /
// Dispatch), so replicant victims reroute cross-pod.
//###########################################################################
static Scalar
	BTResolveSplashRadius(Entity *shooter, int weapon_subsys)
{
	extern int BTIsRegisteredMech(Entity *e);
	if (shooter == 0 || weapon_subsys < 0 || application == 0
		|| application->GetResourceFile() == 0 || !BTIsRegisteredMech(shooter))
		return 0.0f;
	Mech *sm = (Mech *)shooter;
	if (weapon_subsys >= sm->GetSubsystemCount())
		return 0.0f;
	Subsystem *w = sm->GetSubsystem(weapon_subsys);
	// ONLY missile launchers spawn Missile entities that carry splash (see banner).
	if (w == 0 || !w->IsDerivedFrom(MissileLauncher::ClassDerivations))
		return 0.0f;
	// The round's model id lives on the launcher's linked AmmoBin (+0x1e8),
	// resolved through the same ammoBinLink the fire path uses.
	extern void *BTWeaponAmmoBin(void *weapon);
	extern int   BTAmmoRoundModelResource(void *bin);
	void *bin = BTWeaponAmmoBin(w);
	if (bin == 0)
		return 0.0f;
	ResourceDescription::ResourceID res =
		(ResourceDescription::ResourceID)BTAmmoRoundModelResource(bin);
	if (res <= 0)
		return 0.0f;
	// The round's GameModel record (type 0xf): SplashRadius @ +0x50 (binary
	// part_013.c:10184 reads resource_data+0x50; the MissileThruster parser
	// FUN_004bf8ec writes "SplashRadius" to +0x50 of the type-0xf/size-0x54 record).
	ResourceDescription *rd = application->GetResourceFile()->SearchList(
		res, ResourceDescription::GameModelResourceType);
	if (rd == 0)
		return 0.0f;
	rd->Lock();
	Scalar radius = *(const Scalar *)((const unsigned char *)rd->resourceAddress + 0x50);
	rd->Unlock();
	if (!(radius > 0.0f) || radius > 1.0e4f)		// NaN / garbage / no-splash guard
		radius = 0.0f;
	return radius;
}

void
	BTApplySplashDamage(Entity *shooter, int weapon_subsys, const Point3D &center,
						Entity *directVictim, const Damage &dmgTemplate)
{
	Scalar radius = BTResolveSplashRadius(shooter, weapon_subsys);
	static const int s_log = getenv("BT_SPLASH_LOG") ? 1 : 0;
	if (s_log)
		DEBUG_STREAM << "[splash] HOOK weapon=" << weapon_subsys
			<< " radius=" << radius << "\n" << std::flush;
	if (radius <= 0.0f)
		return;										// this weapon authors no splash

	extern int BTIsRegisteredMech(Entity *e);
	extern int BTGetTargetCandidates(Entity *shooter, Entity **out, int maxOut);
	Entity *cand[32];
	int nc = BTGetTargetCandidates(shooter, cand, 32);	// excludes the shooter
	const Scalar baseBurst = (Scalar)dmgTemplate.burstCount;
	if (s_log)
		DEBUG_STREAM << "[splash] detonation radius=" << radius << " candidates="
			<< nc << " directVictim=" << (void *)directVictim << "\n" << std::flush;

	for (int ci = 0; ci < nc; ++ci)
	{
		Entity *e = cand[ci];
		if (e == 0 || e == directVictim || !BTIsRegisteredMech(e))
			continue;
		Mech *m = (Mech *)e;
		if (m->IsMechDestroyed() || m->damageZoneCount <= 0)
			continue;
		Point3D p = m->localOrigin.linearPosition;
		Scalar dx = p.x - center.x, dy = p.y - center.y, dz = p.z - center.z;
		Scalar dist = (Scalar)sqrtf((float)(dx*dx + dy*dy + dz*dz));
		if (dist > radius)							// RADIUS GATE -- required: the
			continue;								// falloff floors at 1, so without
												// this every mech would take a burst
		// EXPLODE.cpp:209 -- burstCount = baseBurst / dist^exp, floored at 1.
		// Exponent is the ARCADE 1.25 (decomp 0x3ff40000); the WinTesla source
		// drifted to 1.2f.  Point-blank AMPLIFIES (dist<1 -> >baseBurst bursts) --
		// authentic; a real splash victim is never at dist~0 (the mech AT the
		// blast is the excluded direct victim), so the div-guard epsilon (mechs
		// have body size) only ever fires in the synthetic BT_SPLASH_TEST.
		Scalar d = (dist > 0.01f) ? dist : 0.01f;	// div-guard (no arcade floor)
		int bursts = (int)(0.5f + baseBurst / powf((float)d, 1.25f));	// arcade ROUND
		if (bursts < 1) bursts = 1;					// EXPLODE.cpp:210 Min_Clamp

		Damage dmg      = dmgTemplate;				// amount + type pass through
		dmg.burstCount  = bursts;
		dmg.impactPoint = center;
		dmg.damageForce = Vector3D(dx, dy, dz);		// EXPLODE.cpp:216 radial knockback

		// DIRECT dispatch to EACH victim (T0 EXPLODE.cpp:246 -- target->Dispatch).
		// NOT the shooter's SubsystemMessageManager: AddDamageMessage CONSOLIDATES
		// every damage message of the frame onto ONE common entity (the first hit,
		// messmgr.cpp:279) -- so routing splash through it delivered the bystander's
		// blast onto the DIRECT victim instead (double-hitting the excluded mech).
		// Dispatch reroutes cross-pod for a replicant victim on its own.
		Entity::TakeDamageMessage td(
			Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
			shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/, dmg);
		e->Dispatch(&td);
		if (s_log)
			DEBUG_STREAM << "[splash] victim=" << e->GetEntityID() << " dist=" << dist
				<< " radius=" << radius << " bursts=" << bursts
				<< " amount=" << dmg.damageAmount << " baseBurst=" << (int)baseBurst
				<< "\n" << std::flush;
	}
}

// Called from ProjectileWeapon / MissileLauncher::FireWeapon (via the extern below)
// with the live muzzle, the SHOOTER mech (to resolve the real launch port), the owner's
// locked target entity + point, the launch speed (|muzzleVelocity|), and per-shot damage.
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)
{
	// MUZZLE (muzzle wave, 2026-07-12): the passed muzzle is now the weapon's
	// AUTHENTIC mount segment (GetMuzzlePoint reads the real segmentIndex --
	// the old raw-offset garbage that made it resolve feet/heads is fixed), so
	// the port-name rotation hack that compensated for it is RETIRED.  Keep
	// one safety: a degenerate resolve at/below origin height (an unposed
	// segment) launches from torso height instead of underground.
	Point3D mz = muzzle;
	if (shooter != 0)
	{
		Mech *sm = (Mech *)shooter;
		if (mz.y - sm->localOrigin.linearPosition.y < 1.0f)
		{
			mz = sm->localOrigin.linearPosition;
			mz.y += 12.0f;
		}
	}

	// AUTHENTIC (task #36): NO fallback target.  The launcher passes the mech's
	// own designated-target slots (owner+0x388 via GetTargetPosition); with no
	// designation the missile does not launch -- the acquisition (crosshair on
	// the enemy -> pick -> designate) is the ONLY route to a target, exactly
	// like the energy weapons.  (The old gEnemyMech fallback pre-dated the
	// acquisition and let missiles bypass it.)
	Point3D tpos = targetPos;
	// A VISUAL round (damage <= 0, the replicant-side salvo mirror) flies on the
	// replicated aim POINT alone; a live round still requires the designation.
	if (target == 0 && damage > 0.0f)
		return;
	Vector3D d;
	d.x = tpos.x - mz.x; d.y = tpos.y - mz.y; d.z = tpos.z - mz.z;
	Scalar len = (Scalar)sqrtf(d.x*d.x + d.y*d.y + d.z*d.z);
	if (getenv("BT_PROJ_LOG"))
	{
		Scalar relY = (shooter != 0)
			? (mz.y - ((Mech *)shooter)->localOrigin.linearPosition.y) : mz.y;
		DEBUG_STREAM << "[projectile] PUSH target=" << (void*)target
			<< " len=" << len << " speed=" << speed << " dmg=" << damage
			<< " mz=(" << mz.x << "," << mz.y << "," << mz.z << ") relY=" << relY
			<< " lv=" << (launch_velocity
				? "(auth)" : "(fallback)") << "\n" << std::flush;
	}
	if (len < 0.001f) return;
	for (int i = 0; i < 64; ++i)
	{
		if (gProjectiles[i].active) continue;
		BTProjectile &p = gProjectiles[i];
		p.pos = mz;										// resolved launch port
		p.speed = (speed > 1.0f) ? speed : 120.0f;		// |launchVelocity|; sane fallback

		// AUTHENTIC LAUNCH (missile-arc wave): the launcher's authored
		// MuzzleVelocity is a VECTOR in the shooter's frame (typically
		// up-tilted); rotate it into world by the shooter's pose so the round
		// leaves the rack climbing -- the front half of the pod's missile arc.
		// (The old code collapsed it to |v| along the straight line to the
		// target: dead-flat flight.)  Fall back to the straight line when no
		// authored vector reaches us (non-missile callers).
		int haveLaunch = 0;
		if (launch_velocity != 0 && shooter != 0)
		{
			// FRAME CONVENTION (telemetry-verified 2026-07-12): the authored
			// MuzzleVelocity is (0,0,+100) -- the launcher frame's +Z is
			// FORWARD, but the mech's body frame faces -Z.  Transforming +Z
			// through the body basis launched every round BACKWARD (then the
			// seeker looped it through the ground onto the target --
			// user-reported).  Map launcher-forward (+Z) onto body-forward
			// (-Z basis): negate the z term.
			Mech *sm = (Mech *)shooter;
			UnitVector ax, ay, az;
			sm->localToWorld.GetFromAxis(X_Axis, &ax);
			sm->localToWorld.GetFromAxis(Y_Axis, &ay);
			sm->localToWorld.GetFromAxis(Z_Axis, &az);
			p.vel.x = ax.x*launch_velocity->x + ay.x*launch_velocity->y - az.x*launch_velocity->z;
			p.vel.y = ax.y*launch_velocity->x + ay.y*launch_velocity->y - az.y*launch_velocity->z;
			p.vel.z = ax.z*launch_velocity->x + ay.z*launch_velocity->y - az.z*launch_velocity->z;
			Scalar lv = (Scalar)sqrtf(p.vel.x*p.vel.x + p.vel.y*p.vel.y + p.vel.z*p.vel.z);
			if (lv > 1.0f) { p.speed = lv; haveLaunch = 1; }
		}
		if (!haveLaunch)
		{
			p.vel.x = d.x/len*p.speed; p.vel.y = d.y/len*p.speed; p.vel.z = d.z/len*p.speed;
		}
		p.traveled = 0.0f;
		p.range = len * 1.3f + 60.0f;					// arc margin; expire past the target
		p.target = (Entity *)target;
		p.targetPos = tpos;								// resolved target position (fallback-aware)
		// live re-lead (authentic: the Seeker re-leads the MOVING target every
		// slice): remember the aim's height above the target's origin so the
		// per-frame refresh keeps striking the same body height.
		extern int BTIsRegisteredMech(Entity *e);
		p.aimOffsetY = (target != 0 && BTIsRegisteredMech((Entity *)target))
			? (tpos.y - ((Mech *)target)->localOrigin.linearPosition.y) : 0.0f;
		p.damage = damage;
		p.guided = guided;	// autocannon shells fly straight (no seeker in the binary's plain Projectile)
		p.shooter = (Entity *)shooter;
		p.weaponSubsys = weapon_subsys;
		p.splashBurst = splash_burst;					// >0 only on a salvo-lead round

		// 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
		// 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.
		// GUIDED ROUNDS ONLY: a ballistic shell has no seeker to re-converge
		// it -- the slot-0 pattern deflected every AFC100 shell a fixed
		// 3.4deg left / 2.5deg down (the phantom "4th gold beam" burying its
		// tracer beside the target, user-reported).  The binary's plain
		// Projectile flies straight at the pick.
		if (guided)
		{
			float ja = ((i % 5) - 2) * 0.030f;			// yaw   +-0.06 rad
			float jb = (((i / 5) % 5) - 2) * 0.022f;	// pitch +-0.044 rad
			float vx = p.vel.x, vy = p.vel.y, vz = p.vel.z;
			float ca = cosf(ja), sa = sinf(ja);
			p.vel.x = vx * ca - vz * sa;
			p.vel.z = vx * sa + vz * ca;
			p.vel.y = vy + jb * p.speed;
			p.pos.x += ((i % 3) - 1) * 0.6f;
			p.pos.z += (((i / 3) % 3) - 1) * 0.6f;
		}
		p.active = 1;
		if (getenv("BT_PROJ_LOG"))
			DEBUG_STREAM << "[projectile]   vel=(" << p.vel.x << "," << p.vel.y
				<< "," << p.vel.z << ")"
				<< (launch_velocity
					? " authored=(" : " none=(")
				<< (launch_velocity ? launch_velocity->x : 0.0f) << ","
				<< (launch_velocity ? launch_velocity->y : 0.0f) << ","
				<< (launch_velocity ? launch_velocity->z : 0.0f) << ")" << std::endl;
		return;
	}
}

// Per-frame (viewpoint mech): advance each projectile, draw its tracer, deliver damage on impact.
static void
	BTUpdateProjectiles(Scalar dt)
{
	for (int i = 0; i < 64; ++i)
	{
		BTProjectile &p = gProjectiles[i];
		if (!p.active) continue;
		Point3D prev = p.pos;

		// AUTHENTIC GUIDANCE (missile-arc wave; Seeker::LeadTarget @004beae4 +
		// the @004bef78 steering [T1]).  The seeker LOFTS the aim point while
		// the round is far out -- beyond 200 m the aim rises by
		// 0.1 x clamp(range-200, 0, 300) (up to +30 m above the target) -- so
		// the missile climbs at range and dives as it closes: the pod's
		// "gravity arc".  Steering is the port shape of the binary's
		// squared-error guidance: rotate the velocity toward the lofted aim at
		// the decomp turn gain (MissileTurnGain = 4.0, _DAT_004bf5a4), speed
		// held at the authored launch speed.
		if (p.guided)
		{
			// LIVE RE-LEAD (authentic: Seeker::LeadTarget runs every slice on
			// the MOVING target): refresh the aim from the target's current
			// position, preserving the launch aim's body height.
			extern int BTIsRegisteredMech(Entity *e);
			if (p.target != 0 && BTIsRegisteredMech(p.target)
				&& !((Mech *)p.target)->IsMechDestroyed())
			{
				p.targetPos   = ((Mech *)p.target)->localOrigin.linearPosition;
				p.targetPos.y += p.aimOffsetY;
			}

			Point3D aim = p.targetPos;
			Scalar rx = aim.x - p.pos.x, ry = aim.y - p.pos.y, rz = aim.z - p.pos.z;
			Scalar range = (Scalar)sqrtf(rx*rx + ry*ry + rz*rz);
			if (range > 200.0f)							// SeekerLeadMinRange @004bec18
			{
				Scalar lead = range - 200.0f;
				if (lead > 300.0f) lead = 300.0f;		// SeekerLeadMaxClamp @004bec24
				aim.y += 0.1f * lead;					// SeekerLeadCoef @004bec28
				ry = aim.y - p.pos.y;
			}
			if (range > 0.001f)
			{
				Scalar inv = 1.0f / range;
				Scalar cx = p.vel.x / p.speed, cy = p.vel.y / p.speed, cz = p.vel.z / p.speed;
				// MissileTurnGain (4.0, _DAT_004bf5a4) far out; DOUBLED inside
				// the no-loft radius so the dive converges onto the contact
				// sphere (the port shape of the binary's squared-error terminal
				// aggressiveness -- error^2 steering climbs steeply near boresight).
				Scalar k  = ((range < 200.0f) ? 8.0f : 4.0f) * dt;
				cx += (rx*inv - cx) * k; cy += (ry*inv - cy) * k; cz += (rz*inv - cz) * k;
				Scalar cl = (Scalar)sqrtf(cx*cx + cy*cy + cz*cz);
				if (cl > 0.001f)
				{
					p.vel.x = cx/cl*p.speed; p.vel.y = cy/cl*p.speed; p.vel.z = cz/cl*p.speed;
				}
			}
		}

		p.pos.x += p.vel.x*dt; p.pos.y += p.vel.y*dt; p.pos.z += p.vel.z*dt;
		p.traveled += p.speed * dt;

		// WORLD IMPACT (authentic: the binary missile runs a world collision
		// query every frame, FUN_0042291c, and DETONATES on geometry).  Ray the
		// flight step against the terrain/cave solids -- a lofted round in a
		// low cavern bursts on the CEILING instead of punching through it.
		{
			extern bool BTGroundRayHit(float,float,float, float,float,float,
				float, float*,float*,float*);
			extern Entity *gBTTerrainEntity;		// captured by MakeEntityRenderables
			Scalar step = p.speed * dt;
			if (gBTTerrainEntity != 0 && step > 0.0001f)
			{
				Vector3D rd;
				rd.x = p.vel.x/p.speed; rd.y = p.vel.y/p.speed; rd.z = p.vel.z/p.speed;
				float hx, hy, hz;
				if (BTGroundRayHit(prev.x, prev.y, prev.z, rd.x, rd.y, rd.z,
						step + 1.0f, &hx, &hy, &hz))
				{
					// burst on the rock: the round's own DETONATION (the binary
					// missile detonates on ANY geometry, @004bef78) + a tight
					// puff cluster, no damage
					{
						Point3D hp; hp.x = hx; hp.y = hy; hp.z = hz;
						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
						// for a world hit as well as a mover hit).  A missile that
						// bursts on terrain near a mech still catches it in the blast.
						// ONCE per salvo (splashBurst>0), baseBurst = the cluster count;
						// no direct victim here.  No-op for non-missile rounds.
						if (p.damage > 0.0f && p.splashBurst > 0)
						{
							Damage sdmg;
							sdmg.damageType   = Damage::ExplosiveDamageType;
							sdmg.damageAmount = p.damage;
							sdmg.burstCount   = p.splashBurst;	// cluster count as baseBurst
							sdmg.impactPoint  = hp;
							extern void BTApplySplashDamage(Entity *, int, const Point3D &,
															Entity *, const Damage &);
							BTApplySplashDamage(p.shooter, p.weaponSubsys, hp, 0, sdmg);
						}
					}
					extern void BTPfxTrailPuff(int, float, float, float, float, float, float, int);
					for (int pf = 0; pf < 4; ++pf)
						BTPfxTrailPuff(0, hx, hy, hz, -rd.x, -rd.y, -rd.z, 2);
					if (getenv("BT_PROJ_LOG"))
						DEBUG_STREAM << "[projectile] WORLD burst at(" << hx << ","
							<< hy << "," << hz << ")" << std::endl;
					p.active = 0;
					continue;
				}
			}
		}
		// AUTHENTIC missile look: the round is a short hot streak, and the trail
		// is the real dsrm smoke-trail effect (psfx 0, "the lrm smoke trail")
		// puffed along the flight path each frame with local +Z = backward (the
		// .PFX's velocities stream the smoke behind the round).  This replaces
		// the old 3-segment white tracer lines (a bring-up placeholder).
		Vector3D bd;
		bd.x = p.vel.x/p.speed; bd.y = p.vel.y/p.speed; bd.z = p.vel.z/p.speed;
		BTPushBeam(prev.x, prev.y, prev.z, p.pos.x, p.pos.y, p.pos.z, 0x00FFB040u, 0.10f, 0.9f);	// the round (hot streak)
		{
			extern void BTPfxTrailPuff(int, float, float, float, float, float, float, int);
			BTPfxTrailPuff(0, prev.x, prev.y, prev.z, -bd.x, -bd.y, -bd.z, 2);
		}

		// CONTACT-ONLY damage (user-verified bug: an arcing round that ran out
		// its flight cap while still descending "applied damage without making
		// contact").  Proximity = the hit; the flight-cap expiry is a FIZZLE --
		// no damage, matching the binary (a missile that dies mid-air detonates
		// nothing; only the world-collision hit spawns the Damage entity).
		Scalar dx = p.targetPos.x - p.pos.x, dy = p.targetPos.y - p.pos.y, dz = p.targetPos.z - p.pos.z;
		const int contact = (dx*dx + dy*dy + dz*dz < (10.0f*10.0f));
		if (!contact && p.traveled >= p.range)
		{
			if (getenv("BT_PROJ_LOG"))
				DEBUG_STREAM << "[projectile] FIZZLE (flight cap, no contact)\n" << std::flush;
			p.active = 0;
			continue;
		}
		if (contact)
		{
			BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, p.pos);
			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
			// MP, task #46).  A replicant target reroutes cross-pod via Dispatch.
			extern int BTIsRegisteredMech(Entity *e);
			if (tgt != 0 && BTIsRegisteredMech(tgt) && p.damage > 0.0f)
			{
				Mech *m = (Mech *)tgt;
				if (m->damageZoneCount > 0)
				{
					// UNAIMED (STEP 6): the projectile's world impact position IS the
					// hit point; Mech::TakeDamageMessageHandler resolves the struck
					// zone from the cylinder hit-location table.  (Previously this
					// aimed the internal vital zone directly -> invisible insta-kill.)
					Damage dmg;
					dmg.damageType   = Damage::ExplosiveDamageType;
					dmg.damageAmount = p.damage;
					dmg.burstCount   = 1;
					dmg.impactPoint  = p.pos;
					// Route through the SHOOTER's SubsystemMessageManager with the
					// firing launcher's roster index (task #7 bundling): the
					// consolidation resolves roster[id]+0x3E4 = the weapon's
					// ExplosionModelFile (mslhit/acanhit) and fires it AT the
					// impact point -- the missing missile-hit explosion (the
					// laser path always did this via SendDamageMessage; the
					// projectile path bypassed the manager entirely).
					SubsystemMessageManager *mgr = 0;
					if (p.shooter != 0 && p.weaponSubsys >= 0 && BTIsRegisteredMech(p.shooter))
						mgr = (SubsystemMessageManager *)((Mech *)p.shooter)->GetMessageManager();
					if (mgr != 0)
					{
						Entity::TakeDamageMessage take_damage(
							Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
							p.shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/,
							dmg, p.weaponSubsys);
						mgr->AddDamageMessage(tgt, &take_damage);
					}
					else
					{
						Entity::TakeDamageMessage take_damage(
							Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
							0 /*inflictor id: bring-up*/, -1 /*unaimed -> cylinder resolves*/, dmg);
						tgt->Dispatch(&take_damage);
					}
					// gauge scoring wave (Step 6): a projectile hit credits SCORE too
					// (tgt == gEnemyMech here; local player is the viewpoint shooter).
					BTPostDamageScore((Entity *)tgt, p.damage);
					DEBUG_STREAM << "[projectile] IMPACT damage=" << p.damage
						<< " subsys=" << p.weaponSubsys
						<< (mgr ? " (msgmgr bundled)" : " (direct)")
						<< " (zone cyl-resolved)\n" << std::flush;

					// SPLASH (task #62): a detonating missile SALVO damages every
					// OTHER mech in the blast (tgt excluded -- it took the direct hit
					// above).  Fires ONCE per salvo, on the salvo-LEAD round only
					// (splashBurst>0), with baseBurst = the whole cluster count -- the
					// ONE arcade cluster missile.  The other visual rounds of the
					// salvo carry splashBurst=0 so the falloff floor-at-1 is applied
					// once, not N times (the missileCount-x over-splash bug).
					if (p.splashBurst > 0)
					{
						Damage sdmg    = dmg;
						sdmg.burstCount = p.splashBurst;	// cluster count as baseBurst
						extern void BTApplySplashDamage(Entity *, int, const Point3D &,
														Entity *, const Damage &);
						BTApplySplashDamage(p.shooter, p.weaponSubsys, p.pos, tgt, sdmg);
					}
				}
			}
			p.active = 0;
		}
	}
}

//###########################################################################
// MechDeathHandler effect-spawn bridge (mechdmg.cpp calls this)
//
// Spawns a damage-state descriptor's explosion at the mech.  The authentic path
// (FUN_0043663c -> FUN_004364e4) broadcasts a class-5 message to the effect
// manager app+0x38 (the 0xBD3 SubsystemMessageManager, unreconstructed); we use
// the same established Explosion::Make port the weapon path uses.  The binary
// derives the position from the subsystem (mech+0x184); we use the mech origin.
//###########################################################################
void
	BTSpawnDamageEffect(Mech *mech, int effect_resource, int segment_index)
{
	if (mech == 0)
		return;
	ResourceDescription::ResourceID res = (ResourceDescription::ResourceID)effect_resource;
	if (res <= 0)
		res = gExplodeRes;								// fall back to the resolved generic explosion
	if (res <= 0)
		return;											// nothing to spawn yet

	//
	// Effect position: the damaged zone's SEGMENT, in world space (the binary
	// derives the effect position from the damaged subsystem/zone, not the mech
	// origin -- an origin-anchored effect burns at ground level between the
	// feet).  Resolve segment_index through the segment table exactly as the
	// gun-port muzzles do (GetSegmentToEntity x localToWorld); fall back to
	// torso height over the origin when the zone has no segment.
	//
	Origin o = mech->localOrigin;
	Point3D fxPos = o.linearPosition;
	fxPos.y += kMuzzleHeight;							// default: torso height
	if (segment_index >= 0)
	{
		EntitySegment::SegmentTableIterator it(mech->segmentTable);
		EntitySegment *seg;
		while ((seg = it.ReadAndNext()) != NULL)
		{
			if (seg->GetIndex() == segment_index)
			{
				AffineMatrix mw;
				mw.Multiply(seg->GetSegmentToEntity(), mech->localToWorld);
				fxPos = mw;								// Point3D = matrix translation
				break;
			}
		}
	}
	o.linearPosition = fxPos;

	// IMPACT FRAME for the band effect: orient local -Z from the victim toward
	// the LAST ATTACKER (lastInflictingID, maintained by TakeDamageMessageHandler)
	// -- the .PFX offsets are authored "out of the struck armor".  Falls back to
	// the victim's own frame when the attacker can't be resolved.
	if (application != 0 && application->GetHostManager() != 0)
	{
		Entity *attacker =
			application->GetHostManager()->GetEntityPointer(mech->lastInflictingID);
		if (attacker != 0 && attacker != (Entity *)mech)
		{
			float adx = (float)(mech->localOrigin.linearPosition.x
				- attacker->localOrigin.linearPosition.x);
			float adz = (float)(mech->localOrigin.linearPosition.z
				- attacker->localOrigin.linearPosition.z);
			if (adx * adx + adz * adz > 1e-6f)
				o.angularPosition =								// -Z at the attacker
					EulerAngles(0.0f, (Scalar)atan2((double)adx, (double)adz), 0.0f);
		}
	}

	Explosion::MakeMessage m(
		Explosion::MakeMessageID, sizeof(Explosion::MakeMessage),
		(Entity::ClassID)RegisteredClass::ExplosionClassID, EntityID::Null,
		res, Explosion::DefaultFlags, o,
		mech->GetEntityID(), mech->GetEntityID());
	Explosion *e = Explosion::Make(&m);
	if (e)
		Register_Object(e);
}

//###########################################################################
// Death sequence -- the un-exported master-perf death branch (region
// 0x4a9770-0x4ab188), reconstructed from its EXPORTED consumers + the RP
// VTV::DeathShutdown analog.  See context/combat-damage.md "Death SEQUENCE".
//
// On a vital kill the damage side raises graphicAlarm to 9 (mechdmg.cpp:426/586).
// The authentic per-frame death transition (movementMode 5-8 = collapse -> 2/9 =
// disabled) lives in the Simulate the bring-up drive bypasses, so it is run here.
//###########################################################################
Logical
	Mech::IsMechDestroyed()
{
	// AUTHENTIC LATCH first: IsDestroyed == (movementMode 2 || 9) -- the death
	// modes (FUN_004ab1c8 zeroes locomotion for exactly these) [T1].  The death
	// modes never revert, so a wreck STAYS destroyed.
	//
	// graphicAlarm >= 9 is only the vital-kill TRIGGER that enters the death
	// transition (mechdmg.cpp:407/426).  It is a STATUS indicator, not a latch:
	// a later leg hit on the wreck legitimately rewrites it to 4/3
	// (mechdmg.cpp:417/419), which -- when this predicate was the alarm alone --
	// "resurrected" the wreck (movementMode reverted to 1) and let the next
	// vital hit run the WHOLE death transition again: double kill score, and a
	// Score dispatched into the respawn window's severed playerVehicle -> the
	// engine's Check(playerVehicle) abort (task #52 cdb catch).
	if (MovementMode() == 2 || MovementMode() == 9)
		return True;
	return graphicAlarm.GetLevel() >= 9 ? True : False;
}

//###########################################################################
// Mech::Reset (@0049fb74) -- the respawn RESET.  The authentic respawn REUSES
// the same mech entity: it moves the mech to the new drop-zone origin AND heals
// it back to a clean, alive state.  It does NOT create a new mech (our old
// sever-and-create respawn left the wreck behind as a second entity and built a
// duplicate viewpoint -- the source of the "2 mechs / camera-inside / on-fire
// respawn" glitches).
//
// Faithful reproduction of FUN_0049fb74, adapted to OUR layout: the 1995 raw
// offsets the decomp writes (+0x260/+0x298/+0x12c/...) land on DIFFERENT engine
// fields in the 2007 base (projectedOrigin/projectedVelocity/updateOrigin) and
// on our RELOCATED gait accumulators, so we reset the equivalent NAMED members
// rather than the raw offsets (per the databinding rule).  Steps mirror the
// decomp: reposition + kill dead-reckon; clear the death latch; heal every
// damage zone; DeathReset (vtable+0x28) every subsystem; ForceUpdate to
// broadcast the reset state to replicants.
//###########################################################################
void
	Mech::Reset(const Origin &origin, int mode)
{
	Check(this);

	// --- reposition (localOrigin @0x100, localToWorld @0xd0 rebuilt) ---
	localOrigin  = origin;
	localToWorld = origin;

	// --- kill dead-reckon / replication motion so the reset mech SNAPS to the
	//     drop zone and its replicant stops lerping back toward the death spot
	//     (the 2007 Mover-base fields at the decomp's +0x260/+0x298/+0x12c) ---
	projectedOrigin    = origin;
	previousOrigin     = origin;
	projectedVelocity  = Motion::Identity;
	updateVelocity     = Motion::Identity;
	updateAcceleration = Motion::Identity;
	// The Mover motion scalars too (the binary Reset zeroes the whole motion
	// block): a respawn is a TELEPORT -- stale death-frame velocity must never
	// survive into the first post-respawn collision/publish.
	worldLinearVelocity      = Vector3D(0.0f, 0.0f, 0.0f);
	localVelocity            = Motion::Identity;
	frameEntryWorldVelocity  = Vector3D(0.0f, 0.0f, 0.0f);
	ramLastVictim    = 0;
	ramContactLinger = 0.0f;
	// StopAllEntityEffects (@004d0c14): a respawned mech must not trail its
	// corpse's attached zone effects -- the authentic per-entity effect
	// cleanup, broadcast to every renderer.
	if (application != 0 && application->GetRendererManager() != 0)
		application->GetRendererManager()->StopAllEntityEffects((Entity *)this);

	// --- our RELOCATED gait/motion accumulators -> identity (the 1995 offsets
	//     the decomp zeroes map to these named members in our layout) ---
	ReconQuatIdentity(&motionDelta,   &kIdentityQuat);
	ReconQuatIdentity(&worldPose,     &kIdentityQuat);
	ReconQuatIdentity(&worldPoseBase, &kIdentityQuat);
	ReconQuatIdentity(&angularAccum,  &kIdentityQuat);
	ReconQuatIdentity(&aimRate,       &kIdentityQuat);

	// --- CLEAR THE DEATH LATCH (binary FUN_0049fb74: FUN_0041bbd8(+0x2c, 0)
	//     + the motion/replication scalars zeroed) ---
	SetMovementMode(0);							// simulationState 0 = init/reset; the
												// drive re-selects 1 next frame [T1]
	legCycleSpeed   = 0.0f;						// @0x348 (decomp Reset zeroes these)
	bodyCycleSpeed  = 0.0f;						// @0x6b8
	bodyTargetSpeed = 0.0f;						// @0x6b4
	poseSyncLatch   = 0;						// @0x77c
	graphicAlarm.SetLevel(0);					// clear >=9 (the vital-kill trigger)

	// --- HEAL every damage zone: full structure, intact skin, no burning ---
	for (int z = 0; z < damageZoneCount; ++z)
	{
		Mech__DamageZone *zone = Zone(z);
		if (zone != 0)
			zone->Heal();
	}

	// --- RESET every subsystem (the decomp's loop-2 vtable+0x28 = DeathReset,
	//     the base virtual symmetric with the DeathShutdown we run on death):
	//     restores each subsystem's heat/power/ammo/charge to the initial state ---
	for (int i = 0; i < GetSubsystemCount(); ++i)
	{
		Subsystem *s = GetSubsystem(i);
		if (s != 0)
			s->DeathReset(mode);
	}

	// --- locomotion pre-run + interest gates (a reset master must tick) ---
	SetPreRunFlag();
	if (interestCount == 0) interestCount = 1;

	// --- RENDER un-wreck: drop the sunk dbr hulk + restore the intact body ---
	//     (the wreck swap is one-way on the render side; Reset heals the sim but
	//     the mech keeps rendering as the sunk hulk without this).
	{
		extern void BTRebuildMechModel(Entity *entity);
		BTRebuildMechModel((Entity *)this);
	}

	// --- broadcast the reset state to replicants: the binary Reset tail is
	//     ForceUpdate(0x1f) -- record types 0-4 in one shot (pose + damage
	//     zones + speed + leg-state + orientation resync), replicant-gated on
	//     (flags+0x28 & 0xc) != 4.  Bit 1 re-broadcasts the healed damage
	//     zones: the falling edge MechDeathHandler::Tick uses to un-wreck +
	//     warp the peer on the observer's screen.  [T1 @0x49fdfc]
	if (GetInstance() != ReplicantInstance)
		ForceUpdate(0x1f);

	if (getenv("BT_DEATH_LOG"))
		DEBUG_STREAM << "[respawn] Mech::Reset " << GetEntityID()
			<< " healed+moved to (" << localOrigin.linearPosition.x << ","
			<< localOrigin.linearPosition.y << "," << localOrigin.linearPosition.z
			<< ") alive=" << (int)(!IsMechDestroyed())
			<< " zones=" << damageZoneCount
			<< " subsys=" << GetSubsystemCount() << "\n" << std::flush;
	Check_Fpu();
}

void
	Mech::UpdateDeathState(Scalar dt)
{
	if (!IsMechDestroyed())									// alive
		return;
	if (MovementMode() == 9)								// already settled (disabled/frozen)
	{
		// WRECK SMOKE (port addition, [T3]): keep the dead hulk smoking -- re-fire
		// the death/rubble smoke plume (psfx 1 = DDTHSMK, "the mech death/rubble
		// smoke plume") each time its authored 10-second emission window elapses.
		// The .PFX itself trickles 3 particles/sec for 10s; re-arming it on that
		// same cadence reads as a continuously burning wreck.  The one-shot death
		// visuals (dnboom + skins) stay in the kill path.
		// The wreck hulk's quadratic SINK (the 1996 script's burial): the pieces
		// settle into the ground and are gone ~17s after the kill.  While the
		// wreck is still visible, keep the death/rubble smoke plume alive on its
		// authored 10s window; once buried, the smoke stops with it.
		extern int BTWreckSinkTick(Entity *victim, float dt);
		int wreck_visible = BTWreckSinkTick(this, (float)dt);

		// BURIAL TRANSITION (one-shot): the authentic game REMOVES the dead
		// entity (the death row) once its wreck is gone; full entity teardown is
		// the remaining P5 follow-through (the render tree must be unhooked
		// first), so until then the buried wreck goes INERT instead: no
		// collision volume (stops phantom blocking -- the assistant's gather
		// skips a mover with collisionVolumeCount 0, and MoveCollisionVolume
		// early-outs) and no target lock (stops phantom hits/impact smoke on
		// the empty spot).
		if (!wreck_visible && collisionVolumeCount != 0)
		{
			// Zeroing the count stops MoveCollisionVolume from re-placing the
			// box -- but the mech-vs-mech gather (Mover::GetCurrentCollisions)
			// tests mover->collisionVolume DIRECTLY, never the count, so the
			// stale box would keep blocking at the wreck spot.  PARK it far
			// underground: a box 100km down intersects nothing, ever.
			collisionVolumeCount = 0;
			if (collisionVolume != 0)
			{
				collisionVolume->minY -= 100000.0f;
				collisionVolume->maxY -= 100000.0f;
			}
			if ((Entity *)this == gEnemyMech)
				gEnemyMech = 0;
			if (getenv("BT_DEATH_LOG"))
				DEBUG_STREAM << "[death] buried wreck went INERT (collision parked, "
					"target lock dropped)\n" << std::flush;
		}
		wreckSmokeTimer -= dt;
		if (wreck_visible && wreckSmokeTimer <= 0.0f)
		{
			wreckSmokeTimer = 10.0f;						// DDTHSMK's release window
			extern void BTStartPfx(int effect_number, float x, float y, float z);
			BTStartPfx(1,
				(float)localOrigin.linearPosition.x,
				(float)(localOrigin.linearPosition.y + kMuzzleHeight),
				(float)localOrigin.linearPosition.z);
			if (getenv("BT_DEATH_LOG"))
				DEBUG_STREAM << "[death] wreck smoke plume re-armed (psfx 1)\n" << std::flush;
		}
		return;
	}

	//
	// DEATH = FREEZE, not collapse (decomp-verified 2026-07-08, task #32).
	// The fall modes 5-8 latch leg/body clips from animation-table slots
	// 0x1c-0x1f (FUN_004a5028) -- but NO loader anywhere in the binary fills
	// those slots (mech+0x63c..0x648 appear in no exported function; the table
	// loader FUN_004a80d4 stops at the knockdown slot 0x20), NO fall clip
	// exists in the shipped 27-clip set, and firing the latch would bind
	// resource id 0 -- a StaticAudioStream -- as keyframes (garbage).  The
	// fall modes are engine-lineage vestiges; BT's death modes are the FREEZE
	// modes: IsDestroyed() == (movementMode 2 || 9), and FUN_004ab1c8 zeroes
	// locomotion for them.  So: shut the subsystems down and settle straight
	// to 9 (disabled/frozen wreck) -- never 5-8.  The death READ comes from
	// the effect layer (dnboom + the ddthsmk plume) + the destroyed skins.
	//
	// RP VTV::DeathShutdown analog: shut every subsystem down.  The base
	// Subsystem::DeathShutdown is an empty virtual (SUBSYSTM.h); overrides
	// act.  This is a SHUTDOWN, not teardown -- it frees nothing and never
	// removes the entity, so it is safe (the wreck STAYS).
	for (int i = 0; i < GetSubsystemCount(); ++i)
	{
		Subsystem *s = GetSubsystem(i);
		if (s != 0)
			s->DeathShutdown(1);
	}
	// Request the DEATH record BEFORE entering the disabled state (the
	// ForceUpdate filter masks types 2..8 once IsDisabled) -- the binary
	// death sender is Force(1) + Force(0x40) (@0x4aab2f/@0x4aab3a).  The
	// record emits on the next update pump with simulationState 9 in its
	// header; that flips the replicant's MovementMode, whose own
	// UpdateDeathState then runs the wreck sink/burial on the OBSERVER's
	// screen (this function runs for every mech, replicants included).
	ForceUpdate(Simulation::DefaultUpdateModelFlag
		| (1 << MechDeathUpdateModelBit));
	SetMovementMode(9);										// disabled: IsDisabled() true, locomotion frozen
	if (getenv("BT_DEATH_LOG"))
		DEBUG_STREAM << "[death] mech " << GetEntityID()
			<< " destroyed -> subsystem shutdown + frozen wreck (IsDisabled="
			<< (int)IsDisabled() << ")" << std::endl;

	// --- DEATH EFFECTS (task #42): dispatched HERE, at the VICTIM's own
	//     once-per-death transition, so they fire regardless of WHAT killed it
	//     (laser fire block, missile impact frames later, collision damage).
	//     They used to live in the shooter's laser fire block, whose kill
	//     check a missile killing blow never reached -- and post-#41 the
	//     boresight pick skips a dead mech, so that block never ran against
	//     it again: internally dead, smoking, standing, invulnerable.
	{
		// KILL score (once; the ownerless dummy yields no death for us).
		// task #60: pass the REAL killing-blow magnitude (latched in the damage
		// handler), not the flat bring-up kShotDamage -- the score handler
		// @0x4c02e4 derives the whole kill award from this damageAmount, so a
		// flat 12 made every kill score identically regardless of weapon.
		BTPostKillScore((Entity *)this, lastInflictingDamage);
		if ((Entity *)this == gEnemyMech)
			gEnemyDestroyed = 1;						// latches off damage score

		// The victim's AUTHENTIC per-mech death ModelList -- 'blhdead' (RES 22;
		// the burning-wreck script chain: effect 104 wreck swap + 1007 dnboom +
		// 1001 ddthsmk + 3/4/5/15).  Falls back to the generic explode effect.
		if (gExplodeReady == 0 && application != 0
			&& application->GetResourceFile() != 0)
		{
			ResourceDescription *exp =
				application->GetResourceFile()->FindResourceDescription(
					"explode", (ResourceDescription::ResourceType)1, -1);
			if (exp != 0) { gExplodeRes = exp->resourceID; gExplodeReady = 1; }
			else gExplodeReady = -1;
		}
		ResourceDescription::ResourceID dead_res = gExplodeRes;
		if (application != 0 && application->GetResourceFile() != 0)
		{
			ResourceDescription *dr =
				application->GetResourceFile()->FindResourceDescription(
					"blhdead", (ResourceDescription::ResourceType)1, -1);
			if (dr != 0)
			{
				dead_res = dr->resourceID;
				if (getenv("BT_DEATH_LOG"))
					DEBUG_STREAM << "[death] firing authentic death list 'blhdead' id="
						<< (long)dead_res << "\n" << std::flush;
			}
		}
		if (gExplodeReady == 1 || dead_res != gExplodeRes)
		{
			Origin death_origin = localOrigin;
			death_origin.linearPosition.y += kMuzzleHeight;
			Explosion::MakeMessage death_exp(
				Explosion::MakeMessageID,
				sizeof(Explosion::MakeMessage),
				(Entity::ClassID)RegisteredClass::ExplosionClassID,
				EntityID::Null,
				dead_res,
				Explosion::DefaultFlags,
				death_origin,
				GetEntityID(),							// the victim
				lastInflictingID);						// the killer (task #31 bookkeeping)
			Explosion *boom = Explosion::Make(&death_exp);
			if (boom) Register_Object(boom);
		}
		DEBUG_STREAM << "[damage] *** " << GetEntityID()
			<< " DESTROYED (death effects dispatched from the death transition) ***\n"
			<< std::flush;

		// OBSERVED-DEATH tally (MP DEATHS fix): a REPLICANT's death observed
		// here counts onto its owning player's LOCAL score copy -- the
		// symmetric half of the cross-pod KILLS credit (see btplayer.cpp
		// BTPlayerCountObservedDeath).  Masters count through their own
		// VehicleDead(-1) path; counting them here too would double-tally.
		if (GetInstance() == ReplicantInstance)
		{
			Player *owning_player = GetOwningPlayer();		// entity+0x190
			if (owning_player != 0)
			{
				extern void BTPlayerCountObservedDeath(void *);
				BTPlayerCountObservedDeath(owning_player);
			}
		}
	}

	//
	// --- RESPAWN CYCLE (task #52): notify the owning player of the death. ---
	// The BTPlayer VehicleDead handler @004c05c4 receives this with
	// deathCount == -1 (the ctor default = "immediate death notification"),
	// does the death bookkeeping, severs playerVehicle (this wreck entity
	// STAYS in the world) and re-posts the message to itself at +5 seconds
	// (5.0f @004c0830) -> the engine drop-zone hunt -> DropZoneReply ->
	// CreatePlayerVehicle (a NEW mech).  Only the owner pod's master has a
	// live playerLink, which is exactly where the cycle must run; replicant
	// wrecks just mirror the master's death.  [T1 the handler @004c05c4;
	// T3 this dispatch site -- the binary's exact sender is undecoded, but
	// the once-per-death transition is the only death edge and the message
	// ctor's deathCount=-1 default exists for precisely this notification.]
	//
	{
		Player *owner = GetPlayerLink();
		if (owner != 0)
		{
			Player::VehicleDeadMessage
				vehicle_dead(
					Player::VehicleDeadMessageID,
					sizeof(Player::VehicleDeadMessage)
				);
			owner->Dispatch(&vehicle_dead);
			if (getenv("BT_DEATH_LOG"))
				DEBUG_STREAM << "[death] VehicleDead(-1) dispatched to the owning player\n"
					<< std::flush;
		}
	}
}

// Peer heading published each sim update, sampled by the render loop (BT_RENDHDG).
volatile float gBTReplRenderYaw = -999.0f;

void
	Mech::PerformAndWatch(const Time& till, MemoryStream *update_stream)
{
	// Frame time slice from the simulation clock (same idiom as Mover::Perform).
	Scalar dt = till - lastPerformance;
	lastPerformance = till;

	// The bring-up DRIVE + ANIMATION path is single-player scaffolding: it reads
	// the global gBTDrive and a single shared gBodyAnim, and integrates THIS body's
	// origin.  It must run ONLY for the local player's mech (the viewpoint entity);
	// any other mech in the world (e.g. a spawned target/enemy) must NOT be driven
	// by the player's input or share the player's AnimationInstance.  Non-player
	// mechs still fall through to the subsystem-roster tick below.
	const Logical isPlayerMech =
		(application != 0 && (Entity *)this == application->GetViewpointEntity());

	// task #59: keep this mech on the authentic gait clip set for its role --
	// INTERIOR (level cockpit) if it's the mech you pilot, EXTERIOR (the -8deg
	// walk lean) for every other mech.  One-time flip per viewpoint change; the
	// ctor lands everyone on interior because the port never sets the copy bit.
	MaintainViewClipSet();

	// DEATH consumer -- runs for EVERY mech (player + spawned targets).  On a
	// vital kill it collapses + shuts the subsystems down + settles to disabled;
	// the drive's movementMode write below is guarded on !IsMechDestroyed so a
	// dead mech keeps its death state instead of reverting to a live gait.
	UpdateDeathState(dt);

	// MechDeathHandler (@0042aa2c): per-frame destroyed-skin + explosion effects as
	// this mech's zones cross damage thresholds.  The binary ticks it off the mech's
	// Performance list (mech+0xbc), which the bring-up drive override bypasses, so
	// it is driven here.  Runs for EVERY mech (so the enemy visibly falls apart too).
	if (deathHandler != 0)
		((MechDeathHandler *)deathHandler)->Tick();

	// WAVE 7 Phase B: advance/render/impact the flying projectiles once per frame (viewpoint).
	if (isPlayerMech && dt > 0.0001f)
		BTUpdateProjectiles(dt);

	// 1-Hz non-player mech telemetry (multiplayer diagnosis): every mech that is
	// NOT the local viewpoint -- whatever its instance claims -- with position.
	if (!isPlayerMech && getenv("BT_REPL_LOG") && dt > 0.0001f)
	{
		static float s_otherLog = 0.0f; s_otherLog += dt;
		if (s_otherLog >= 1.0f)
		{
			s_otherLog = 0.0f;
			DEBUG_STREAM << "[mech-exec] entity " << GetEntityID()
				<< " instance=" << (int)GetInstance()
				<< " pos=(" << localOrigin.linearPosition.x << ", "
				<< localOrigin.linearPosition.z << ")" << std::endl;
		}
	}

	// --- REPLICANT MOTION (P6 multiplayer) -----------------------------------
	// A remote-mastered mech does not run the local drive: it dead-reckons
	// between network updates via the engine reckoner (Mover::DeadReckon lerps
	// localOrigin toward projectedOrigin, which ReadUpdateRecord snaps from the
	// master's broadcast update records -- MOVER.cpp:493).  Subsystems still
	// tick below (their ctors carry the replicant gates on simulationFlags).
	if (GetInstance() == ReplicantInstance)
	{
		if (dt > 0.0001f && dt < 0.5f)
		{
			// AUTHENTIC PEER HEADING (decomp FUN_004ab188/FUN_00409f58 via FUN_004ab1c8):
			// the original replicant integrates its heading INCREMENTALLY from the
			// CURRENT pose -- compose an exact rotation of (replicated yaw rate * dt)
			// onto the rendered heading each frame -- and re-anchors it when a type-4
			// resync lands.  It does NOT slerp toward a projected angular target: the
			// engine Mover::DeadReckon lerp uses the shared lastUpdate/nextUpdate
			// timebase, which the DENSE type-0 pose stream resets every frame while
			// walking while RESTORING a stale orientation (the authentic case-0 strip)
			// -- so the angular projection barely advances from a stale base and the
			// slerp DRAGS the heading back each frame.  Measured: walking+turning, the
			// peer yaw advanced at ~40% rate with half the frames stepping BACKWARD
			// (the keyboard-steering skip); pure spin/walk never showed it (one stream).
			// Save the heading, let DeadReckon own the LINEAR channel, then override
			// the angular result with the original's incremental integration.
			// ROOT-MOTION peer POSITION (authentic FUN_004ab1c8: the clip's travel
			// drives the body between records; pose records re-anchor).  Save the
			// position so the engine velocity-lerp result can be discarded below
			// (BT_DR_POS=1 restores the velocity dead-reckon for A/B).
			// DEFAULT: engine velocity dead-reckon (best-measured: ratio 1.0022).  The
			// coupled root-motion experiment (BT_ROOT_POS=1) needs the MASTER-side
			// gait mirror to stop the authority tug-of-war (measured ratio 1.86) --
			// the completion path is mapped in the session notes.
			static const int s_drPos = getenv("BT_DR_POS") ? 1 : 0;	// default 0 = authentic gait-coupled linear (BT_DR_POS=1 restores velocity)
			Point3D replPrevPos = localOrigin.linearPosition;
			Quaternion replPrevHeading = localOrigin.angularPosition;
			DeadReckon(dt);							// engine reckoner: LINEAR position/velocity
			if (angSyncLatch)
			{
				// type-4 arrived: re-anchor on the authoritative orientation
				localOrigin.angularPosition = updateOrigin.angularPosition;
				angSyncLatch = 0;
			}
			else
			{
				Vector3D replAngStep;
				replAngStep.Multiply(updateVelocity.angularMotion, dt);
				ReconQuatIntegrate(&localOrigin.angularPosition,
								   &replPrevHeading, &replAngStep);	// exact FUN_00409f58
			}
			localOrigin.angularPosition.Normalize();
			if (!s_drPos)
				localOrigin.linearPosition = replPrevPos;	// root-motion mode: gait applies travel below
			localToWorld = localOrigin;

			// RENDER-vs-SIM decoupling probe: publish the heading the RENDER will
			// actually draw (localToWorld == localOrigin here).  The render loop
			// (L4VIDEO.cpp) logs this PER RENDERED FRAME; if it repeats the same
			// value across render frames, the peer sim updates slower than the
			// render draws -> the rotation stutters even though each sim update is
			// smooth (which the per-update [replhdg] metric could not see).
			{
				extern volatile float gBTReplRenderYaw;
				YawPitchRoll _rry; _rry = localOrigin.angularPosition;
				gBTReplRenderYaw = (float)_rry.yaw;
			}

			// Lightweight dead-reckon smoothness probe (one flush/sec, cheap).
			// PATH length (sum of |per-frame step|) vs NET displacement reveals
			// jitter: a smooth walk has path==net; oscillation inflates path while
			// net stays small.  avgHorizon is the lerp target distance in time.
			if (getenv("BT_JIT"))
			{
				static bool  s_jinit = false;
				static Point3D s_jprev, s_jnetA;
				static float s_jacc = 0.0f, s_jpath = 0.0f, s_jmax = 0.0f, s_jhzn = 0.0f;
				static int   s_jfr = 0;
				if (s_jinit)
				{
					const float dx = (float)(localOrigin.linearPosition.x - s_jprev.x);
					const float dz = (float)(localOrigin.linearPosition.z - s_jprev.z);
					const float step = sqrtf(dx * dx + dz * dz);
					s_jpath += step;
					if (step > s_jmax) s_jmax = step;
				}
				else { s_jnetA = localOrigin.linearPosition; s_jinit = true; }
				s_jprev = localOrigin.linearPosition;
				s_jhzn += (float)(nextUpdate - lastPerformance);
				s_jfr++; s_jacc += dt;
				if (s_jacc >= 1.0f)
				{
					const float ndx = (float)(localOrigin.linearPosition.x - s_jnetA.x);
					const float ndz = (float)(localOrigin.linearPosition.z - s_jnetA.z);
					const float net = sqrtf(ndx * ndx + ndz * ndz);
					DEBUG_STREAM << "[repljit] frames=" << s_jfr
						<< " path=" << s_jpath << " net=" << net
						<< " ratio=" << (net > 0.001f ? s_jpath / net : 0.0f)
						<< " maxStep=" << s_jmax << " avgStep=" << (s_jpath / s_jfr)
						<< " avgHorizon=" << (s_jhzn / s_jfr)
						<< "\n" << std::flush;
					s_jacc = 0.0f; s_jpath = 0.0f; s_jmax = 0.0f; s_jhzn = 0.0f;
					s_jfr = 0; s_jnetA = localOrigin.linearPosition;
				}
			}

			// REPLICANT GAIT (task #50): animate the peer mech's legs at the
			// REPLICATED speed -- without this the replicant slides around as a
			// frozen statue.  The master publishes worldLinearVelocity in every
			// update record (Mover::WriteUpdateRecord, MOVER.cpp:759); the
			// replicant stores it in updateVelocity.linearMotion (:712).  Derive
			// the signed speed demand from it (forward = local -Z; a negative
			// forward dot = the master is reversing), feed the LEG channel's live
			// demand source, and advance the leg state machine for its JOINT
			// writes only -- TRAVEL stays with DeadReckon (the returned distance
			// is discarded), so position always follows the master and the clip
			// cadence matches the replicated speed (residual foot-slip is the
			// inherent dead-reckoning artifact).  The state machine self-arms
			// stand->walk / winds down from the demand exactly as on the master.
			MechControlsMapper *replMppr = MappingMapper();	// roster slot 0 (task #7)
			if (!IsMechDestroyed() && replMppr != 0)
			{
				// AUTHENTIC LEG-CHANNEL DEMAND (decomp-verified, workflow wv1km7lvc D3):
				// the leg channel FUN_004a5028 reads its speed demand from
				// mapper->speedDemand == **(mech+0x128)+0x128 (part_012.c:11947/11975/
				// 12028) and slews cadence + selects the stand->walk gate from it.  On a
				// peer this MUST be the REPLICATED COMMANDED speed bodyTargetSpeed@0x6b4 --
				// the master's own throttle-commanded speedDemand (mechmppr.cpp:749),
				// stamped into every record (mech.cpp:2036, record->speedDemand) and read
				// back on the peer (mech.cpp type-0 @1833, type-2 @1841/1848, type-3 @1871).
				// It is the IDENTICAL value the master's leg channel consumes, so the peer
				// clears the stand->walk gate exactly when the master does (the commanded
				// speed is what makes the master walk, so it clears standSpeed by
				// construction) and ramps cadence CONTINUOUSLY through accel/decel --
				// cadence tracks travel like the master.  bodyTargetSpeed is signed
				// (negative == reverse); it is 0 only while COASTING (thr=0), which
				// authentically winds the gait down to stand, matching the master.
				//
				// The prior default DERIVED a noisy speed from the dead-reckoned velocity
				// and PINNED it flat with a standSpeed*1.05 floor across the walk threshold
				// -- the CONFIRMED leg-stutter/skip + cadence-vs-travel desync.  Retained
				// behind BT_REPL_VEL for A/B comparison only.
				static const int s_replVel = getenv("BT_REPL_VEL") ? 1 : 0;
				if (s_replVel)
				{
					const Vector3D &wv = updateVelocity.linearMotion;
					float spd = sqrtf((float)(wv.x * wv.x + wv.z * wv.z));
					UnitVector zAxR;
					localToWorld.GetFromAxis(Z_Axis, &zAxR);
					const float fdot = -((float)wv.x * (float)zAxR.x
									   + (float)wv.z * (float)zAxR.z);	// mech faces -Z
					Scalar sd = (fdot < 0.0f) ? -spd : spd;
					if (sd > standSpeed * 0.5f && sd < standSpeed * 1.05f)
						sd = standSpeed * 1.05f;
					replMppr->speedDemand = sd;
				}
				else
				{
					replMppr->speedDemand = bodyTargetSpeed;	// authentic replicated commanded speed
				}
				// AUTHENTIC PEER TURNING is NET-DRIVEN (decomp-verified D8): the angular
				// slerp inside DeadReckon (MOVER.cpp:521-525) rotates
				// localOrigin.angularPosition toward projectedOrigin.angularPosition from
				// the replicated yaw rate (decomp angular path part_012.c:15001/15010).
				// The leg SM's turn-in-place 'trn' clip is a MASTER-ONLY construct fed by
				// the LIVE controls mapper; the old synthetic yawRate->turnDemand
				// (quantized +-0.02) flickered the trn clip in/out as the noisy replicated
				// rate crossed the band -> the jerky pivot / statue-rotate.  Do NOT
				// synthesize it -- let the whole body rotate via the slerp.
				// TURN-STEP (presentation of the authentic pivot): the authentic peer
				// shows a stepping pivot because its BODY channel replays the master's
				// replicated turn body-STATE (type-3 SetBodyAnimation, mech.cpp:1869).
				// We run the LEG channel (D2 not switched), which has no replicated turn
				// state, so we reproduce the visible turn-step by arming the leg SM's
				// 'trn' clip from the replicated yaw rate.  The old feed used a bare
				// +-0.02 threshold that CHATTERED the trn clip in/out as the noisy rate
				// wandered near zero (the "jerky pivot", decomp-noted D8).  HYSTERESIS
				// fixes that: enter the turn only above +-0.08 rad/s, hold it (the
				// mapper's turnDemand persists frame-to-frame -- we are its only writer on
				// a peer) until the rate clearly collapses below 0.03.  A steady turn sits
				// well above both, so the clip never flickers.  (BT_REPL_NOTURN =
				// decomp-strict net-driven rotation with NO step.)
				const float yawRate = (float)updateVelocity.angularMotion.y;
				static const int s_noTurn = getenv("BT_REPL_NOTURN") ? 1 : 0;
				if (s_noTurn)
				{
					replMppr->turnDemand = 0.0f;
				}
				else
				{
					const float prevTd = (float)replMppr->turnDemand;	// last frame (our own write)
					if (prevTd != 0.0f)								// turning: hold until the rate collapses
						replMppr->turnDemand = (fabsf(yawRate) < 0.03f) ? 0.0f : prevTd;
					else											// idle: start only on a clear turn
						replMppr->turnDemand = (yawRate >  0.08f) ? 1.0f
											 : (yawRate < -0.08f) ? -1.0f : 0.0f;
				}
				// Prime the same clip-advance scalars the master's gait block sets
				// each frame -- uninitialized on a replicant they read 0, freezing
				// the clip at advance-time dt*0 (observed: legState engaged at 11,
				// legCycle stuck 0).  (The old forwardCycleRate floor is retired:
				// the ctor reads the authentic MaxAcceleration since the task #4
				// record-layout fix.)
				globalTimeScale = 1.0f;
				idleStrideScale = 1.0f;
				// reverseSpeedMax2@0x7a0: the run-cycle rise-CLAMP (leg case 12/13);
				// LoadLocomotionClips doesn't set it, so a replicant reads 0xCDCD
				// (-4.3e8) and the clamp CLOBBERS legCycleSpeed the moment the run
				// cycle engages (observed live: legCycle=-4.31602e+08, state 13
				// stuck).  Same heal as the master's drive block.
				reverseSpeedMax2 = reverseStrideLength;
				// legCycleSpeed itself is also debug-heap garbage until a walk
				// state first writes it (Standing never touches it); sane-band it
				// once so a direct run-state entry can't slew from -4.3e8.
				if (legCycleSpeed < -100.0f || legCycleSpeed > 200.0f)
					legCycleSpeed = 0.0f;
				// PEER POSE CHANNEL (decomp workflow w1s9ou02o -- the authentic fix): the
				// 1995 peer posed the WHOLE skeleton from the BODY channel (FUN_004a5678)
				// FOLLOWING the master's replicated body-anim state (bodyStateAlarm@0x728,
				// set by the type-3 reader's SetBodyAnimation, mech.cpp:1913) + replicated
				// bodyTargetSpeed@0x6b4.  The LEG channel (FUN_004a5028) is DEAD CODE.  Our
				// port resurrected the leg SM as the peer's poser fed a LOCALLY re-derived
				// commanded speed, and the leg SM's phase-independent pre-switch wind-down
				// (mech2.cpp:560-568, force-jump {6,7,8,9}->stand when legCycleSpeed<=0) makes
				// the peer SKIP the master's decel state 8 and snap to stand while the body
				// coasts (the 'slid forward after legs stopped' bug).  The body channel has
				// NO such wind-down and follows the replicated decel state, playing the
				// master's exact clips.  Swapping the peer to the body channel RESTORES the
				// original single-channel design (no new netcode).  BT_PEER_LEGCH=1 = old.
				static const int s_peerLegCh = getenv("BT_PEER_LEGCH") ? 1 : 0;
				// TRUE-MIRROR CADENCE: drive the body clip from the ACTUAL replicated ground
				// speed so the animation phase tracks the master exactly instead of drifting
				// via the local bodyCycleSpeed slew (the accel/decel hesitation).  Consumed in
				// AdvanceBodyAnimation walk/reverse.  -1 disables -> authentic slew.
				if (!s_peerLegCh && !getenv("BT_NO_MIRROR_CAD"))
				{
					const Vector3D &mcv = updateVelocity.linearMotion;
					peerMirrorSpeed = (Scalar)sqrtf((float)(mcv.x * mcv.x + mcv.z * mcv.z));
				}
				else
					peerMirrorSpeed = -1.0f;
				// TURN-STEP ARMING for the body channel: the body Standing case only enters
				// walk/reverse/stand -- it never arms the turn-in-place clip (state 4); on the
				// master that arming comes from the LEG Standing case cross-arming both
				// channels (mech2.cpp:937 note), which the peer no longer runs.  Arm/exit the
				// body turn state here from the replicated turn (replMppr->turnDemand, from the
				// replicated yaw rate + hysteresis above): standing + turning -> state 4 (body
				// case 4 then advances the trn clip -> the peer STEPS through the pivot); turn
				// stops -> back to Standing.  Walk/reverse transitions still own their exits.
				if (!s_peerLegCh)
				{
					const int  bs = (int)bodyStateAlarm.GetLevel();
					const bool turning = (replMppr->turnDemand > 0.05f
									   || replMppr->turnDemand < -0.05f);
					if (turning && bs == 0)			// standing + turning -> enter turn
						SetBodyAnimation(4);
					else if (!turning && bs == 4)	// turn stopped -> back to stand
						SetBodyAnimation(0);
				}
				const Scalar replLegAdv = s_peerLegCh
					? AdvanceLegAnimation(dt)			// old: re-derived leg SM
					: AdvanceBodyAnimation(dt, 1);		// authentic: body channel, mj=1 poses skeleton
				// AUTHENTIC coupled position (mirror of the master's world-step at
				// mech4.cpp:3325-3336 == Mech::IntegrateMotion tail @004ab1c8): between
				// records the body advances by the CLIP'S OWN travel rotated by the
				// heading -- feet and ground are locked BY CONSTRUCTION, so cadence-vs-
				// travel mismatch (the speed-change glitch) is structurally impossible.
				// A pose record re-anchors onto the authoritative position
				// (poseSyncLatch, armed by every type-0 read).  adv is SIGNED by the SM
				// (reverse clips return negative travel; master dir==1.0 in SM mode).
				if (!s_drPos)
				{
					// POSE-SYNC ERROR-PROPORTIONAL ABSORPTION: track the error to the
					// received authority (updateOrigin) and bleed it into localOrigin at a
					// rate that scales with the error, so a small steady offset is absorbed
					// gently (no foot-pop) while a large speed-change drift is caught fast.
					// The residual snap came from two sources: (a) a fixed slow ~1/3s decay
					// let drift reach 3-11u during accel/decel; (b) a hard ground snap popped
					// because records are not perfectly dense on this relay.  Error-scaled
					// absorption is the stable middle: k grows from ~0.15/frame at small
					// error toward ~0.6 at large error.  (Records re-measure the error every
					// frame they arrive, so this converges to authority within a few frames.)
					{
						Vector3D rmErr;
						rmErr.Subtract(updateOrigin.linearPosition, localOrigin.linearPosition);
						poseSyncLatch = 0;
						const float eMag = (float)rmErr.Length();
						float k = 0.15f + eMag * 0.15f;			// error-proportional catch-up
						if (k > 0.6f) k = 0.6f;
						// DIAG (BT_SNAPLOG, task #50): the peer drift = |authority - dead-reckon|
						// absorbed this frame -- the "snap" magnitude.  Log > 0.5u so an accel/decel
						// soak (BT_DRIVE_SWEEP0) measures the residual.
						static const int s_snapLog = getenv("BT_SNAPLOG") ? 1 : 0;
						if (s_snapLog && eMag > 0.5f)
							DEBUG_STREAM << "[snap] drift=" << eMag << " k=" << k
								<< " legAdv=" << replLegAdv << "\n" << std::flush;
						// AT-REST snap (user "gliding stops"): when the legs have wound down
						// (replLegAdv ~0), gliding the body to absorb the residual offset is
						// VISIBLE because no leg motion masks it.  With the feet planted a
						// sub-unit correction is invisible, so resolve it immediately instead
						// of gliding it in over several frames.  Only when the offset is small
						// (a large offset at rest = a real teleport that should still ease).
						const Logical atRest = ((replLegAdv < 0.05f && replLegAdv > -0.05f)
											 && eMag < 1.0f);
						if (atRest) k = 1.0f;
						localOrigin.linearPosition.x += rmErr.x * k;
						localOrigin.linearPosition.y += rmErr.y * k;
						localOrigin.linearPosition.z += rmErr.z * k;
					}
					if (replLegAdv != 0.0f)
					{
						Vector3D rmLocal(0.0f, 0.0f, -replLegAdv);	// forward = -Z, per-frame DISTANCE
						Matrix34 rmOrient;
						Matrix34::FromQuaternion(&rmOrient, &localOrigin.angularPosition);
						Vector3D rmWorld;
						FUN_00408744(&rmWorld, (Scalar *)&rmLocal, &rmOrient);
						localOrigin.linearPosition.x += rmWorld.x;
						localOrigin.linearPosition.y += rmWorld.y;
						localOrigin.linearPosition.z += rmWorld.z;
					}
					localToWorld = localOrigin;				// refresh with the coupled position
				}

				// SLIDE probe (BT_SLIDE): per-frame, log when the peer position MOVES
				// while its legs are in STAND (legState 0) -- the user's "sliding after
				// legs stopped".  Reports move magnitude, offset to authority, leg state.
				if (getenv("BT_SLIDE"))
				{
					static Point3D s_slPrev; static int s_slInit = 0;
					const int ls = getenv("BT_PEER_LEGCH") ? (int)legStateAlarm.GetLevel() : (int)bodyStateAlarm.GetLevel();
					if (s_slInit)
					{
						const float dx = (float)(localOrigin.linearPosition.x - s_slPrev.x);
						const float dz = (float)(localOrigin.linearPosition.z - s_slPrev.z);
						const float mv = sqrtf(dx * dx + dz * dz);
						if (ls == 0 && mv > 0.02f)		// standing but moving = slide
						{
							Vector3D off; off.Subtract(updateOrigin.linearPosition, localOrigin.linearPosition);
							DEBUG_STREAM << "[slide] move=" << mv
								<< " legAdv=" << replLegAdv
								<< " offToAuth=" << (float)off.Length()
								<< " legState=" << ls << "\n" << std::flush;
						}
					}
					s_slPrev = localOrigin.linearPosition; s_slInit = 1;
				}

				// PEER-DRIFT probe (BT_MIRDIV, 1s max): how far the peer's coupled
				// position wanders from the last-received authority (updateOrigin)
				// before a record re-anchors it -- the other end of the snap.
				if (getenv("BT_MIRDIV"))
				{
					static float s_pdAcc = 0.0f, s_pdMax = 0.0f;
					Vector3D d; d.Subtract(localOrigin.linearPosition, updateOrigin.linearPosition);
					const float dl = (float)d.Length();
					if (dl > s_pdMax) s_pdMax = dl;
					s_pdAcc += dt;
					if (s_pdAcc >= 1.0f)
					{
						DEBUG_STREAM << "[peerdrift] maxDriftFromAuthority=" << s_pdMax
							<< " replLegAdv=" << replLegAdv << "\n" << std::flush;
						s_pdAcc = 0.0f; s_pdMax = 0.0f;
					}
				}

				// GAIT-EVENNESS probe (BT_GAITEV, 1s stats): the metric of the VISIBLE
				// speed-change glitch -- per-frame leg-clip advance regularity + leg-state
				// transition (flap) rate on the peer.  Position dead-reckons smoothly
				// regardless; the churn shows in the LEGS.
				if (getenv("BT_GAITEV"))
				{
					static float s_gvPrevF = -1.0f, s_gvAcc = 0.0f, s_gvSum = 0.0f, s_gvMax = 0.0f;
					static int   s_gvN = 0, s_gvBack = 0, s_gvTrans = 0, s_gvPrevSt = -1;
					static float s_gvDemPrev = 0.0f; static int s_gvDemChg = 0;
					const int _pb = getenv("BT_PEER_LEGCH") ? 1 : 0;
					const float gf = _pb ? (float)legAnimation.currentFrame : (float)bodyAnimation.currentFrame;
					const int   gs = _pb ? (int)legStateAlarm.GetLevel() : (int)bodyStateAlarm.GetLevel();
					if (s_gvPrevF >= 0.0f)
					{
						float df = gf - s_gvPrevF;
						if (df < -5.0f) df += 20.0f;			// clip wrap heal (approx)
						const float adf = (df < 0.0f) ? -df : df;
						s_gvSum += adf; if (adf > s_gvMax) s_gvMax = adf;
						if (df < 0.0f) s_gvBack++;
						s_gvN++;
					}
					if (s_gvPrevSt >= 0 && gs != s_gvPrevSt) s_gvTrans++;
					if ((float)bodyTargetSpeed != s_gvDemPrev) { s_gvDemChg++; s_gvDemPrev = (float)bodyTargetSpeed; }
					s_gvPrevF = gf; s_gvPrevSt = gs; s_gvAcc += dt;
					if (s_gvAcc >= 1.0f && s_gvN > 0)
					{
						DEBUG_STREAM << "[gaitev] frmAvg=" << (s_gvSum / s_gvN)
							<< " frmMax=" << s_gvMax
							<< " back=" << s_gvBack << "/" << s_gvN
							<< " stateFlips/s=" << s_gvTrans
							<< " demChanges/s=" << s_gvDemChg
							<< " state=" << gs << "\n" << std::flush;
						s_gvAcc = 0.0f; s_gvSum = 0.0f; s_gvMax = 0.0f; s_gvN = 0; s_gvBack = 0; s_gvTrans = 0; s_gvDemChg = 0;
					}
				}

				// TURN-STEP rate probe (BT_TRNRATE, 0.5s): body rotation rate vs trn clip advance.
				if (getenv("BT_TRNRATE"))
				{
					static bool  s_tinit = false;
					static float s_tYaw = 0.0f, s_tFrm = 0.0f, s_tAcc = 0.0f, s_tRot = 0.0f, s_tAdv = 0.0f;
					YawPitchRoll _typ; _typ = localOrigin.angularPosition;
					const float yaw = (float)_typ.yaw, frm = (float)legAnimation.currentFrame;
					if (s_tinit)
					{
						float dy = yaw - s_tYaw; if (dy > 3.14159f) dy -= 6.28319f; if (dy < -3.14159f) dy += 6.28319f;
						s_tRot += (dy < 0.0f) ? -dy : dy;
						const float df = frm - s_tFrm;
						if (df > 0.0f) s_tAdv += df;
					}
					s_tYaw = yaw; s_tFrm = frm; s_tinit = true;
					s_tAcc += dt;
					if (s_tAcc >= 0.5f)
					{
						DEBUG_STREAM << "[trnrate] bodyRot/s=" << (s_tRot / s_tAcc) << "rad"
							<< " legFrm/s=" << (s_tAdv / s_tAcc)
							<< " legState=" << (int)legStateAlarm.GetLevel()
							<< " turnDemand=" << MappingMapper()->turnDemand << "\n" << std::flush;
						s_tAcc = 0.0f; s_tRot = 0.0f; s_tAdv = 0.0f;
					}
				}

				// Cadence-vs-travel sync probe (0.25s, ~4 flush/s -- cheap).  Compares
				// the leg CADENCE input (bodyTargetSpeed / legCycleSpeed) against the
				// ACTUAL dead-reckoned ground speed; if the legs are in sync the two
				// track together and legFrm advances monotonically.
				if (getenv("BT_ANIM"))
				{
					static bool  s_ainit = false;
					static Point3D s_aprev;
					static float s_aacc = 0.0f, s_apath = 0.0f;
					if (s_ainit)
					{
						const float dx = (float)(localOrigin.linearPosition.x - s_aprev.x);
						const float dz = (float)(localOrigin.linearPosition.z - s_aprev.z);
						s_apath += sqrtf(dx * dx + dz * dz);
					}
					s_aprev = localOrigin.linearPosition; s_ainit = true;
					s_aacc += dt;
					if (s_aacc >= 0.25f)
					{
						DEBUG_STREAM << "[anim] cmd=" << bodyTargetSpeed
							<< " drSpeed=" << (s_apath / s_aacc)
							<< " legCyc=" << legCycleSpeed
							<< " legState=" << (int)legStateAlarm.GetLevel()
							<< " legFrm=" << legAnimation.currentFrame
							<< "\n" << std::flush;
						s_aacc = 0.0f; s_apath = 0.0f;
					}
				}

				if (getenv("BT_REPL_TRN"))
				{
					static float s_rt = 0.0f; s_rt += dt;
					if (s_rt >= 0.15f)
					{
						s_rt = 0.0f;
						DEBUG_STREAM << "[repltrn] yawRate=" << yawRate
							<< " turnDemand=" << replMppr->turnDemand
							<< " legState=" << (int)legStateAlarm.GetLevel()
							<< " bodyState=" << (int)bodyStateAlarm.GetLevel()
							<< " legFrm=" << legAnimation.currentFrame
							<< " bodyFrm=" << bodyAnimation.currentFrame
							<< " spd=" << replMppr->speedDemand
							<< " tgt=" << bodyTargetSpeed << "\n" << std::flush;
					}
				}
				// PER-FRAME heading trace (BT_REPL_HDG, choppy-spin diagnosis): the
				// replicant's rendered yaw each frame + dt.  A smooth dead-reckoned
				// spin advances ~yawRate*dt every frame; choppiness shows as heading
				// STALLS followed by SNAPS (records arriving without integration
				// between them).
				if (getenv("BT_REPL_HDG") && yawRate != 0.0f)
				{
					YawPitchRoll rh, ph, uh;
					rh = localOrigin.angularPosition;
					ph = projectedOrigin.angularPosition;
					uh = updateOrigin.angularPosition;
					DEBUG_STREAM << "[replhdg] yaw=" << (Scalar)rh.yaw
						<< " proj=" << (Scalar)ph.yaw
						<< " upd=" << (Scalar)uh.yaw
						<< " lp-lu=" << (lastPerformance.ticks - lastUpdate.ticks)
						<< " nu-lp=" << (nextUpdate.ticks - lastPerformance.ticks)
						<< " yawRate=" << yawRate << " dt=" << dt << "\n" << std::flush;
				}

				// PER-FRAME travel/animation coupling probe (BT_REPL_MOV, user:
				// "slides forward before it steps"): position vs the dead-reckon target
				// vs the derived speed vs the leg SM state, every frame there is
				// replicated motion.  Reveals travel-vs-animation lag + lerp-gap jitter.
				if (getenv("BT_REPL_MOV"))
				{
					const Scalar uvx = (Scalar)updateVelocity.linearMotion.x;
					const Scalar uvz = (Scalar)updateVelocity.linearMotion.z;
					const Scalar umag = (Scalar)sqrtf(uvx*uvx + uvz*uvz);
					if (umag > 0.01f || replMppr->speedDemand != 0.0f)
						DEBUG_STREAM << "[replmov]"
							<< " pos=(" << localOrigin.linearPosition.x << ","
							<< localOrigin.linearPosition.z << ")"
							<< " proj=(" << projectedOrigin.linearPosition.x << ","
							<< projectedOrigin.linearPosition.z << ")"
							<< " uvel=" << umag
							<< " spd=" << replMppr->speedDemand
							<< " legState=" << (int)legStateAlarm.GetLevel()
							<< " legFrm=" << legAnimation.currentFrame
							<< " standSp=" << standSpeed
							<< " walkStr=" << walkStrideLength
							<< " nu-lp=" << (nextUpdate.ticks - lastPerformance.ticks)
							<< " dt=" << dt << "\n" << std::flush;
				}

				// REPLICANT BEAMS (task #51): the emitters carry live replicated
				// discharge state (Emitter::ReadUpdateRecord); draw them with the
				// same per-weapon walk the master uses.
				DrawWeaponBeams(dt);
			}

			if (getenv("BT_REPL_LOG"))
			{
				static float s_replLog = 0.0f; s_replLog += dt;
				if (s_replLog >= 1.0f)
				{
					s_replLog = 0.0f;
					DEBUG_STREAM << "[repl] mech " << (long)GetEntityID()
						<< " pos=(" << localOrigin.linearPosition.x << ", "
						<< localOrigin.linearPosition.y << ", "
						<< localOrigin.linearPosition.z << ")"
						<< " vel=(" << updateVelocity.linearMotion.x << ","
						<< updateVelocity.linearMotion.z << ")"
						<< " legState=" << (int)legStateAlarm.GetLevel()
						<< " legCycle=" << legCycleSpeed << "\n" << std::flush;
				}
			}
		}
	}

	// AUTHENTIC GROUND MODEL for NON-PLAYER masters (task #15): the binary
	// installs FUN_004a9b5c as the performance of EVERY MasterInstance mech
	// (ctor part_012.c:9947-9956), so the spawned test dummy grounds/collides
	// too -- not just the player.  The player's call runs inside the drive
	// block below (after its move, with the true start-of-frame position).
	if (!isPlayerMech && GetInstance() == MasterInstance && GroundReal()
		&& dt > 0.0001f && dt < 0.5f)
	{
		// A stationary master: old_position is a COPY of the current position
		// (must not alias localOrigin.linearPosition -- the block writes it).
		Point3D preMovePos = localOrigin.linearPosition;
		AuthenticGroundAndCollide(dt, preMovePos);
	}

	if (isPlayerMech && dt > 0.0001f && dt < 0.5f)		// ignore zero / huge (stall) slices
	{
		// --- VIRTUAL CONTROLS (dev keyboard -> the pod's analog inputs) --------
		// The pod's throttle was an ABSOLUTE analog LEVER and its turn input an
		// analog stick; momentary 0/1 keys can't express either (at 60fps every
		// tap = a frame-perfect FULL demand = the "controls too sensitive after
		// the perf fix" report -- at the old 10fps most taps aliased away, which
		// only LOOKED like fine control).  Integrate dt-scaled (fps-independent):
		//   lever:  W/S sweep a PERSISTENT lever (tap ~= 0.07 step, full sweep
		//           ~1.4s) with a DETENT at zero -- braking from forward stops AT
		//           0; release and press S again to engage reverse.  X = all stop.
		//   stick:  A/D deflect a momentary stick over ~0.4s, auto-centering on
		//           release -- tap = gentle nudge, hold = full rate.
		{
			static float sLever = 0.0f, sStick = 0.0f;
			static int   sDetent = 0;					// latched at the zero crossing
			const float kLeverRate = 0.7f;				// lever sweep per second
			const float kStickRate = 2.5f;				// stick deflect per second
			const float kStickCenterRate = 5.0f;		// stick auto-center per second

			// POLL the real key state.  WndProc key messages are unusable: the
			// engine keyboard reader (L4CTRL.cpp:1506) GetMessage()s every
			// WM_KEYUP/WM_CHAR out of the queue for its key-command channel, so
			// only KEYDOWNs ever reached the WndProc and key state latched on
			// forever.  GetAsyncKeyState is immune; the foreground guard keeps
			// background typing from driving the mech.
			{
				typedef short (__stdcall *AsyncFn)(int);
				typedef void *(__stdcall *FgFn)(void);
				typedef unsigned long (__stdcall *WtpFn)(void *, unsigned long *);
				typedef unsigned long (__stdcall *PidFn)(void);
				static AsyncFn pAsync = 0; static FgFn pFg = 0;
				static WtpFn pWtp = 0; static PidFn pPid = 0;
				if (pAsync == 0)
				{
					HMODULE u = GetModuleHandleA("user32.dll");
					HMODULE k = GetModuleHandleA("kernel32.dll");
					pAsync = (AsyncFn)GetProcAddress(u, "GetAsyncKeyState");
					pFg    = (FgFn)GetProcAddress(u, "GetForegroundWindow");
					pWtp   = (WtpFn)GetProcAddress(u, "GetWindowThreadProcessId");
					pPid   = (PidFn)GetProcAddress(k, "GetCurrentProcessId");
				}
				int focused = 0;
				if (pFg && pWtp && pPid)
				{
					void *fg = pFg();
					unsigned long fgPid = 0;
					if (fg) pWtp(fg, &fgPid);
					focused = (fgPid == pPid());
				}
				// TEST HOOK (BT_KEY_NOFOCUS=1): accept keys without foreground focus --
				// automation harnesses (SendInput from a background shell) can't grant
				// real foreground; interactive play never needs this.
				static int sNoFocus = -1;
				if (sNoFocus < 0) { const char *nf = getenv("BT_KEY_NOFOCUS"); sNoFocus = (nf && *nf == '1') ? 1 : 0; }
				if (sNoFocus) focused = 1;
				if (pAsync)
				{
					const int dn = 0x8000;
					gBTDrive.keyFwd   = focused && ((pAsync('W') | pAsync(0x26 /*VK_UP*/))    & dn) ? 1 : 0;
					gBTDrive.keyBack  = focused && ((pAsync('S') | pAsync(0x28 /*VK_DOWN*/))  & dn) ? 1 : 0;
					gBTDrive.keyLeft  = focused && ((pAsync('A') | pAsync(0x25 /*VK_LEFT*/))  & dn) ? 1 : 0;
					gBTDrive.keyRight = focused && ((pAsync('D') | pAsync(0x27 /*VK_RIGHT*/)) & dn) ? 1 : 0;
					// WEAPON GROUPS (task #43, KEYBOARD only per user): three fire
					// channels like three pod buttons -- 1/SPACE = lasers, 2 = PPCs,
					// 3/CTRL = missiles.  (Interim; the authentic system is the
					// ConfigureMappables/ChooseButton mapper channels.)
					gBTLaserKey       = focused && ((pAsync('1') | pAsync(0x20 /*VK_SPACE*/)) & dn) ? 1 : 0;
					gBTPPCKey         = focused && (pAsync('2') & dn) ? 1 : 0;
					gBTMissileKey     = focused && ((pAsync('3') | pAsync(0x11 /*VK_CONTROL*/)) & dn) ? 1 : 0;
					// The pod's 4TH fire button (Pinky, 0x45) -- previously
					// unmapped on desktop, so any weapon the authored groups
					// put there (the Avatar's NARC etc.) was unreachable.
					gBTPinkyKey       = focused && (pAsync('4') & dn) ? 1 : 0;
					// task #6: HOLD 'G' opens the config session on the selected
					// weapon (BT_CONFIG_SLOT, default: the first weapon); while
					// held, the fire keys TOGGLE its group membership.
					gBTConfigKey      = focused && (pAsync('G') & dn) ? 1 : 0;
					// task #12: F5..F8 assign the selected weapon to Generator
					// A..D; F9 toggles Manual/Auto reconnect.
					gBTGenSelKey =
						  (focused && (pAsync(0x74 /*F5*/) & dn)) ? 4
						: (focused && (pAsync(0x75 /*F6*/) & dn)) ? 5
						: (focused && (pAsync(0x76 /*F7*/) & dn)) ? 6
						: (focused && (pAsync(0x77 /*F8*/) & dn)) ? 7
						: (focused && (pAsync(0x78 /*F9*/) & dn)) ? 8
						: 0;
					// task #13: 'C' cycles the coolant valve (BT_VALVE_SLOT
					// picks the condenser roster slot; default = Condenser1).
					gBTValveKey = focused && (pAsync('C') & dn) ? 1 : 0;
					// TORSO CONTROLS (2026-07-13): 'M' cycles the control mode
					// (Basic -> Standard -> Veteran -- the pod console button,
					// CycleControlModeMessageHandler); Q/E deflect the torso
					// twist axis (the STICK in Standard/Veteran, where A/D
					// become the pedals).  Ramped like the turn stick.
					{
						static int sPrevM = 0;
						const int mNow = focused && (pAsync('M') & dn) ? 1 : 0;
						if (mNow && !sPrevM) gBTModeCycle = 1;	// edge -> one cycle
						sPrevM = mNow;
						const int tw = (focused && (pAsync('E') & dn) ? 1 : 0)
									 - (focused && (pAsync('Q') & dn) ? 1 : 0);
						static float sTwist = 0.0f;
						if (tw != 0)
						{
							sTwist += tw * kStickRate * dt;
							if (sTwist >  1.0f) sTwist =  1.0f;
							if (sTwist < -1.0f) sTwist = -1.0f;
						}
						else if (sTwist != 0.0f)
						{
							// SPRING-CENTER on release (user fix 2026-07-13: the
							// old hold-deflection model left a residual axis ->
							// the torso drifted forever with no way to stop).
							// The pod stick is spring-centered: the axis is a
							// twist-RATE demand, so release = rate 0 = the torso
							// HOLDS where you aimed it (position is kept by
							// Torso::currentTwist, not by the axis).  Same model
							// as the A/D turn stick (kStickCenterRate).
							const float step = kStickCenterRate * dt;
							if      (sTwist >  step) sTwist -= step;
							else if (sTwist < -step) sTwist += step;
							else                     sTwist  = 0.0f;
						}
						// X = all-stop for the torso too: zero the axis NOW and
						// pulse the AUTHENTIC recenter (torso centerCommand ->
						// Recenter @004b6918 slews currentTwist back to 0; any
						// new Q/E deflection cancels it, sim-side).  Separate
						// edge detector from the drive all-stop below -- both
						// fire on the same press.
						static int sPrevXT = 0;
						const int xtNow = focused && (pAsync('X') & dn) ? 1 : 0;
						if (xtNow && !sPrevXT)
						{
							sTwist = 0.0f;
							gBTTorsoRecenter = 1;	// mapper consumes -> CommandRecenter()
						}
						sPrevXT = xtNow;
						gBTTwistAxis = sTwist;
					}
					// gBTDrive.fire = "any weapon trigger down" (feeds the bring-up
					// damage dispatcher + the beam-visual keepalive)
					gBTDrive.fire     = (gBTLaserKey || gBTPPCKey || gBTMissileKey || gBTPinkyKey) ? 1 : 0;
					static int sPrevX = 0;
					const int xNow = focused && (pAsync('X') & dn) ? 1 : 0;
					if (xNow && !sPrevX) gBTDrive.allStop = 1;	// edge -> one all-stop
					sPrevX = xNow;
					// V: toggle the view between the authentic COCKPIT eyepoint
					// (the pod's only view) and the external chase camera.
					static int sPrevV = 0, sViewInside = 0;
					const int vNow = focused && (pAsync('V') & dn) ? 1 : 0;
					if (vNow && !sPrevV)
					{
						sViewInside = !sViewInside;
						extern void BTSetViewInside(int inside);
						BTSetViewInside(sViewInside);
					}
					sPrevV = vNow;

					// RETICLE = SCREEN CENTER (task #58 correction, supersedes the
					// task #39 "body-mounted view" model): the CAMERA yaws with
					// the torso (the cockpit sits on jointtorso -- see the
					// gBTEyeTwist publisher), so screen center always IS the gun
					// boresight and the crosshair stays centered while the WORLD
					// rotates past it -- the twist reads on the bottom tape
					// carets / compass / radar wedge, not the crosshair.  [T1:
					// jointtorso->jointeye->siteeyepoint chain + FUN_004c22c4
					// inverse-chain view; crosshair-twist forensics 2026-07-13.]
					// The old gBTAimX = tan(twist) slew was a port invention on
					// the falsified body-mounted premise -- with the yawing eye
					// it double-counts (crosshair pinned to HULL-forward, fire
					// ray resolving body-forward: "twisting leaves the
					// crosshairs behind").  Reticle mobility exists in the
					// binary (Reticle::reticlePosition, RETICLE.h:42) but its
					// twist-era use is the FIXED-TORSO free-aim channel
					// (mech+0x36c, writer un-exported) -- deferred.
					// BT_AIM="x y" remains as the headless harness.
					{
						static int   sAimEnv = -1;
						static float sAimEnvX = 0.0f, sAimEnvY = 0.0f;
						if (sAimEnv < 0)
						{
							const char *av = getenv("BT_AIM");
							sAimEnv = (av != 0 &&
								sscanf(av, "%f %f", &sAimEnvX, &sAimEnvY) == 2) ? 1 : 0;
						}
						if (sAimEnv)
						{
							gBTAimX = sAimEnvX;
							gBTAimY = sAimEnvY;
						}
						else
						{
							// Boresight = screen center: the eye carries the
							// twist (task #58), the pick ray inherits it from
							// the published eye basis.
							gBTAimX = 0.0f;
							gBTAimY = 0.0f;
						}
					}
				}
			}

			// DEV: BT_AUTOFIRE=1 holds the trigger (drives the fireForced hook) and
			// BT_AUTODRIVE=<0..1> holds the throttle (drives the forced hook) so the
			// full walk->fire->damage->death chain can be exercised headlessly.
			{
				static int   sAutoFire  = -1;
				static float sAutoDrive = -1.0f;
				static int   sGotoDrive = -1;
				if (sAutoFire < 0)
				{
					const char *af = getenv("BT_AUTOFIRE");
					sAutoFire = (af && *af == '1') ? 1 : 0;
					const char *ad = getenv("BT_AUTODRIVE");
					sAutoDrive = ad ? (float)atof(ad) : 0.0f;
					const char *gv = getenv("BT_GOTO");
					sGotoDrive = (gv && *gv) ? 1 : 0;
				}
				gBTDrive.fireForced = sAutoFire;
				if (sAutoDrive > 0.0f)
				{
					gBTDrive.forced         = 1;
					gBTDrive.forcedThrottle = sAutoDrive;
					// task #64 harness: BT_WALK_DELAY holds the FORCED throttle (the
					// mapper's actual input -- the local `throttle` gate below never
					// reached it) at 0 for n sim-secs (turn-in-place phase, with
					// BT_FORCE_TURN active), then ramps it over 1.2s -- the manual
					// "lever dwell through the walk threshold" repro.
					{
						static float s_wdD = -1.0f, s_wdC = 0.0f;
						if (s_wdD < -0.5f)
						{
							const char *v = getenv("BT_WALK_DELAY");
							s_wdD = v ? (float)atof(v) : 0.0f;
						}
						if (s_wdD > 0.0f)
						{
							s_wdC += dt;
							float r = (s_wdC - s_wdD) / 1.2f;
							gBTDrive.forcedThrottle = (r <= 0.0f) ? 0.0f
								: (r < 1.0f ? sAutoDrive * r : sAutoDrive);
						}
					}
					// BT_DRIVE_SWEEP=<period>: triangle-sweep the FORCED throttle
					// 0.2..0.9 -- sweeps the ACTUAL drive velocity AND (via mechmppr's
					// forced key_throttle) the mapper demand together, the true
					// keyboard accel/decel regime.  BT_FORCE_STEP=1 quantizes it to
					// the RIO Ranger 0.05 grid (the authentic stepped input shape).
					{
						static float s_dsP = -1.0f, s_dsClock = 0.0f;
						static int   s_dsStep = -1;
						if (s_dsP < 0.0f)
						{
							const char *v = getenv("BT_DRIVE_SWEEP");
							s_dsP = v ? (float)atof(v) : 0.0f;
							s_dsStep = getenv("BT_FORCE_STEP") ? 1 : 0;
						}
						if (s_dsP > 0.0f)
						{
							s_dsClock += dt;
							float ph  = fmodf(s_dsClock, 2.0f * s_dsP) / s_dsP;
							float tri = (ph < 1.0f) ? ph : (2.0f - ph);
							// BT_DRIVE_SWEEP0=1: sweep 0.0..0.7 (through STOP each cycle -- the
							// stand<->walk gauntlet: walk-entry, came-to-rest sends, type-3 stomps).
							static int s_dsZero = -1;
							if (s_dsZero < 0) s_dsZero = getenv("BT_DRIVE_SWEEP0") ? 1 : 0;
							float thr = s_dsZero ? (0.7f * tri) : (0.2f + 0.7f * tri);
							if (s_dsStep)
								thr = floorf(thr / 0.05f + 0.5f) * 0.05f;
							gBTDrive.forcedThrottle = thr;
						}
					}
				}
				else if (sGotoDrive)
				{
					// BT_GOTO is self-driving: enable forced mode so the goto beeline
					// block (gated on gBTDrive.forced, below) runs on its own without
					// requiring BT_AUTODRIVE.  That block sets the actual throttle
					// (drive toward the target, 0 inside the stop radius).
					gBTDrive.forced         = 1;
					gBTDrive.forcedThrottle = 0.8f;
				}
			}

			if (gBTDrive.allStop) { sLever = 0.0f; sDetent = 0; gBTDrive.allStop = 0; }

			if (getenv("BT_KEY_LOG"))
			{
				static float sKlog = 0.0f; sKlog += dt;
				if (sKlog >= 1.0f) { sKlog = 0.0f;
					DEBUG_STREAM << "[vctl] fwd=" << gBTDrive.keyFwd << " back=" << gBTDrive.keyBack
						<< " L=" << gBTDrive.keyLeft << " R=" << gBTDrive.keyRight
						<< " fire=" << gBTDrive.fire << " lever=" << sLever
						<< " stick=" << sStick << "\n" << std::flush; }
			}

			const int fwd = gBTDrive.keyFwd, back = gBTDrive.keyBack;
			if (!fwd && !back) sDetent = 0;				// keys released -> detent re-arms
			float sweep = ((fwd ? 1.0f : 0.0f) - (back ? 1.0f : 0.0f)) * kLeverRate * dt;
			if (sweep != 0.0f && !sDetent)
			{
				const float prev = sLever;
				sLever += sweep;
				// zero detent: a sweep that CROSSES 0 stops there and latches until
				// the keys are released (clean stop instead of swinging to reverse).
				if ((prev > 0.0f && sLever <= 0.0f && back) ||
					(prev < 0.0f && sLever >= 0.0f && fwd))
				{
					sLever = 0.0f;
					sDetent = 1;
				}
				if (sLever >  1.0f) sLever =  1.0f;
				if (sLever < -1.0f) sLever = -1.0f;
			}

			const float want = (gBTDrive.keyRight ? 1.0f : 0.0f) - (gBTDrive.keyLeft ? 1.0f : 0.0f);
			if (want != 0.0f)
			{
				sStick += want * kStickRate * dt;
				if (sStick >  1.0f) sStick =  1.0f;
				if (sStick < -1.0f) sStick = -1.0f;
			}
			else if (sStick != 0.0f)					// auto-center
			{
				const float step = kStickCenterRate * dt;
				if      (sStick >  step) sStick -= step;
				else if (sStick < -step) sStick += step;
				else                     sStick  = 0.0f;
			}

			// RIO-GRID PUBLISH (authentic input shaping; decomp workflow w0odszxro):
			// the pod's analog lever/stick reached the mapper through the RIO Ranger
			// (800 ADC counts, 0.05 deadband -- L4RIO.cpp:801), so the DEMAND the 1995
			// machinery consumed moved in coarse, settled STEPS with silence between
			// them -- one type-2 speed record per step, and each gait threshold
			// (standSpeed etc., zero hysteresis) crossed ONCE per maneuver.  Our
			// keyboard integrator sweeps CONTINUOUSLY (kLeverRate every frame of a
			// key-hold): ~86 records per accel, demand DWELLING on the knife edges ->
			// transition-clip churn -- the speed-change peer glitch.  Publish on the
			// Ranger's own 0.05 grid: internal lever feel unchanged, published demand
			// steps like the real hardware (a bucket holds ~4 frames at kLeverRate, so
			// the send gate self-closes between steps).  Also kills -epsilon reverse
			// publishes from rest (bucket rounds to 0 until the lever passes -0.025;
			// authentic reverse is a BUTTON sign-flip, never a sweep through zero).
			// DEFAULT: CONTINUOUS publish (authentic -- the pod lever was analog,
			// 800-count ADC ~= continuous; empirical A/B found NO gait/position
			// difference between swept and stepped demand after the clock-guard +
			// heading-integrator fixes, so the grid gains nothing and makes the
			// DRIVE step unauthentically).  BT_GRID_LEVER=1 keeps the 0.05-grid
			// variant for experiments.
			static const int s_gridLever = getenv("BT_GRID_LEVER") ? 1 : 0;
			if (s_gridLever)
			{
				gBTDrive.throttle = floorf(sLever / 0.05f + 0.5f) * 0.05f;
				gBTDrive.turn     = floorf(sStick / 0.05f + 0.5f) * 0.05f;
			}
			else
			{
				gBTDrive.throttle = sLever;
				gBTDrive.turn     = sStick;
			}
		}

		// DEATH GATE (task #52): a DESTROYED mech takes no pilot input -- kill
		// throttle/turn/fire and the forced harnesses at the source so the
		// wreck can't be steered around invisibly ("ghost mode", user-reported
		// from 2-window play).  Deliberately INPUT-only: the gait/animation
		// machinery below keeps running so the death collapse plays out and the
		// wind-down settles; respawn is the recovery/drop cycle (task #52 open).
		if (IsMechDestroyed())
		{
			gBTDrive.throttle   = 0.0f;
			gBTDrive.turn       = 0.0f;
			gBTDrive.fire       = 0;
			gBTDrive.fireForced = 0;
			gBTDrive.forced     = 0;
			gBTLaserKey = gBTPPCKey = gBTMissileKey = gBTPinkyKey = 0;
		}

		float throttle = gBTDrive.forced ? gBTDrive.forcedThrottle : gBTDrive.throttle;
		float turn     = gBTDrive.forced ? 0.0f : gBTDrive.turn;

		// BT_SPAWN_AT="x z [hdg]" (DEBUG harness): teleport the player to a
		// world position (+ optional heading, radians) on the first driven
		// frame -- exact-position reproduction of a user-reported render
		// glitch for A/B toggle comparison at the same viewpoint.
		{
			static int s_spawnAt = -1;
			static float s_sx = 0.0f, s_sz = 0.0f, s_sh = 0.0f;
			static int s_haveH = 0;
			if (s_spawnAt < 0)
			{
				const char *sv = getenv("BT_SPAWN_AT");
				if (sv)
				{
					const int n = sscanf(sv, "%f %f %f", &s_sx, &s_sz, &s_sh);
					if (n >= 2) { s_spawnAt = 1; s_haveH = (n >= 3); }
					else s_spawnAt = 0;
				}
				else s_spawnAt = 0;
			}
			if (s_spawnAt == 1)
			{
				s_spawnAt = 2;
				localOrigin.linearPosition.x = s_sx;
				localOrigin.linearPosition.z = s_sz;
				// FIX (user-reported: teleport embeds the mech in sloped terrain):
				// BT_SPAWN_AT set only X/Z, keeping the OLD (dropzone) Y -- wrong for
				// the new ground height.  Lift the mech WELL ABOVE local terrain so the
				// authentic per-frame ground SNAP (BoxTree FindBoundingBoxUnder, no
				// gravity) settles it onto the surface next frame.  The snap only
				// lowers, so it must start above the surface, not below.
				localOrigin.linearPosition.y += 500.0f;
				if (s_haveH)
				{
					gDriveHeading = s_sh;
					// the mech's REAL orientation (the chase camera derives from
					// it) is the quaternion, not the scalar mirror -- set both,
					// same convention as the bring-up drive path below.
					localOrigin.angularPosition = EulerAngles(0.0f, s_sh, 0.0f);
				}
				localToWorld = localOrigin;
				DEBUG_STREAM << "[spawnat] teleported to (" << s_sx << ", " << s_sz
					<< ") hdg=" << (s_haveH ? s_sh : gDriveHeading) << "\n" << std::flush;
			}
		}

		// BRING-UP repro harness: BT_FORCE_SECONDS=<n> releases the forced throttle
		// after n sim-seconds (headless stand-in for an interactive tap->release,
		// exercising the walk->stop gait transition the constant-throttle soaks
		// never take); BT_FORCE_TURN=<t> holds a steering demand (the turn path).
		if (gBTDrive.forced)
		{
			static float s_forceClock = 0.0f, s_forceLimit = -1.0f, s_forceTurn = -999.0f;
			if (s_forceLimit < 0.0f)
			{
				const char *fs = getenv("BT_FORCE_SECONDS");
				s_forceLimit = fs ? (float)atof(fs) : 0.0f;
			}
			if (s_forceTurn < -900.0f)
			{
				const char *ft = getenv("BT_FORCE_TURN");
				s_forceTurn = ft ? (float)atof(ft) : 0.0f;
			}
			turn = s_forceTurn;
			// Publish the forced turn to gBTDrive too: mechmppr derives turnDemand
			// from gBTDrive.turn (NOT this local), so without this the headless rig
			// never entered the trn state -- BT_FORCE_TURN was silently inert for
			// the gait (task #64 harness fix).
			gBTDrive.turn = s_forceTurn;
			// BT_WALK_DELAY=<n> (stutter repro, task #64): hold throttle at 0 for the
			// first n sim-seconds (TURN-IN-PLACE, turn stays active) then release the
			// forced throttle -- reproduces "turn in place, then start walking BEFORE
			// the turn stops" exactly.  Turn (BT_FORCE_TURN) persists across the seam.
			static float s_walkDelay = -1.0f, s_wdClock = 0.0f;
			if (s_walkDelay < -0.5f)
			{
				const char *wd = getenv("BT_WALK_DELAY");
				s_walkDelay = wd ? (float)atof(wd) : 0.0f;
			}
			if (s_walkDelay > 0.0f)
			{
				s_wdClock += dt;
				if (s_wdClock < s_walkDelay)
					throttle = 0.0f;					// turn-in-place phase
				else
				{
					// RAMP the throttle up over 1.2s (simulate the manual lever
					// sweep DWELLING through standSpeed) with the turn still active
					// -- the continuous-stutter repro.
					float r = (s_wdClock - s_walkDelay) / 1.2f;
					if (r < 1.0f) throttle *= r;
				}
			}
			if (s_forceLimit > 0.0f)
			{
				s_forceClock += dt;
				if (s_forceClock > s_forceLimit)
				{
					throttle = 0.0f;
					turn = 0.0f;
				}
			}
			// BT_GOTO="x z" (DEBUG harness): beeline toward a world point --
			// steer the turn demand from the heading error each frame (headless
			// reproduction of "run toward that feature").  Uses the scalar
			// heading mirror gDriveHeading (forward = -Z at heading 0).
			{
				static float s_gx = 0.0f, s_gz = 0.0f;
				static int   s_goto = -1;
				if (s_goto < 0)
				{
					const char *gv = getenv("BT_GOTO");
					if (gv && !stricmp(gv, "enemy")) s_goto = 2;	// chase nearest replicant
					else if (gv && sscanf(gv, "%f %f", &s_gx, &s_gz) == 2) s_goto = 1;
					else s_goto = 0;
				}
				if (s_goto == 2)
				{
					// DYNAMIC target (test harness): beeline toward the CLOSEST live
					// peer replicant wherever it currently is -- MP spawn positions
					// vary per run, so a fixed "x z" can't reliably face the enemy.
					extern int BTGetTargetCandidates(Entity *shooter, Entity **out, int maxOut);
					Entity *ec[32];
					const int enc = BTGetTargetCandidates((Entity *)this, ec, 32);
					float best = 1e30f; int found = 0;
					for (int ei = 0; ei < enc; ++ei)
					{
						Mech *em = (Mech *)ec[ei];
						if (em == 0 || em->IsMechDestroyed()) continue;	// any peer (solo dummy OR replicant)
						Point3D ep = em->localOrigin.linearPosition;
						float edx = (float)ep.x - (float)localOrigin.linearPosition.x;
						float edz = (float)ep.z - (float)localOrigin.linearPosition.z;
						float ed2 = edx*edx + edz*edz;
						if (ed2 < best) { best = ed2; s_gx = (float)ep.x; s_gz = (float)ep.z; found = 1; }
					}
					if (found) s_goto = 2; else { gBTGotoActive = 0; }
				}
				if (s_goto == 1 || (s_goto == 2 && (s_gx != 0.0f || s_gz != 0.0f)))
				{
					float ddx = s_gx - (float)localOrigin.linearPosition.x;
					float ddz = s_gz - (float)localOrigin.linearPosition.z;
					float dist2 = ddx * ddx + ddz * ddz;
					// Heading error from the mech's ACTUAL forward (localToWorld's -Z
					// axis), NOT the gDriveHeading scalar mirror: that mirror is seeded
					// to 0, but MP pilots (and any non-zero spawn pose) start facing a
					// different way, so `want - gDriveHeading` steered the beeline the
					// WRONG way.  err = signed angle (about +Y) from forward to target.
					UnitVector zAxisG;
					localToWorld.GetFromAxis(Z_Axis, &zAxisG);
					float fwdX = -(float)zAxisG.x, fwdZ = -(float)zAxisG.z;	// faces -Z
					float err = 0.0f;
					float tlen = (float)sqrt((double)dist2);
					if (tlen > 1e-3f)
					{
						float tX = ddx / tlen, tZ = ddz / tlen;
						float dot = fwdX * tX + fwdZ * tZ;
						float crs = fwdZ * tX - fwdX * tZ;	// signed; flip if it steers away
						err = (float)atan2((double)crs, (double)dot);
					}
					// STOP radius: "enemy" holds at weapon range so it can shoot
					// instead of ramming; a fixed point drives right up to the spot.
					const float stopDist  = (s_goto == 2) ? 300.0f : 5.0f;
					const float stopDist2 = stopDist * stopDist;
					const int   arrived   = (dist2 < stopDist2);
					// publish for the mapper bridge (the real-controls path reads
					// gBTDrive in mechmppr.cpp, NOT this local `turn`)
					gBTGotoTurn = (err > 0.3f) ? 1.0f : (err < -0.3f ? -1.0f
						: (err > 0.02f ? 0.3f : (err < -0.02f ? -0.3f : 0.0f)));
					if (arrived) gBTGotoTurn = 0.0f;
					// throttle down while far off-heading (the authentic
					// speed-vs-turn clamp makes run-speed turn circles huge)
					gBTGotoThrottle = (err > 0.5f || err < -0.5f) ? 0.2f : 1.0f;
					gBTGotoActive = 1;
					if (getenv("BT_GOTO_LOG"))
					{
						static float s_gl = 0.0f; s_gl += dt;
						if (s_gl >= 1.0f) { s_gl = 0.0f;
							DEBUG_STREAM << "[goto] dist=" << (float)sqrt((double)dist2)
								<< " err=" << err << " turn=" << gBTGotoTurn
								<< " thr=" << gBTGotoThrottle << " arr=" << arrived
								<< " fwd=(" << fwdX << "," << fwdZ << ")"
								<< " tgt=(" << s_gx << "," << s_gz << ")" << std::endl; }
					}
					// SELF-DRIVE (task #48): a "beeline" harness must supply its OWN
					// forward throttle, not only steering -- otherwise it just turns
					// in place and never closes the distance (the "drive-to-range
					// stall": BT_GOTO alone advanced nothing because the throttle came
					// only from BT_AUTODRIVE).  Drive forward toward the target and cut
					// the throttle inside the stop radius so it holds at firing range.
					gBTDrive.forced         = 1;
					gBTDrive.forcedThrottle = arrived ? 0.0f : 0.8f;
					turn = gBTGotoTurn;		// non-real-controls fallback path
				}
			}
		}

		// ⭐ REAL CONTROLS (env BT_REAL_CONTROLS): route the raw input through the
		// RECONSTRUCTED MechControlsMapper (@004afd10) instead of consuming it raw.
		// INPUT BRIDGE (dev scaffolding, marked): write the mapper's published input
		// attributes exactly as a streamed Direct .CTL mapping would -- the engine's
		// ControlsInstanceDirectOf<T> writes these same public members from the
		// device groups; the dev box has no RIO/Thrustmaster, so WASD stands in for
		// the stick/throttle HARDWARE.  Everything downstream (speed/turn demands,
		// mode clamps, torso axes, HUD free-aim) is the real reconstructed tick,
		// which runs in the subsystem-roster walk below (un-skipped under this env).
		static const int s_realControls = BTEnvOn("BT_REAL_CONTROLS", 1);	// default ON (=0 to disable)
		MechControlsMapper *mppr = MappingMapper();			// roster slot 0 (task #7)
		if (s_realControls && mppr != 0)
		{
			// Diagnostic: what the ENGINE controls push left in the attribute since
			// our last write (a stale device element overwriting the bridge shows
			// here as pre != our previous write).
			float preThrottle = mppr->throttlePosition;
			(void)preThrottle;
			mppr->throttlePosition = (throttle >= 0.0f) ? throttle : -throttle;
			mppr->reverseThrust    = (throttle < 0.0f) ? 1 : 0;	// ControlsButton: >=1 engaged
			mppr->stickPosition.x  = turn;						// Basic mode: turn = stick yaw
			mppr->stickPosition.y  = 0.0f;
			// Consume the PREVIOUS frame's interpreted demands (the mapper ticks in
			// the roster walk after this block -- one frame of input latency).
			// turnDemand is the mode-shaped steering; speedDemand (world u/s, sign =
			// reverse) feeds the gait target below.
			turn = mppr->turnDemand;
			// BT_GOTO steering must reach the orientation integration DIRECTLY: the
			// mapper round-trip (stickPosition -> turnDemand) zeroes out in -net mode
			// (the key-bridge only shapes the local viewpoint mech there), which froze
			// the beeline's heading (it drove straight past the target).  The goto turn
			// is a plain yaw demand -- apply it here, after the mapper read, so it works
			// identically solo and cross-net.
			extern int gBTGotoActive; extern float gBTGotoTurn;
			if (gBTGotoActive) turn = gBTGotoTurn;
			static float s_mpprLog = 0.0f; s_mpprLog += dt;
			if (s_mpprLog >= 1.0f)
			{
				s_mpprLog = 0.0f;
				DEBUG_STREAM << "[mppr] in thr=" << mppr->throttlePosition
					<< " pre=" << preThrottle
					<< " rev=" << mppr->reverseThrust
					<< " stickX=" << mppr->stickPosition.x
					<< " -> speedDemand=" << mppr->speedDemand
					<< " turnDemand=" << mppr->turnDemand
					<< " mode=" << mppr->controlMode
					<< " mapper=" << (void*)mppr
					<< " &mode=" << (void*)&mppr->controlMode << "\n" << std::flush;
			}
		}

		// BRING-UP one-shot: dump the mech's skeleton segment names + joint indices so
		// we can identify the TORSO horizontal joint for torso-twist tracking (P3).
		{
			static int s_segDump = 0;
			if (s_segDump++ == 0)
			{
				EntitySegment::SegmentTableIterator it(segmentTable);
				EntitySegment *seg;
				int i = 0;
				while ((seg = it.ReadAndNext()) != 0)
				{
					DEBUG_STREAM << "[skel] seg[" << i++ << "] name=" << seg->GetName()
						<< " jointIdx=" << seg->GetJointIndex() << "\n";
				}
				DEBUG_STREAM << "[skel] total=" << i << "\n" << std::flush;
			}
		}

		// Snapshot the pre-move position for the collision resolve after the drive.
		Point3D collisionOldPos = localOrigin.linearPosition;

		if (!gDriveSeeded) { gDriveHeading = 0.0f; gDriveSeeded = 1; }

		// --- AUTHENTIC turn rate (master-perf disasm 0x4aa3d3-0x4aa4ff [T1]) ---------
		// The yaw rate is per-mech lerp(walkingTurnRate, runningTurnRate) by GROUND
		// SPEED, not the old bring-up constant kDriveTurnRate.  spd = horizontal speed
		// (last frame's worldLinearVelocity); base = walkingTurnRate; above the low
		// gate (reverseSpeedMax@0x538) lerp walk->run across [walkStride 0x534 ..
		// topSpeed 0x34c], with an over-run falloff runningTurnRate/t^2 past topSpeed;
		// clamp >=0.  angularVelocity = turnDemand * turnRate, ZEROED in the death/
		// limbo/airborne leg states (1/2/3) or when the death-anim latch is set.
		// Falls back to the bring-up constant if the model supplied no rates.
		Scalar authTurnRate = kDriveTurnRate;
		if (walkingTurnRate > 0.0f)
		{
			const Scalar spd = (Scalar)sqrtf(worldLinearVelocity.x * worldLinearVelocity.x
										   + worldLinearVelocity.z * worldLinearVelocity.z);
			authTurnRate = walkingTurnRate;						// base (walk / idle / turn-in-place)
			if (spd >= reverseSpeedMax)							// 0x538 low gate
			{
				const Scalar den = reverseStrideLength - walkStrideLength;	// topSpeed - walkStride
				const Scalar t   = (den != 0.0f) ? (spd - walkStrideLength) / den : 0.0f;
				if (t <= 1.0f)
					authTurnRate = walkingTurnRate + (runningTurnRate - walkingTurnRate) * t;	// walk->run
				else
					authTurnRate = runningTurnRate / (t * t);	// over-run falloff
			}
			if (authTurnRate < 0.0f) authTurnRate = 0.0f;
			const int ls = legAnimationState;					// 0x3b0
			if (deathAnimationLatched != 0 || ls == 1 || ls == 2 || ls == 3)
				authTurnRate = 0.0f;							// no yaw in death/limbo/airborne
		}
		if (getenv("BT_TURN_LOG"))
			DEBUG_STREAM << "[turnrate] walkTR=" << walkingTurnRate
				<< " runTR=" << runningTurnRate << " spd="
				<< (Scalar)sqrtf(worldLinearVelocity.x*worldLinearVelocity.x
							   + worldLinearVelocity.z*worldLinearVelocity.z)
				<< " -> authTurnRate=" << authTurnRate
				<< " (kDrive=" << kDriveTurnRate << ")\n" << std::flush;

		// --- turn: integrate heading, write it onto the body orientation -----
		gDriveHeading += turn * authTurnRate * dt;		// scalar mirror (drive log + fallback)
		if (s_realControls)
		{
			// AUTHENTIC ORIENTATION INTEGRATION (the IntegrateMotion-tail form):
			// compose the yaw RATE into the pose quaternion via the engine
			// integrate op (Quaternion::Add == FUN_00409f58) -- the same op the
			// binary's motion tail uses -- instead of rebuilding the orientation
			// from a scalar heading.  The rate SCALE is now the AUTHENTIC per-mech
			// turn rate (authTurnRate above) -- lerp(walkingTurnRate, runningTurnRate)
			// by ground speed, master-perf 0x4aa3d3 [T1].
			Vector3D	angStep(0.0f, turn * authTurnRate * dt, 0.0f);
			Quaternion	prevPose = localOrigin.angularPosition;
			localOrigin.angularPosition.Add(prevPose, angStep);
		}
		else
		{
			localOrigin.angularPosition = EulerAngles(0.0f, gDriveHeading, 0.0f);
		}
		localToWorld = localOrigin;					// build the world matrix

		// --- forward step: the mech faces local -Z (gun ports / eyepoint are at
		//     -Z; see btl4vid.cpp).  Take the matrix Z basis in world and negate
		//     to get the facing direction; the ACTUAL forward distance this frame
		//     is the animation's root translation (see the Animate block below) --
		//     P3 STEP 1: locomotion is now ANIMATION-DRIVEN, not a procedural slide,
		//     so the body advances exactly as far as the gait clip's feet step
		//     (feet plant, no skating).  The old `kDriveMaxSpeed * throttle * dt`
		//     slide is removed; `adv` from AnimationInstance::Animate replaces it.
		UnitVector zAxis;
		localToWorld.GetFromAxis(Z_Axis, &zAxis);
		const float fx = -zAxis.x;
		const float fz = -zAxis.z;

		// --- P3 STEP 2: GAIT SELECTION by throttle.  Pick the body clip from throttle
		//     magnitude -- |throttle| >= kRunThrottle -> RUN (blhrrl), else WALK (blhwwl);
		//     each moves at ITS clip's authentic root-translation speed (STEP 1).  Throttle
		//     SIGN gives direction: negative -> reverse (back up).  (The blh set has no
		//     dedicated reverse CYCLE clip -- stand/walk/run + transitions only -- so reverse
		//     is a bring-up: it plays the selected forward clip and drives the body backward.)
		//     SetAnimation only on a gait CHANGE (re-binding every frame would reset the phase).
		//
		// ⚠ GAIT MUST TICK EVERY FRAME (task #15 fix): this block was gated on
		// `throttle != 0`, so releasing the key stopped CALLING the state machine --
		// the skeleton froze at the last written pose and the authentic walk->
		// stop->stand transition chain (BodyClipFinished states 8/9, the wsl/wsr
		// clips, which END in the standing pose) never got the chance to play.
		// The SM handles idle natively: bodyTargetSpeed=0 drives walk->stop->stand
		// and the Standing case returns adv=0 (no motion).  Only the LEGACY inline
		// clip-select path (BT_GAIT_SM=0) still needs the throttle gate (it would
		// loop the walk clip forever at idle).
		{
			static const int s_gaitTicksAlways = BTEnvOn("BT_GAIT_SM", 1);
			if (s_gaitTicksAlways || throttle != 0.0f)
			{
			const int   wantGait = ((throttle < 0.0f ? -throttle : throttle) >= kRunThrottle) ? 1 : 0;
			const char *suffix   = wantGait ? "rrl" : "wwl";

			// ⭐ P3 STEP-7 CUTOVER (env BT_GAIT_CUTOVER; default OFF -> STEP 1-2 path).
			// Drive locomotion via the reconstructed two-channel gait (the real
			// SequenceController bodyAnimation), replacing the STEP-1/2 free-standing
			// AnimationInstance stand-in.  bodyAnimation.Advance runs the reconstructed
			// controller: it animates the skeleton joints AND returns the clip's
			// per-frame root-translation forward step (applied to localOrigin).
			static const int s_gaitCutover = BTEnvOn("BT_GAIT_CUTOVER", 1);	// default ON (=0 to disable)
			if (s_gaitCutover)
			{
				static const int s_gaitSM = BTEnvOn("BT_GAIT_SM", 1);		// default ON (=0 to disable)
				Scalar adv;
				Scalar legAdv = 0.0f;		// leg-channel distance (drives the LOCAL mech)
				if (s_gaitSM)
				{
					// STEP 2: drive the REAL gait STATE MACHINE (Mech::AdvanceBodyAnimation,
					// mech2.cpp) instead of the inline clip-select.  It re-syncs bodyAnimation
					// State from bodyStateAlarm, slews bodyCycleSpeed toward bodyTargetSpeed with
					// the LoadLocomotionClips speed caps, and Advances the bound clip.  Feed the
					// inputs the controls mapper normally would (it is bypassed in bring-up) and
					// force the walk-cycle state 6 (case 6/7 -> slew to walkStrideLength + Advance
					// animationClips[6]=wwr).  Requires BT_GAIT_CUTOVER too (loads the clips).
					globalTimeScale  = 1.0f;
					idleStrideScale  = 1.0f;
					// forwardCycleRate: read from the model record (+0x44) in the
					// ctor, but the Blackhawk's value decodes to ~1 u/s^2 -- a
					// 55-SECOND ramp to run speed (the mech "couldn't go forward"
					// and the clip crawled in slow motion).  Either the record
					// field is mis-offset or its units differ (per-frame?).  Until
					// the field decode is verified against the raw parser, FLOOR
					// the authentic MaxAcceleration reads directly since the task #4
					// record-layout fix (madcat: 30 u/s^2); the old floor-25 block
					// and its one-shot log are retired.
					if (!IsMechDestroyed())						// a dead mech keeps its death movementMode
						SetMovementMode(1);						// ground, non-death, non-airborne
					// reverseSpeedMax2@0x7a0 is the run-cycle bodyCycleSpeed CLAMP (AdvanceBody
					// Animation case 12/13); LoadLocomotionClips does not set it -> it reads
					// 0xCDCDCDCD (-4.3e8) and the clamp clobbers bodyCycleSpeed -> the run cycle
					// explodes.  Bring-up: clamp the run cadence to the run stride (reverseStride
					// Length, the case-12 divisor) so bodyCycleSpeed/reverseStrideLength ~ 1, like
					// walk.  (The authentic value comes from the model/LoadLocomotionClips.)
					reverseSpeedMax2 = reverseStrideLength;
					// Gait SELECTION by throttle: walk aims for walkStrideLength; run aims for
					// reverseSpeedMax (the run/reverse cap).  Exceeding walkStrideLength makes the
					// walk handler transition "up" to the run cycle (state 11 -> 12/13) via the real
					// BodyClipFinished callback -- i.e. the authentic walk->run transition.
					// REAL CONTROLS: the commanded speed comes from the reconstructed mapper's
					// speedDemand (topSpeed * throttle * scale, mode-clamped vs steering -- the
					// AUTHENTIC gait selection input; |.| because sign encodes reverse).
					// SIGNED demand (task #15): the earlier Abs() stripped the sign, so
					// the body/motion channel could never see reverse -- motion direction
					// then had to come from the raw throttle sign, flipping INSTANTLY
					// while the animation transitioned gradually (the "physics don't
					// line up with the animation" desync).  Signed, the SM decelerates
					// through stop -> reverse-entry exactly like the leg channel.
					// BT_MASTER_GAITMIRROR: the invisible mj=0 body channel becomes the
					// PEER's predictor, so it must slew toward the LAST-SENT commanded
					// speed (which the record writers stamp into bodyTargetSpeed@0x6b4 on
					// each send, mech.cpp), NOT the live mapper demand.  Leaving
					// bodyTargetSpeed at last-sent re-activates the authentic type-2
					// deadband and makes the mirror model exactly what the peer computes.
					// The master's VISIBLE motion uses the LEG channel reading live demand
					// independently, so this does not change what the local player sees.
					// (Default OFF: live overwrite preserved.)
					static const int s_gaitMirror = getenv("BT_NO_MASTER_GAITMIRROR") ? 0 : 1;	// default ON (authentic coupled)
					if (s_gaitMirror)
					{
						/* leave bodyTargetSpeed at the last-sent value */
					}
					else if (s_realControls && MappingMapper() != 0)
						bodyTargetSpeed = MappingMapper()->speedDemand;
					else
						bodyTargetSpeed = ((throttle < 0.0f) ? -1.0f : 1.0f)
							* (wantGait ? reverseSpeedMax : walkStrideLength);
					if (gCurrentGait != 6)
					{
						bodyCycleSpeed = 0.0f;					// slew up from rest
						// Enter at STANDING (task #15): both channels' case-0 handlers
						// self-arm stand->walk when the demand rises past standSpeed
						// (AdvanceBodyAnimation case 0 == raw FUN_004a5678), so the boot
						// state is the authentic idle stand -- no pre-armed walk clip
						// stuck at frame 1 while waiting for the first throttle.
						bodyStateAlarm.SetLevel(0);
						if (s_realControls)
						{
							// AUTHENTIC TWO-CHANNEL SPLIT: the LEG channel enters
							// NATURALLY from Standing (state 0): AdvanceLegAnimation's
							// case-0 sees the live mapper speedDemand > standSpeed and
							// arms stand->walk (5); the real leg finished-callback
							// (LegClipFinished == FUN_004a6928) then chains the
							// transitions.  Only hygiene here: a clean cycle-speed start.
							legCycleSpeed = 0.0f;
						}
						gCurrentGait = 6;
						DEBUG_STREAM << "[gaitSM] enter walk-6 walkStride=" << walkStrideLength
							<< " reverseSpeedMax=" << reverseSpeedMax << " target=" << bodyTargetSpeed
							<< " split=" << s_realControls << "\n" << std::flush;
					}
					if (s_realControls)
					{
						// CHANNEL ROLES (task #49, disasm-corrected [T1]): in the binary
						//   - the MASTER PERF (0x4a9b5c region) advances the LEG channel
						//     (call @0x4aa399, airborne @0x4aa388) and stores -dist/dt into
						//     localVelocity (+0x1cc) -- the LEG drives the LOCAL mech's
						//     travel AND (writing last, mj=1) the displayed pose;
						//   - IntegrateMotion (0x4ab1c8, body advance @0x4ab312) stores
						//     -dist/dt into the PROJECTED velocity (+0x2a0) -- the BODY
						//     channel is the dead-reckoning/replication PROJECTOR, whose
						//     phase drift is locally INVISIBLE.
						// The previous arrangement here (v4: display+travel from the BODY)
						// had the roles swapped: whenever the two state machines phase-split
						// (the leg-only trn pivot seed, or a demand change landing between
						// the channels' end-of-clip callbacks -> divergent transitions), the
						// out-of-phase LEG pose showed through on every frame the body
						// didn't write (Standing/wind-down) = the visible leg stutter.
						// v5 ordered body-first/leg-last believing the leg's overwrite made
						// body drift invisible -- WRONG (task #64): the leakage merely
						// inverted (the body's pose flashed through instead).  v6 (below)
						// removes the body from the skeleton entirely (mj=0).
						// task #64 RESOLUTION (user-verified): the body channel must NOT
						// write joints.  The v5 claim "body writes first, leg overwrites,
						// so body drift can never show" was FALSE -- whenever the two
						// channels phase-split (the turn-in-place entry armed only the
						// leg), the body's out-of-phase pose leaked into the displayed
						// skeleton: rhythmic leg skips + visibly reduced bob, 100% repro
						// on turn-then-walk, while every leg-channel trace read clean
						// (the leg data was fine -- the RENDERED pose wasn't the leg's).
						// mj=0 advances + projects the body channel identically (records,
						// cycle speeds, travel projection all unchanged -- same engine
						// mode the replicant leg path uses) but structurally removes the
						// second skeleton writer.  This also matches the binary's own
						// observable: "the body channel's phase drift is locally
						// INVISIBLE in the binary" (locomotion.md) -- now it is here too.
						// BT_BODY_MJ=1 restores the old double-writer for A/B.
						static const int s_bodyMj = BTEnvOn("BT_BODY_MJ", 0);
						adv    = AdvanceBodyAnimation(dt, s_bodyMj);	// channel B: replication projection (mj=0)
						mirrorBodyAdv = adv;					// stash for the send-mirror gait advance
						legAdv = AdvanceLegAnimation(dt);		// channel A: local sim -- pose + travel
					}
					else
					{
						adv = AdvanceBodyAnimation(dt, 1);		// single-channel: body does both
					}
					// [syncF] per-frame divergence probe (task #49): log every frame
					// where the two channels return different distances or either
					// state machine changed state -- pinpoints WHICH frames the
					// advSum/legSum drift and the visible pose pops come from.
					if (getenv("BT_SYNC_LOG"))
					{
						static int s_lastBS = -1, s_lastLS = -1;
						const int bs = (int)bodyStateAlarm.GetLevel();
						const int ls = (int)legStateAlarm.GetLevel();
						const float dD = (float)(adv - legAdv);
						if (bs != s_lastBS || ls != s_lastLS
							|| dD > 0.001f || dD < -0.001f)
						{
							DEBUG_STREAM << "[syncF] dt=" << dt
								<< " bs=" << bs << " ls=" << ls
								<< " adv=" << adv << " legAdv=" << legAdv
								<< " bCyc=" << bodyCycleSpeed << " lCyc=" << legCycleSpeed
								<< " bFrm=" << bodyAnimation.currentFrame
								<< " bT=" << bodyAnimation.currentTime
								<< " lFrm=" << legAnimation.currentFrame
								<< " lT=" << legAnimation.currentTime
								<< "\n" << std::flush;
							s_lastBS = bs; s_lastLS = ls;
						}
					}
					// COCKPIT BOB (task #15): the leg channel writes the clip's vertical
					// root motion into jointlocal (balltranslate) every frame, but the
					// camera's DCS chain doesn't consume animated joints -- publish the
					// baseline-relative bob for DPLEyeRenderable (L4VIDRND.cpp) to add
					// to the view.  Baseline = the first sample after the joint resolves
					// (the clip's neutral root height).
					{
						extern float gBTEyeBobY;
						extern float gBTEyeSwayX;
						static Joint *s_rootJt = 0; static int s_rootTried = 0;
						static float s_bobBase = 0.0f, s_swayBase = 0.0f; static int s_bobBased = 0;
						if (!s_rootTried) { s_rootTried = 1; s_rootJt = ResolveJoint("jointlocal"); }
						if (s_rootJt)
						{
							const Point3D rt = s_rootJt->GetTranslation();
							if (!s_bobBased) { s_bobBased = 1; s_bobBase = rt.y; s_swayBase = rt.x; }
							gBTEyeBobY  = rt.y - s_bobBase;
							gBTEyeSwayX = rt.x - s_swayBase;	// lateral weight shift (the swagger)
						}
					}
					static float s_smlog = 0.0f; s_smlog += dt;
					static double s_advSum = 0.0, s_legSum = 0.0;
					s_advSum += adv; s_legSum += legAdv;
					// BT_GAIT_TRACE=1 (task #64): log the gait state EVERY frame (not 1 Hz)
					// so a frame-level stutter at a transition is visible.
					static const int s_gtrace = getenv("BT_GAIT_TRACE") ? 1 : 0;
					if (s_smlog >= 1.0f || s_gtrace) { s_smlog = 0.0f;
						extern float gBTEyeBobY;
						static Joint *s_dbgRoot = 0; static int s_dbgTried = 0;
						if (!s_dbgTried) { s_dbgTried = 1; s_dbgRoot = ResolveJoint("jointlocal"); }
						Point3D dbgRt(0,0,0);
						if (s_dbgRoot) dbgRt = s_dbgRoot->GetTranslation();
						DEBUG_STREAM << "[sync] advSum=" << (float)s_advSum
							<< " legSum=" << (float)s_legSum
							<< " posZ=" << localOrigin.linearPosition.z
							<< " rootZ=" << dbgRt.z << " rootX=" << dbgRt.x << "\n" << std::flush;
						MechControlsMapper *s_tm = MappingMapper();		// input demands (task #64)
						DEBUG_STREAM << "[gaitSM] adv=" << adv << " legAdv=" << legAdv
							<< " cycleSpeed=" << bodyCycleSpeed << " legCycle=" << legCycleSpeed
							<< " state=" << bodyAnimationState << " legState=" << legAnimationState
							<< " kfCur=" << bodyAnimation.currentFrame
							<< " legFrm=" << legAnimation.currentFrame
							<< " spd=" << (s_tm ? s_tm->speedDemand : 0.0f)
							<< " turn=" << (s_tm ? s_tm->turnDemand : 0.0f)
							<< " bob=" << gBTEyeBobY << "\n" << std::flush; }
				}
				else
				{
					if (gCurrentGait != wantGait)
					{
						ResourceDescription::ResourceID *clip = ResolveAnimationClip("blh", suffix);
						if (clip)
						{
							// loop callback: SequenceController::Advance calls it at end-of-clip
							// to re-arm the same clip (bring-up loop; see Mech::LoopBodyClip).
							bodyAnimation.SelectSequence(*clip, (void *)&Mech::LoopBodyClip, 0, 0);
							gCurrentGait = wantGait;
							DEBUG_STREAM << "[gait] SequenceController -> blh" << suffix
								<< " id=" << (long)*clip << " (" << (wantGait ? "run" : "walk")
								<< ")" << std::endl;
						}
					}
					adv = bodyAnimation.Advance(dt, 1);			// inline clip-select path
				}
				// Motion direction comes FROM the animation: reverse clips carry
				// negative root translation, so adv is already signed.  (dir kept
				// only for the legacy non-SM path, whose forward clips are unsigned.)
				const float dir = (s_gaitCutover && BTEnvOn("BT_GAIT_SM", 1))
					? 1.0f
					: ((throttle < 0.0f) ? -1.0f : 1.0f);

				// --- AUTHENTIC world-step (Mech::IntegrateMotion tail @004ab1c8) ---
				// The 1995 mech does NOT slide the position directly; it integrates a
				// VELOCITY vector rotated by the body orientation.  VERIFIED mapping: the
				// 1995 motion transform { Point3D @0x260; Quaternion @0x26c } IS the engine
				// `localOrigin` (Origin = { linearPosition; angularPosition }, ORIGIN.h:15;
				// FUN_0040ab44 builds the matrix from BOTH halves).  So reproduce the real
				// integrator: localVelocity = {0,0,-adv/dt} (forward = local -Z), rotate it
				// into world space by the orientation via the now-backed world-step transform
				// FUN_00408744, then position += worldVelocity*dt.  Result == facing*adv, but
				// through the authentic velocity->rotate->integrate model (runs FUN_00408744
				// live).  Remaining full-IntegrateMotion work: velocity STORAGE for dead-reckon,
				// orientation integration, and the AdvanceBodyAnimation gait state machine
				// (+ LoadLocomotionClips) in place of the inline clip select above.
				const Scalar invDt = (dt > 0.0f) ? (1.0f / dt) : 0.0f;
				// VELOCITY SMOOTHING (task #15, the binary's IntegrateMotion model):
				// SequenceController::Advance returns distance only when keyframes
				// are CROSSED -- partial frames interpolate the joints but add zero
				// distance (binary-faithful lumping).  Integrating adv/dt directly
				// stutters the ground speed while the legs sweep smoothly (feet
				// appear to glide/skate).  The original smoothed this through its
				// persistent-velocity integration; reproduce it: velocity = the
				// accumulated distance over the elapsed time since the last
				// keyframe contribution, HELD across zero-distance frames, hard
				// zero when Standing (and after 0.3s of silence, e.g. paused clip).
				// FOOT-PLANT BY CONSTRUCTION (v5 -- task #49, disasm-corrected):
				// display AND travel BOTH come from the LEG channel (the binary's
				// master perf advances the leg and writes -dist/dt into
				// localVelocity @+0x1cc; the leg writes the pose last).  The v4
				// reading ("raw FUN_004ab430:15076 takes the travel from the body
				// advance") was WRONG -- 0x4ab312's IntegrateMotion stores its
				// -dist/dt into the PROJECTED velocity (+0x2a0), the dead-reckoning
				// feed, not local travel.  v3 (travel=leg, display=body) foot-slipped
				// because display and travel were DIFFERENT channels; v4 unified on
				// the body, which planted feet but let the out-of-phase LEG pose show
				// through wherever the body didn't write (the visible stutter).  v5
				// unifies on the leg exactly as the binary does: display == travel by
				// construction, live-demand channel authoritative, body drift local-
				// invisible.  (Knockdown still staggers BOTH channels, so a hard
				// impact freezes travel as before.)
				const Scalar travelAdv = s_realControls ? legAdv : adv;
				const Scalar localAdv = travelAdv * dir;
				// MASTER-SLIDE probe (BT_SLIDE): does the MASTER itself advance while its
				// legs are in STAND (legState 0)?  If yes -> authentic momentum coast, the
				// peer correctly mirrors it.  If the master travelAdv reaches 0 exactly
				// when legState reaches 0 -> the master stops clean and the peer's stand-
				// slide is a peer leg-SM-too-fast desync.  Logs the decel profile.
				if (getenv("BT_SLIDE"))
				{
					const int mls = (int)legStateAlarm.GetLevel();
					const float ta = (float)(travelAdv < 0 ? -travelAdv : travelAdv);
					if (ta > 0.005f)		// master still moving
						DEBUG_STREAM << "[mslide] travelAdv=" << travelAdv
							<< " legState=" << mls
							<< " legCycle=" << legCycleSpeed
							<< " thr=" << throttle << "\n" << std::flush;
				}
				linearSpeed = (localAdv < 0.0f ? -localAdv : localAdv) * invDt;	// forward ground speed -> LinearSpeed gauge
				Vector3D localVel(0.0f, 0.0f, -localAdv * invDt);	// exact frame distance as velocity
				Matrix34 orient;									// rotation from the heading (set @ line ~626)
				Matrix34::FromQuaternion(&orient, &localOrigin.angularPosition);
				Vector3D worldVel;
				FUN_00408744(&worldVel, (Scalar *)&localVel, &orient);	// worldVel = orient * localVel
				localOrigin.linearPosition.x += worldVel.x * dt;
				localOrigin.linearPosition.y += worldVel.y * dt;
				localOrigin.linearPosition.z += worldVel.z * dt;
				localToWorld = localOrigin;
				gBodyAnimLog += dt;
				if (gBodyAnimLog >= 1.0f)
				{
					gBodyAnimLog = 0.0f;
					DEBUG_STREAM << "[gait] adv=" << travelAdv << " (proj=" << adv
						<< ") pos=("
						<< localOrigin.linearPosition.x << ", "
						<< localOrigin.linearPosition.z << ")" << std::endl;
				}
			}
			else
			{
			if (gBodyAnim == 0 || gCurrentGait != wantGait)		// first time, or gait changed
			{
				ResourceDescription::ResourceID *clip = ResolveAnimationClip("blh", suffix);
				if (clip)
				{
					if (gBodyAnim == 0) gBodyAnim = new AnimationInstance(this);
					// LOOP callback (not NULL) -- Animate invokes it at clip end
					// (JMOVER ~1592); NULL there crashes on a null member ptr.
					gBodyAnim->SetAnimation(
						*clip,
						reinterpret_cast<JointedMover::AnimationCallback>(&Mech::OnBodyAnimFinished));
					gCurrentGait   = wantGait;
					gBodyAnimReady = 1;
					DEBUG_STREAM << "[anim] gait -> blh" << suffix << " id=" << (long)*clip
						<< " (" << (wantGait ? "run" : "walk") << ")" << std::endl;
				}
				else
				{
					gBodyAnimReady = -1;
					DEBUG_STREAM << "[anim] blh" << suffix << " unresolved -- not moving\n" << std::flush;
				}
			}

			if (gBodyAnimReady == 1 && gBodyAnim != 0)
			{
				// Animate advances the clip, writes the joint DCS (legs cycle) AND returns
				// the clip's per-frame root-translation distance (the [RootTranslation]
				// integral -- CLAUDE.md section 7).  Drive the body by exactly that distance
				// so travel == foot stride (feet plant); negative throttle backs it up.
				Scalar adv = gBodyAnim->Animate(dt, True);
				// Motion direction comes FROM the animation: reverse clips carry
				// negative root translation, so adv is already signed.  (dir kept
				// only for the legacy non-SM path, whose forward clips are unsigned.)
				const float dir = (s_gaitCutover && BTEnvOn("BT_GAIT_SM", 1))
					? 1.0f
					: ((throttle < 0.0f) ? -1.0f : 1.0f);
				localOrigin.linearPosition.x += fx * adv * dir;
				localOrigin.linearPosition.z += fz * adv * dir;
				linearSpeed = (dt > 0.0f) ? ((adv < 0.0f ? -adv : adv) / dt) : 0.0f;	// LinearSpeed gauge
				localToWorld = localOrigin;					// rebuild with the new position

				gBodyAnimLog += dt;
				if (gBodyAnimLog >= 1.0f)
				{
					gBodyAnimLog = 0.0f;
					DEBUG_STREAM << "[anim] " << (wantGait ? "run" : "walk")
						<< (dir < 0 ? " (reverse)" : "") << " adv=" << adv
						<< " loops=" << gBodyAnimLoops
						<< "  pos=(" << localOrigin.linearPosition.x << ", "
						<< localOrigin.linearPosition.z << ")" << std::endl;
				}
			}
		}		// close the BT_GAIT_CUTOVER else (STEP 1-2 path)
			}	// close the gait-ticks-always gate
		}		// close the every-frame gait scope (task #15 fix)

		// DIAG (turn-hitch hunt): flag any frame that took abnormally long -- the
		// hang shows up as a big dt on the NEXT tick.  Correlate with [loadobj].
		if (dt > 0.2f)
			DEBUG_STREAM << "[spike] dt=" << dt
				<< " turn=" << turn << " thr=" << throttle << "\n" << std::flush;

		// --- 1 Hz world-position log while moving (proves the body walks) -----
		gDriveLogAccum += dt;
		if ((throttle != 0.0f || turn != 0.0f) && gDriveLogAccum >= 1.0f)
		{
			gDriveLogAccum = 0.0f;
			DEBUG_STREAM << "[drive] pos=("
				<< localOrigin.linearPosition.x << ", "
				<< localOrigin.linearPosition.y << ", "
				<< localOrigin.linearPosition.z << ")  hdg=" << gDriveHeading
				<< "  thr=" << throttle << "  turn=" << turn
				<< "  dt=" << dt << "\n" << std::flush;
		}

		// --- COLLISION (engine Mover flow; gated BT_COLLISION) ------------------
		// After the drive moved localOrigin, resolve penetration against the world's
		// collision solids (terrain buttes/hills + buildings) with the engine's OWN
		// pipeline (MOVER.cpp): reposition the collision volume to the new transform,
		// gather overlapping solids, and let ProcessCollisionList push localOrigin back
		// out of anything it entered (it writes localOrigin.linearPosition + reads
		// worldLinearVelocity).  This is what stops the mech phasing through objects and
		// sinking through the ground.  The dead Mech::Simulate used a 1995 heightfield
		// terrain-follow (FUN_0040e5f0); the 2007 engine models terrain AS collision
		// solids, so this single path covers both terrain-follow AND object collision.
		// Guarded on a real collision volume -> no-op for volume-less mechs.
		// --- VELOCITY STORAGE (the MP update writer; ALWAYS maintained) ---------
		// The engine Mover::WriteUpdateRecord (MOVER.cpp:740) publishes
		// localVelocity + worldLinearVelocity in every network update record --
		// the data remote pods dead-reckon from.  Keep them live each frame:
		// world velocity from the frame's position delta; local velocity =
		// forward speed on local -Z (the mech's facing axis) + the yaw rate.
		{
			const Scalar invDtV = (dt > 0.0001f) ? (1.0f / dt) : 0.0f;
			worldLinearVelocity.x = (localOrigin.linearPosition.x - collisionOldPos.x) * invDtV;
			worldLinearVelocity.y = (localOrigin.linearPosition.y - collisionOldPos.y) * invDtV;
			worldLinearVelocity.z = (localOrigin.linearPosition.z - collisionOldPos.z) * invDtV;
			Scalar fwdSpeed = (Scalar)sqrtf(
				worldLinearVelocity.x * worldLinearVelocity.x +
				worldLinearVelocity.z * worldLinearVelocity.z);
			localVelocity.linearMotion  = Vector3D(0.0f, worldLinearVelocity.y, -fwdSpeed);
			localVelocity.angularMotion = Vector3D(0.0f, turn * authTurnRate, 0.0f);

			// MASTER twin of [replmov]: the velocity this mech PUBLISHES vs its own
			// leg state -- compare the master ramp to the replicant response.
			if (getenv("BT_REPL_MOV") && (fwdSpeed > 0.01f || throttle != 0.0f))
				DEBUG_STREAM << "[mastervel] fwdSpeed=" << fwdSpeed
					<< " thr=" << throttle
					<< " legState=" << (int)legStateAlarm.GetLevel()
					<< " pos=(" << localOrigin.linearPosition.x << ","
					<< localOrigin.linearPosition.z << ")"
					<< " dt=" << dt << "\n" << std::flush;

			// CRASH motion suppression (spec: the binary zeroes localVelocity while
			// crashed).  While the stagger clip (legState 0x20) plays, freeze the
			// velocity so a knocked-down mech does not creep back into the obstacle and
			// re-detect an impact every frame (impactVel = localVelocity below).  Then
			// run the post-recovery refractory so it can't instantly re-knockdown.
			if ((int)legStateAlarm.GetLevel() == 0x20)
			{
				worldLinearVelocity        = Vector3D(0.0f, 0.0f, 0.0f);
				localVelocity.linearMotion = Vector3D(0.0f, 0.0f, 0.0f);
			}
		}

		if (GroundReal())
		{
			// AUTHENTIC GROUND MODEL (task #15, ground-model-decode): the binary's
			// probe/snap/response block (FUN_004a9b5c @4aa630-4aab5f) -- see the
			// AuthenticGroundAndCollide banner below.  NO gravity, NO floor clamp,
			// NO step guard in this path; a probe MISS (h==-1) does NOTHING (Y
			// holds -- byte-faithful, also correct over authentic no-solid content
			// like the rav canyon backdrops / arena detail pieces).
			AuthenticGroundAndCollide(dt, collisionOldPos);
		}
		else if (BTEnvOn("BT_COLLISION", 1) && GetCollisionVolumeCount() > 0)
		{
			// GRAVITY / terrain-settle: the collision below only pushes the mech OUT of
			// solids -- there is no downward force, so a floating mech never comes down.
			// Press it down each frame; the collision then holds it at the terrain surface
			// (gravity down + collision up = equilibrium ON the ground).  A FLOOR CLAMP at
			// the spawn/ground Y is the safety net: on a map with NO terrain solid the mech
			// rests at ground level instead of falling forever.  On real terrain (modeled as
			// solids) the collision catches it above the floor -> it follows hills/valleys.
			// kGravityRate world-units/sec (tunable via BT_GRAVITY).
			static Scalar s_groundY = 0.0f; static int s_groundYSet = 0;
			static const Scalar kGravityRate =
				getenv("BT_GRAVITY") ? (Scalar)atof(getenv("BT_GRAVITY")) : 20.0f;
			if (!s_groundYSet) { s_groundY = localOrigin.linearPosition.y; s_groundYSet = 1; }
			localOrigin.linearPosition.y -= kGravityRate * dt;			// fall

			const Scalar invDtC = (dt > 0.0f) ? (1.0f / dt) : 0.0f;
			worldLinearVelocity.x = (localOrigin.linearPosition.x - collisionOldPos.x) * invDtC;
			worldLinearVelocity.y = (localOrigin.linearPosition.y - collisionOldPos.y) * invDtC;
			worldLinearVelocity.z = (localOrigin.linearPosition.z - collisionOldPos.z) * invDtC;
			MoveCollisionVolume();							// reposition the volume to localToWorld
			BoxedSolidCollisionList *cols = GetCurrentCollisions();
			if (cols)
			{
				Point3D before = localOrigin.linearPosition;
				frameEntryWorldVelocity = worldLinearVelocity;	// collision-damage guard (see mech.hpp)
				Damage collisionDamage;						// filled by ProcessCollisionList (unused)
				ProcessCollisionList(cols, dt, collisionOldPos, &collisionDamage);	// pushes localOrigin out
				Scalar dx = localOrigin.linearPosition.x - before.x;
				Scalar dz = localOrigin.linearPosition.z - before.z;
				if (dx*dx + dz*dz > 0.01f)					// log only real object/wall pushes (not ground settle)
					DEBUG_STREAM << "[collide] pushed out by (" << dx << ", "
						<< (localOrigin.linearPosition.y - before.y) << ", " << dz
						<< ") pos=(" << localOrigin.linearPosition.x << ", "
						<< localOrigin.linearPosition.z << ")" << std::endl;
			}
			// Floor clamp: never sink below the base ground (safety on solid-less maps).
			if (localOrigin.linearPosition.y < s_groundY)
				localOrigin.linearPosition.y = s_groundY;
			localToWorld = localOrigin;						// rebuild after gravity/collision/clamp
		}

		// LOD EYEPOINT (authentic): feed the renderer the MECH's eyepoint as the
		// LOD-distance reference.  The pod's eyepoint sat ON the mech (cockpit),
		// so turning in place never changed an object's LOD distance; our chase
		// camera ORBITS the mech +-40u as it turns, which swept objects near
		// their 0x2046 band edges in and out = "scenery blinks with viewing
		// angle" / floor flicker at the arena fringe.
		{
			extern void BTSetLodEye(float, float, float);
			BTSetLodEye((float)localOrigin.linearPosition.x,
			            (float)localOrigin.linearPosition.y + 7.0f,	// cockpit height
			            (float)localOrigin.linearPosition.z);
		}

		// SHADOW TILT (task #20 + #15): pose 'jointshadow' (the *_tshd proxy's
		// terrain-angle channel; SKL "apply terrain angle to pitch and roll") to the
		// LOCAL ground slope each frame.  The ground decode (#15) is now live, so
		// instead of the old flat-up placeholder we sample the collision surface at
		// the mech's XZ and two world-axis offsets, build the surface normal from the
		// height gradient, and rotate it into the mech's local frame (upright, yaw =
		// gDriveHeading).  On a slope the shadow then LIES ON the ground; the old flat
		// quad was buried in the hillside and Z-culled -> "the shadow disappears on
		// elevation" (and the missing ground-contact cue made the feet read as sunk,
		// though the origin is measurably ON the surface).  A probe miss (mech at the
		// edge of its collision node) falls back to flat.  Gated BT_SHADOW_TILT (=0
		// restores the flat placeholder for A/B).
		{
			Vector3D shadowNormal(0.0f, 1.0f, 0.0f);
			static int s_shTilt = -1;
			if (s_shTilt < 0)
			{
				const char *sv = getenv("BT_SHADOW_TILT");
				s_shTilt = (sv == 0 || sv[0] != '0') ? 1 : 0;
			}
			BoundingBoxTreeNode *gnode = (GetCollisionVolumeCount() > 0 && collisionTemplate != 0)
				? GetMoverCollisionRoot() : 0;
			if (s_shTilt && gnode != 0)
			{
				const Scalar D     = 12.0f;						// probe offset (small -> stays in the node)
				const Scalar baseY = localOrigin.linearPosition.y + collisionTemplate->minY + 50.0f;
				const Scalar cx    = localOrigin.linearPosition.x;
				const Scalar cz    = localOrigin.linearPosition.z;
				Scalar hC = -1.0f, hX = -1.0f, hZ = -1.0f;
				Point3D qC(cx, baseY, cz), qX(cx + D, baseY, cz), qZ(cx, baseY, cz + D);
				BoundingBox *bC = gnode->FindBoundingBoxUnder(qC, &hC);
				BoundingBox *bX = gnode->FindBoundingBoxUnder(qX, &hX);
				BoundingBox *bZ = gnode->FindBoundingBoxUnder(qZ, &hZ);
				if (bC != 0 && bX != 0 && bZ != 0)
				{
					// surfaceY = probeY - downDistance; then add the render-only
					// VISUAL lift at each probe (task #49b): the quad is drawn on
					// the VISIBLE terrain (btvisgnd conform), which runs 0..~2u
					// above the collision solid by DIFFERENT amounts across a
					// slope -- the VISUAL gradient, not the collision gradient,
					// is the surface the quad must hug.  Lift 0 (no data/gated
					// off) degrades to the collision gradient.
					extern float BTVisualGroundLift(float x, float y, float z);
					Scalar yC = baseY - hC, yX = baseY - hX, yZ = baseY - hZ;
					yC += BTVisualGroundLift((float)cx,     (float)yC, (float)cz);
					yX += BTVisualGroundLift((float)(cx+D), (float)yX, (float)cz);
					yZ += BTVisualGroundLift((float)cx,     (float)yZ, (float)(cz+D));
					Vector3D wn(-(yX - yC) / D, 1.0f, -(yZ - yC) / D);
					Scalar len = (Scalar)sqrtf(wn.x * wn.x + wn.y * wn.y + wn.z * wn.z);
					if (len > 1e-6f) { wn.x /= len; wn.y /= len; wn.z /= len; }
					// world -> mech-local: rotate by -yaw about Y.  Use the TRUE
					// heading from localToWorld (engine convention MATRIX.cpp:196:
					// Z basis = (sin y, 0, cos y) -> yaw = atan2(z.x, z.z)) -- NOT
					// the gDriveHeading scalar mirror, which is seeded to 0 and
					// drifts from the real pose (the task-#48 goto-steering bug
					// class; a wrong yaw here swings the tilt as the mech turns).
					UnitVector zAxisS;
					localToWorld.GetFromAxis(Z_Axis, &zAxisS);
					const Scalar shYaw = (Scalar)atan2f((float)zAxisS.x, (float)zAxisS.z);
					const Scalar cth = (Scalar)cosf((float)shYaw);
					const Scalar sth = (Scalar)sinf((float)shYaw);
					shadowNormal.x = wn.x * cth - wn.z * sth;
					shadowNormal.y = wn.y;
					shadowNormal.z = wn.x * sth + wn.z * cth;
					if (getenv("BT_SHADOW_LOG"))
					{
						static float s_shLog = 0.0f; s_shLog += dt;
						if (s_shLog >= 1.0f) { s_shLog = 0.0f;
							DEBUG_STREAM << "[shtilt] wn=(" << wn.x << "," << wn.y << "," << wn.z
								<< ") local=(" << shadowNormal.x << "," << shadowNormal.y
								<< "," << shadowNormal.z << ") yaw=" << shYaw << "\n" << std::flush; }
					}
				}
			}
			UpdateShadowJoint(shadowNormal);
		}

		// --- VISUAL-GROUND CONFORM (PORT ADDITION, presentation only; btvisgnd.cpp;
		//     gate BT_VISUAL_GROUND, default ON) ---------------------------------
		// The sim rides the coarse 1995 collision solids (authentic snap: origin.y
		// = solid surfaceY); the VISUAL terrain runs 0..~2.1u above them on slopes,
		// clipping the feet.  Invisible in 1995 (cockpit-only view); visible in our
		// external camera.  Lift the RENDER matrix to the visible surface sampled
		// from the actual terrain triangles under the mech.  localOrigin (collision,
		// aim, damage, dead-reckoning) is NEVER touched; next frame's drive rebuilds
		// localToWorld from localOrigin, so the lift is per-frame and render-only.
		{
			extern float BTVisualGroundLift(float x, float y, float z);
			const float vLift = BTVisualGroundLift(
				localOrigin.linearPosition.x,
				localOrigin.linearPosition.y,
				localOrigin.linearPosition.z);
			if (vLift != 0.0f)
			{
				localToWorld(3, 1) = localToWorld(3, 1) + vLift;
				if (GroundLog())
				{
					static float s_vgAccum = 0.0f; s_vgAccum += dt;
					if (s_vgAccum >= 1.0f)
					{
						s_vgAccum = 0.0f;
						DEBUG_STREAM << "[visgnd] lift=" << vLift
							<< " at (" << localOrigin.linearPosition.x << ", "
							<< localOrigin.linearPosition.y << ", "
							<< localOrigin.linearPosition.z << ")" << std::endl;
					}
				}
			}
		}

		// --- MASTER UPDATE EMISSION (P6 multiplayer; the VTV pattern, RP/VTV.cpp:
		//     840-861) --------------------------------------------------------
		// Predict where remote pods believe this mech is (run OUR OWN dead
		// reckoner: projectedOrigin advances from the last-broadcast state) and
		// ForceUpdate() when the truth diverges -- position error, orientation
		// deviation, or the 2-second heartbeat.  ForceUpdate marks the update-
		// model bits; the engine update pump then WriteUpdateRecord()s (our
		// localVelocity/worldLinearVelocity are maintained every frame above)
		// and the InterestManager broadcasts to all remote hosts.
		if (GetInstance() == MasterInstance && deadReckoner != 0
			&& application != 0
			&& application->GetApplicationState() == Application::RunningMission)
		{
			// (RunningMission gate: broadcasting motion during mission CREATION kept
			// the PEER's event queue busy, so its CreatingMission->LoadingMission
			// quiet-timeout never fired -- the pods deadlocked pre-launch.  The real
			// system never moves before launch, so the gate is faithful.)
			// SEND-MIRROR advance: model where the PEER believes this mech is, so the
			// deadband fires exactly when the peer would drift.  Authentic (binary
			// FUN_004a9b5c @0x4aab9c): advance projectedOrigin by IntegrateMotion(mj=0)
			// -- the SAME gait predictor the peer runs -- fed the LAST-SENT commanded
			// speed.  Our default mirror uses the engine constant-velocity deadReckoner
			// (AcceleratedDeadReckoner), which does NOT model a gait-driven peer -> the
			// deadband under-sends and the coupled peer drifts (measured).  With
			// BT_MASTER_GAITMIRROR: increment projectedOrigin.linear by the mj=0 body
			// channel's travel (mirrorBodyAdv, = the peer's predicted stride) rotated by
			// the mirror heading, and integrate the heading by the last-sent angular
			// velocity.  Re-seeded to localOrigin on each send by the record writers'
			// projectedOrigin re-base, so this is incremental-from-last-send, exactly the
			// peer's IntegrateMotion.  (Default OFF: constant-velocity deadReckoner.)
			static const int s_gaitMirrorSend = getenv("BT_NO_MASTER_GAITMIRROR") ? 0 : 1;	// default ON
			if (s_gaitMirrorSend)
			{
				Vector3D mAng;
				mAng.Multiply(updateVelocity.angularMotion, dt);	// last-sent yaw rate
				ReconQuatIntegrate(&projectedOrigin.angularPosition,
								   &projectedOrigin.angularPosition, &mAng);
				projectedOrigin.angularPosition.Normalize();
				if (mirrorBodyAdv != 0.0f)
				{
					Vector3D mLocal(0.0f, 0.0f, -mirrorBodyAdv);	// forward = -Z, frame distance
					Matrix34 mOrient;
					Matrix34::FromQuaternion(&mOrient, &projectedOrigin.angularPosition);
					Vector3D mWorld;
					FUN_00408744(&mWorld, (Scalar *)&mLocal, &mOrient);
					projectedOrigin.linearPosition.x += mWorld.x;
					projectedOrigin.linearPosition.y += mWorld.y;
					projectedOrigin.linearPosition.z += mWorld.z;
				}
			}
			else
			{
				(this->*deadReckoner)();
			}
			Vector3D error;
			error.Subtract(
				projectedOrigin.linearPosition,
				localOrigin.linearPosition);
			// MIRROR-DIVERGENCE probe (BT_MIRDIV, 1s max): how far the master's
			// send-mirror (projectedOrigin, body-channel gait) drifts from the true
			// localOrigin (leg-channel gait) BEFORE a send fires.  This is the source
			// of the peer's residual snap -- quantifies the leg-vs-body channel shape
			// mismatch.  posDb (~0.55u) is the send threshold; anything the mirror
			// over/under-shoots beyond it is the channel divergence.
			if (getenv("BT_MIRDIV"))
			{
				static float s_mdAcc = 0.0f, s_mdMax = 0.0f, s_mdAdvB = 0.0f, s_mdAdvL = 0.0f;
				const float e = (float)error.Length();
				if (e > s_mdMax) s_mdMax = e;
				s_mdAdvB += (float)(mirrorBodyAdv < 0 ? -mirrorBodyAdv : mirrorBodyAdv);
				s_mdAcc += dt;
				if (s_mdAcc >= 1.0f)
				{
					DEBUG_STREAM << "[mirdiv] maxErr=" << s_mdMax
						<< " bodyTravel/s=" << s_mdAdvB
						<< " bodyTgtSpd=" << bodyTargetSpeed << "\n" << std::flush;
					s_mdAcc = 0.0f; s_mdMax = 0.0f; s_mdAdvB = 0.0f; s_mdAdvL = 0.0f;
				}
			}
			Quaternion angular_deviation;
			angular_deviation.Subtract(
				projectedOrigin.angularPosition,
				localOrigin.angularPosition);
			// Position deadband (binary @0x4aad35: |pos - lastSent|^2 vs the
			// model's UpdatePositionDiffrence @0x768) + the 2s heartbeat.
			// Guard against an unstreamed/zero deadband with the old stand-in.
			{
				Scalar posDb = (updatePositionDeadband > 0.0f)
					? updatePositionDeadband : 0.04f;
				// LINEAR "came-to-rest" trigger -- the symmetric analog of the
				// angular (live yaw-rate==0 && replicated yaw-rate!=0) resync
				// trigger in the type-4 gate below.  The position deadband alone
				// does NOT catch a stop: once the master halts, its localOrigin is
				// pinned and its OWN dead-reckon projection is re-based to it each
				// frame, so error->0 and no pose record fires -- leaving the peer
				// holding the last WALKING velocity (updateVelocity), which the
				// replicant keeps dead-reckoning + animating as a phantom walk
				// until the 2s heartbeat.  Fire a pose record the frame the live
				// horizontal speed collapses while the last-SENT speed was still
				// non-zero, so the replicant gets velocity=0 immediately.
				const Scalar liveSpdSq =
					(Scalar)(localVelocity.linearMotion.x * localVelocity.linearMotion.x
					+ localVelocity.linearMotion.z * localVelocity.linearMotion.z);
				const Scalar sentSpdSq =
					(Scalar)(updateVelocity.linearMotion.x * updateVelocity.linearMotion.x
					+ updateVelocity.linearMotion.z * updateVelocity.linearMotion.z);
				const Logical cameToRest = (liveSpdSq < 0.01f && sentSpdSq > 0.01f);
				// DENSE MOTION SEND (replicant-smoothness): the position deadband
				// fires IRREGULARLY (only after ~0.55u of drift), but the replicant's
				// dead-reckoner predicts the next record one PAST-interval ahead and
				// lerps its localOrigin toward that fixed horizon target -- so when the
				// deadband cadence != the predicted cadence, the peer decelerates
				// toward a stale target then lurches when a record finally lands
				// (measured: dead-reckoned ground speed surging 5<->27 while the master
				// walks a steady 6.13).  Sending a pose record EVERY frame while moving
				// makes the cadence regular + dense (records ~1 frame apart), so the
				// horizon prediction matches and the lerp tracks smoothly.  On a LAN
				// this is a handful of small packets/frame -- negligible.
				// Dense while moving: with the gait-driven peer, the master's velocity
				// mirror no longer models the peer, so the deadband under-sends and the
				// peer diverges (measured 11u snaps).  Dense records keep the correction
				// offset tiny; the peer absorbs it via the authentic pose-sync decay.
				static const int s_denseTx = getenv("BT_NO_DENSE_TX") ? 0 : 1;
				const Logical moving = (liveSpdSq > 0.25f);
				if (
					error.LengthSquared() > posDb
					|| lastPerformance - lastUpdate > 2.0f
					|| cameToRest
					|| (s_denseTx && moving)
				)
				{
					ForceUpdate();					// type 0: pose record
					// RE-SEED the gait mirror to the just-sent pose: the peer will now
					// extrapolate from THIS localOrigin, so the mirror must too.  (The
					// type-0 writer only re-bases projectedOrigin in the sim-state-2
					// transition, not the normal walk case, so do it here.)
					if (s_gaitMirrorSend)
					{
						projectedOrigin.linearPosition  = localOrigin.linearPosition;
						projectedOrigin.angularPosition = localOrigin.angularPosition;
					}
					if (getenv("BT_WIRE"))
						DEBUG_STREAM << "[tx0] errSq=" << error.LengthSquared()
							<< " posDb=" << posDb
							<< " hb=" << (float)(lastPerformance - lastUpdate)
							<< (cameToRest ? " REST" : "")
							<< " lvLin=(" << localVelocity.linearMotion.x << ","
							<< localVelocity.linearMotion.z << ")\n" << std::flush;
				}
			}
			// ORIENTATION rides ONLY the type-4 resync record (the authentic
			// case-0 writer/reader save-restore strips it from the pose
			// record).  Binary triggers (@0x4aac2b / @0x4aac6c):
			//   |localOrigin.angular.y - projectedOrigin.angular.y| > @0x770
			//   |localVelocity.angular.y - projectedVelocity.angular.y| > @0x76c
			//   or (live yaw-rate == 0 && replicated yaw-rate != 0)
			// -- the quaternion Y component and the yaw rate, against the
			// model's UpdateTurnDegreeDiffrence (deg->rad) and
			// UpdateTurnVelocityDiffrence.  [T1 expressions; zero-deadband
			// guard falls back to the old quat-w stand-in]
			{
				// SCALAR PEER-YAW MIRROR (replaces the quaternion projectedOrigin mirror):
				// the peer renders yaw ~= angMirrorYaw + angMirrorRate*(now - angMirrorTime)
				// (re-based by the type-4 writer on every send, mech.cpp case 4).  The old
				// quaternion mirror was ALSO recomputed each frame by the master's own
				// reckoner from lastUpdate timing it does not control, so it drifted in
				// slow pi-waves and FLOODED angle resyncs in bursts (measured maxAng~=pi)
				// -- the walk+turn record churn.  Scalars: wrap-safe, self-timed, exact
				// for the constant-rate spin the peer's exact integrator reproduces.
				Scalar angDb = (updateTurnAngleDeadband    > 0.0f) ? updateTurnAngleDeadband    : -1.0f;
				Scalar velDb = (updateTurnVelocityDeadband > 0.0f) ? updateTurnVelocityDeadband : -1.0f;
				Logical resync = False;
				int rzn = 0;	// 1=angleDrift 2=velDrift 4=cameToRestAng 8=mirrorInvalid
				Scalar angDrift = 0.0f;
				if (angMirrorValid)
				{
					YawPitchRoll _yprL;
					_yprL = localOrigin.angularPosition;
					const Scalar mirrorYaw = angMirrorYaw
						+ angMirrorRate * (Scalar)(lastPerformance - angMirrorTime);
					Scalar _dyaw = (Scalar)_yprL.yaw - mirrorYaw;
					while (_dyaw >  3.14159265f) _dyaw -= 6.28318531f;
					while (_dyaw < -3.14159265f) _dyaw += 6.28318531f;
					angDrift = (_dyaw < 0.0f) ? -_dyaw : _dyaw;
				}
				else
				{
					resync = True; rzn |= 8;				// no baseline yet -> establish one
				}
				// Compare the live yaw rate against the LAST-SENT rate (updateVelocity,
				// which the type-4 writer copies straight from localVelocity, mech.cpp:2107)
				// -- NOT projectedVelocity, which the master's dead-reckoner recomputes in a
				// different representation/sign so the scalar-.y compare always read the full
				// 2x mismatch and FLOODED a resync every frame (measured: byVel=55/55,
				// velDrift=2.618=2x the 1.309 spin rate).  The flood reset the peer's
				// dead-reckon horizon every frame, pinning its slerp at ~half rate -> the
				// "rotates slow then jumps" spin.  updateVelocity is the value the peer
				// actually extrapolates with, in the same units, so a steady spin now drifts
				// 0 (no resync) and the peer extrapolates smoothly; a real rate change still
				// diverges past velDb and fires.
				// NB: the Abs() macro (STYLE.H:118) is UNPARENTHESIZED --
				// Abs(a-b) mis-expands to (a-b>0 ? a-b : -a-b) == -(a+b) on the false
				// branch, NOT |a-b|.  For a steady spin (localVy==updVy) that yields
				// -(2*rate): on a reverse spin it is +2*rate and FLOODED a resync every
				// frame (the confirmed root of the half-rate/freeze).  Diff into a temp and
				// take an explicit abs so the macro only ever sees a single token.
				const Scalar velDiff  = (Scalar)localVelocity.angularMotion.y - (Scalar)updateVelocity.angularMotion.y;
				const Scalar velDrift = (velDiff < 0.0f) ? -velDiff : velDiff;
				// ANGULAR SIGN hunt (BT_ANGSIGN, 0.2s): instantaneous gate values, to
				// catch which of local/update/projected angular-Y carries the wrong sign.
				if (getenv("BT_ANGSIGN"))
				{
					static float s_as = 0.0f; s_as += dt;
					if (s_as >= 0.2f)
					{
						s_as = 0.0f;
						DEBUG_STREAM << "[angsign] localVy=" << (Scalar)localVelocity.angularMotion.y
							<< " updVy=" << (Scalar)updateVelocity.angularMotion.y
							<< " projVy=" << (Scalar)projectedVelocity.angularMotion.y
							<< " velDrift=" << velDrift << " angDrift=" << angDrift
							<< " turn=" << turn << "\n" << std::flush;
					}
				}
				if (angDb > 0.0f)
				{
					if (angDrift > angDb) { resync = True; rzn |= 1; }
					if (velDrift > velDb) { resync = True; rzn |= 2; }
					if ((Scalar)localVelocity.angularMotion.y == 0.0f
						&& (Scalar)updateVelocity.angularMotion.y != 0.0f) { resync = True; rzn |= 4; }
				}
				else if (Abs(angular_deviation.w) < 0.997f)
				{
					resync = True;					// stand-in when unstreamed
				}
				if (resync)
				{
					ForceUpdate(1 << MechResyncUpdateModelBit);	// type 4
				}
				// SPIN diagnosis (BT_SPIN, once/sec): how DENSE are the orientation
				// (type-4) records while turning?  A stuttery peer spin == too sparse.
				if (getenv("BT_SPIN"))
				{
					static float sAcc = 0.0f; static int sResync = 0, sFrames = 0, sAng = 0, sVel = 0, sRest = 0;
					static float sMaxAng = 0.0f, sMaxVel = 0.0f;
					sAcc += dt; sFrames++; if (resync) sResync++;
					if (rzn & 1) sAng++; if (rzn & 2) sVel++; if (rzn & 4) sRest++;
					if (angDrift > sMaxAng) sMaxAng = angDrift;
					if (velDrift > sMaxVel) sMaxVel = velDrift;
					if (sAcc >= 1.0f)
					{
						DEBUG_STREAM << "[spin-tx] resyncs=" << sResync << "/" << sFrames
							<< " byAngle=" << sAng << " byVel=" << sVel << " byRest=" << sRest
							<< " maxAng=" << sMaxAng << "(db" << angDb << ")"
							<< " maxVel=" << sMaxVel << "(db" << velDb << ")"
							<< " liveYaw=" << (Scalar)localVelocity.angularMotion.y
							<< " updYaw=" << (Scalar)updateVelocity.angularMotion.y << "\n" << std::flush;
						sAcc = 0.0f; sResync = 0; sFrames = 0; sAng = 0; sVel = 0; sRest = 0; sMaxAng = 0.0f; sMaxVel = 0.0f;
					}
				}
			}
			// Commanded-speed deadband (binary @0x4aac88): the mapper's live
			// speedDemand vs the last-replicated bodyTargetSpeed -> the tiny
			// type-2 record.  This is the AUTHENTIC replicant-gait feed.
			if (MappingMapper() != 0
				&& MappingMapper()->speedDemand != bodyTargetSpeed)
			{
				ForceUpdate(1 << MechSpeedUpdateModelBit);		// type 2
			}
			// Heat/impact-state change (binary @0x4aab75: heatAlarm level vs
			// the frame-entry snapshot @0x780) -> the type-7 record.
			if ((int)heatAlarm.GetLevel() != heatLevelSnapshot)
			{
				ForceUpdate(1 << MechImpactUpdateModelBit);		// type 7
			}
			heatLevelSnapshot = (int)heatAlarm.GetLevel();		// @0x780 snapshot
			if (getenv("BT_REPL_LOG"))
			{
				static float s_emitLog = 0.0f; s_emitLog += dt;
				if (s_emitLog >= 1.0f)
				{
					s_emitLog = 0.0f;
					DEBUG_STREAM << "[emit] updateModel=" << (int)updateModel
						<< " errSq=" << error.LengthSquared()
						<< " lastPerf=" << (float)lastPerformance
						<< " lastUpd=" << (float)lastUpdate
						<< " interesting=" << (int)IsInteresting() << "\n" << std::flush;
				}
			}
		}

		// FIRING ARC -- now an EXPLICIT OPT-IN presentation clamp only (task #36).
		// AUTHENTIC: the binary has NO aim/arc test -- Emitter::FireWeapon engages
		// the LOCKED target whenever HasActiveTarget (part_013.c:7758); the skill
		// is ACQUIRING the lock (crosshair on the enemy -> pick ray -> designate).
		// The old ±30°-default cone was a stand-in from before the acquisition
		// existed.  Set BT_FIRE_ARC=<degrees> to re-enable the cone (+ the mech's
		// real torso reach) as an external-camera presentation clamp; unset = the
		// authentic no-arc model.
		bool targetInArc = true;
		{
			static Scalar s_baseRad = -2.0f;
			if (s_baseRad < -1.5f)
			{
				const char *av = getenv("BT_FIRE_ARC");
				s_baseRad = (av != 0) ?
					(Scalar)((double)atof(av) * 3.14159265358979 / 180.0) : -1.0f;
			}
			if (s_baseRad >= 0.0f && gEnemyMech != 0)
			{
				UnitVector zAxisA;
				localToWorld.GetFromAxis(Z_Axis, &zAxisA);
				Vector3D fA(-(Scalar)zAxisA.x, -(Scalar)zAxisA.y, -(Scalar)zAxisA.z);
				Scalar fl = (Scalar)Sqrt(fA.x*fA.x + fA.y*fA.y + fA.z*fA.z);
				if (fl < 1e-4f) fl = 1.0f;
				fA.x /= fl; fA.y /= fl; fA.z /= fl;
				Point3D ep = ((Mech *)gEnemyMech)->localOrigin.linearPosition;
				Vector3D toE(ep.x - localOrigin.linearPosition.x,
				             ep.y - localOrigin.linearPosition.y,
				             ep.z - localOrigin.linearPosition.z);
				Scalar tl = (Scalar)Sqrt(toE.x*toE.x + toE.y*toE.y + toE.z*toE.z);
				if (tl < 1e-4f) tl = 1.0f;
				Scalar d = (fA.x*toE.x + fA.y*toE.y + fA.z*toE.z) / tl;
				Scalar half = s_baseRad + GetHorizontalFiringReach();
				if (half > 3.14159265f) half = 3.14159265f;
				targetInArc = (d >= (Scalar)cos((double)half));
			}
		}

		{
			// (The old fwd/muzzle-collection + straight-ahead `aim` block that
			// lived here fed the pre-#33 single-visual beam; the per-weapon
			// emitter beams below carry their own live muzzle + endpoint, so it
			// was dead code and was removed with the task-#36 acquisition work.)

			// resolve the "explode" effect once (also used by the target block).
			if (gExplodeReady == 0)
			{
				gExplodeReady = -1;
				if (application != 0 && application->GetResourceFile() != 0)
				{
					ResourceDescription *exp =
						application->GetResourceFile()->FindResourceDescription(
							"explode", (ResourceDescription::ResourceType)1, -1);
					if (exp != 0)
					{
						gExplodeRes = exp->resourceID;
						gExplodeReady = 1;
						DEBUG_STREAM << "[fire] explode effect resolved id=" << (long)gExplodeRes << "\n" << std::flush;
					}
					else DEBUG_STREAM << "[fire] 'explode' effect not found\n" << std::flush;
				}
			}

			// AUTHENTIC PER-WEAPON BEAMS (task #33): each Emitter/PPC draws ITS OWN
			// beam from its live sim state.  The state machine is the REAL one:
			// FireWeapon arms beamFlag + dischargeTimer (= the weapon's authored
			// DischargeTime); ServiceDischarge clears it when the window expires;
			// the recharge gate is the weapon's own authored RechargeRate.  The
			// muzzle resolves LIVE from the weapon's real mount segment (the beam
			// origin tracks the gun as the mech moves), the endpoint is the fire's
			// stored world hit point, and the colour is the weapon's authored
			// PipColor.  Volley-vs-stagger patterns, cadence and colours all now
			// emerge from each mech's real loadout (the BLH mounts 3 lasers + 2
			// PPCs) instead of the old hardcoded single-look stagger, so every
			// mech type fires like its data says.  (Old stagger/keepalive block
			// removed; see the git history for the bring-up scaffolding.)
			DrawWeaponBeams(dt);		// task #51: extracted (runs for replicants too)
		}

		// --- TARGETING: the WORLD-PICK model (task #41, the reconciliation of
		//     ALL the evidence).  The boresight ray picks whatever is DOWNRANGE:
		//       1. the enemy mech's collision volume -> aimed target (hull point
		//          -> STEP-6 zone under the boresight; hotbox + lock ring);
		//       2. else the TERRAIN (BTGroundRayHit) -> the ground point becomes
		//          the target -- the beam fires at the scenery ("firing at
		//          nothing", seen in the pod demo videos), no damage;
		//       3. else (sky, nothing within range) -> NO target, and the
		//          weapon's own double gate (FUN_004baa88:7689 + FUN_004bace8:
		//          7727 -- both require mech+0x388 != 0) refuses the discharge.
		//     Binary evidence for non-mech targets: HudSimulation :5620
		//     explicitly handles a target WITHOUT damage zones (target->0x120
		//     == 0) -- dead code if only mechs were ever targeted.  The pick is
		//     AUTOMATIC every frame (0x388 has 11 reads / 0 direct stores in
		//     CODE -- written indirectly, never a manual player lock).
		Entity *hotTarget = 0;						// the enemy mech under the boresight
		Point3D hotPoint;							// picked world point on its hull
		Entity *pickTarget = 0;						// what the boresight ray hit (mech/terrain)
		Point3D pickPoint;							// where it hit
		static int gAimNoRay = 0, gAimGround = 0, gAimHits = 0;	// 1Hz diagnostics
		{
			extern int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]);
			extern Entity *gBTTerrainEntity;		// captured by MakeEntityRenderables
			extern int BTGetTargetCandidates(Entity *shooter, Entity **out, int maxOut);
			Entity *cand[32];
			const int nc = BTGetTargetCandidates((Entity *)this, cand, 32);

			// Candidate diagnostics + the cross-pod PROOF hook run INDEPENDENT of
			// the aim ray (they don't need screen geometry -- and in -net mode the
			// aim projection may not be live, gap-map item 5).
			static float s_candLog = 0.0f; s_candLog += dt;
			const int candLog = (getenv("BT_MP_LOG") && s_candLog >= 1.0f);
			if (candLog) s_candLog = 0.0f;
			if (candLog)
				DEBUG_STREAM << "[mp-self] MY mech entityID=" << GetEntityID()
					<< " inst=" << (int)GetInstance() << "  (" << nc << " candidates)\n"
					<< std::flush;
			if (candLog)
				for (int ci = 0; ci < nc; ++ci)
				{
					Mech *m = (Mech *)cand[ci];
					if (m == 0) continue;
					Point3D mp = m->localOrigin.linearPosition;
					DEBUG_STREAM << "[mp-cand] " << (void*)m
						<< " entityID=" << m->GetEntityID()
						<< " ownerID=" << (int)m->GetOwnerID()
						<< " inst=" << (int)m->GetInstance()
						<< " classID=" << (int)m->GetClassID()
						<< " pos=(" << mp.x << "," << mp.y << "," << mp.z << ")"
						<< " zones=" << m->damageZoneCount << "\n" << std::flush;
				}

			// CRIT-PROPAGATION PROBE (BT_CRIT_PROBE=<zone>, task #2): every 4s
			// hammer ONE named zone of the LOCAL mech with a heavy hit until the
			// zone is destroyed -- drives Zone::TakeDamage -> RecurseSegmentTable
			// -> SendSubsystemDamage over the now-BOUND critical plugs, without
			// waiting for combat to randomly destroy a non-vital zone.  Diag
			// only, off by default.
			{
				static int   s_critZone = -2;
				static float s_critT    = 0.0f;
				if (s_critZone == -2)
				{
					const char *cz = getenv("BT_CRIT_PROBE");
					s_critZone = cz ? atoi(cz) : -1;
				}
				if (s_critZone >= 0 && s_critZone < damageZoneCount
					&& isPlayerMech && !IsMechDestroyed())
				{
					s_critT += dt;
					if (s_critT >= 4.0f)
					{
						s_critT = 0.0f;
						Mech__DamageZone *z = Zone(s_critZone);
						if (z != 0)
						{
							Damage dmg;
							dmg.damageType   = Damage::ExplosiveDamageType;
							dmg.damageAmount = 40.0f;
							dmg.burstCount   = 1;
							dmg.impactPoint  = localOrigin.linearPosition;
							DEBUG_STREAM << "[crit-probe] hitting zone "
								<< s_critZone << "\n" << std::flush;
							z->TakeDamage(dmg);
						}
					}
				}
			}

			// CROSS-POD DAMAGE TEST HOOK (BT_MP_FORCE_DMG, task #47): once a second,
			// dispatch an unaimed TakeDamage at the first live REPLICANT through the
			// SAME virtual Entity::Dispatch path a beam hit uses -- Entity::Dispatch
			// reroutes a replicant's message to the owning master over the wire, the
			// master's now-valid mech resolves + applies it (cylinder lookup).  Lets a
			// 2-node test exercise cross-pod delivery without lining up a boresight.
			// Off by default; the interactive beam path needs no such hook.
			if (getenv("BT_MP_FORCE_DMG"))
			{
				static float s_fd = 0.0f; s_fd += dt;
				if (s_fd >= 1.0f)
				{
					s_fd = 0.0f;
					for (int ci = 0; ci < nc; ++ci)
					{
						Mech *m = (Mech *)cand[ci];
						if (m == 0 || m->GetInstance() != ReplicantInstance
							|| m->IsMechDestroyed() || m->damageZoneCount <= 0)
							continue;
						Damage dmg;
						dmg.damageType   = Damage::ExplosiveDamageType;
						dmg.damageAmount = kShotDamage;
						dmg.burstCount   = 1;
						dmg.impactPoint  = m->localOrigin.linearPosition;
						Entity::TakeDamageMessage td(
							Entity::TakeDamageMessageID,
							sizeof(Entity::TakeDamageMessage),
							GetEntityID(), -1, dmg);
						if (getenv("BT_MP_LOG"))
							DEBUG_STREAM << "[mp-force] " << (void*)m
								<< " cross-pod TakeDamage id=" << m->GetEntityID()
								<< " -> ownerID=" << (int)m->GetOwnerID() << "\n" << std::flush;
						m->Dispatch(&td);
						break;
					}
				}
			}

			// WORLD-STRUCTURE SWEEP PROBE (BT_WSWEEP, task #50 verification): once a
			// second cast a horizontal fan of rays from the eyepoint against the
			// STATIC collision solid tree (WorldStructurePick -> FindStaticSolidHitBy)
			// and log the hit count + nearest range.  Confirms arena1's structures
			// (garages/walls) ARE in the collision tree the boresight now queries --
			// independent of the render sentinel or interactive aim.  Diag, off by default.
			if (getenv("BT_WSWEEP"))
			{
				static float s_ws = 0.0f; s_ws += dt;
				if (s_ws >= 1.0f)
				{
					s_ws = 0.0f;
					Point3D eye = localOrigin.linearPosition; eye.y += 30.0f;
					int hits = 0; float nearest = 1e30f; float ndir = -1.0f;
					for (int a = 0; a < 24; ++a)
					{
						double th = (double)a * (6.2831853071795862 / 24.0);
						Vector3D d((float)cos(th), 0.0f, (float)sin(th));
						Point3D hp;
						if (WorldStructurePick(eye, d, 2000.0f, &hp))
						{
							++hits;
							float dx = hp.x-eye.x, dz = hp.z-eye.z;
							float r = (float)Sqrt(dx*dx + dz*dz);
							if (r < nearest) { nearest = r; ndir = (float)(th * 57.29578); }
						}
					}
					DEBUG_STREAM << "[wsweep] eye=(" << eye.x << "," << eye.z
						<< ") struct-hits=" << hits << "/24 nearest="
						<< (hits ? nearest : -1.0f) << " dir=" << ndir << "deg\n" << std::flush;
				}
			}

			float rs[3], rd[3];
			if (!BTGetAimRay(gBTAimX, gBTAimY, rs, rd))
			{
				++gAimNoRay;
			}
			else
			{
				Point3D  rayStart(rs[0], rs[1], rs[2]);
				Vector3D rayDir(rd[0], rd[1], rd[2]);
				// MP TARGETING (task #46): walk EVERY other living mech (the solo
				// dummy AND every peer replicant, from the live-mech registry) and
				// pick the CLOSEST one the boresight ray strikes -- generalising
				// the solo gEnemyMech.
				float bestDist = 1e30f;
				for (int ci = 0; ci < nc; ++ci)
				{
					Mech *m = (Mech *)cand[ci];
					if (m == 0 || m->IsMechDestroyed())
						continue;
					Point3D hp;
					if (!m->PickRayHit(rayStart, rayDir, 4000.0f, &hp))
						continue;
					float dx = hp.x - rs[0], dy = hp.y - rs[1], dz = hp.z - rs[2];
					float d = dx*dx + dy*dy + dz*dz;
					if (d < bestDist)
					{
						bestDist  = d;
						hotTarget = cand[ci];
						hotPoint  = hp;
					}
				}
				// WORLD-STRUCTURE pick (task #50): ray-test the STATIC collision
				// solid tree (garages/walls/props -- the geometry that already
				// BLOCKS the mech's walk) along the boresight.  A structure CLOSER
				// than the mech hit occludes it (the shot stops at the wall); with
				// no mech, a structure hit IS the designation.  This is the
				// authentic non-mech world pick: Mech::WorldStructurePick ->
				// Mover::FindStaticSolidHitBy, the "test against the static world"
				// half of the engine's own FindBoxedSolidHitBy (MOVER.cpp) -- so a
				// shot lands on exactly the geometry the walk collides against
				// (the user's "structures block the mech, but weapons pass through").
				Point3D structPoint;
				float   structDist = 1e30f;
				bool    haveStruct = false;
				if (gBTTerrainEntity != 0)
				{
					Point3D sp;
					if (WorldStructurePick(rayStart, rayDir, 1200.0f, &sp))
					{
						float dx = sp.x-rs[0], dy = sp.y-rs[1], dz = sp.z-rs[2];
						structDist  = dx*dx + dy*dy + dz*dz;
						structPoint = sp;
						haveStruct  = true;
						static float s_wp = 0.0f; s_wp += dt;
						if (getenv("BT_GROUND_LOG") && s_wp >= 1.0f)
						{
							s_wp = 0.0f;
							DEBUG_STREAM << "[wpick] structure hit at ("
								<< sp.x << "," << sp.y << "," << sp.z << ") dist="
								<< (float)Sqrt(structDist)
								<< (hotTarget ? "  (mech also under boresight)" : "")
								<< "\n" << std::flush;
						}
					}
				}

				if (hotTarget != 0 && (!haveStruct || bestDist <= structDist))
				{
					pickTarget = hotTarget;			// mech is the closest hit
					pickPoint  = hotPoint;
					++gAimHits;
				}
				else if (haveStruct)
				{
					// a world structure is the closest hit (garage/wall) -- or it
					// occludes a mech behind it.  Designate it as the non-mech world
					// pick (the geometry rides in the POINT; the sentinel entity has
					// no damage-zone table -> no lock ring, HudSim part_013.c:5620).
					pickTarget = gBTTerrainEntity;
					pickPoint  = structPoint;
					++gAimGround;
				}
				else
				{
					// no mech, no structure -> the flat ground downrange of the
					// guns (max = the pod's HUD range scale; past it the shot is a
					// sky shot -> no target)
					extern bool BTGroundRayHit(float,float,float, float,float,float,
						float, float*,float*,float*);
					float hx, hy, hz;
					if (gBTTerrainEntity != 0 &&
						BTGroundRayHit(rs[0], rs[1], rs[2], rd[0], rd[1], rd[2],
							1200.0f, &hx, &hy, &hz))
					{
						pickTarget = gBTTerrainEntity;
						pickPoint.x = hx; pickPoint.y = hy; pickPoint.z = hz;
						++gAimGround;
					}
				}
			}

			// The Reticle struct (the mech's TargetReticle attribute): position,
			// pick result.  targetDamageZone stays -1 -- the zone ROLL happens at
			// damage delivery (the authentic percent-table roll, STEP 6).
			targetReticle.reticlePosition.x = gBTAimX;
			targetReticle.reticlePosition.y = gBTAimY;
			targetReticle.targetEntity      = pickTarget;
			targetReticle.targetDamageZone  = -1;
			if (pickTarget != 0)
				targetReticle.rayIntersection = pickPoint;

			// the engine-Entity target slots the whole weapon path reads
			if (pickTarget != 0)
			{
				MECH_TARGET_ENTITY(this) = pickTarget;
				MECH_TARGET_SUBIDX(this) = -1;
				MECH_TARGET_POS(this)    = pickPoint;	// beam endpoint = the pick
			}
			else if (gBTTerrainEntity != 0)
			{
				// FIRE-AT-NOTHING (authentic, decomp part_013.c:7727): the pod fired
				// freely at empty space -- the discharge gate is 0x388!=0 (the boresight
				// always DESIGNATES a point, not a manual mech lock).  When nothing pickable
				// is hit (a level shot flies over the flat ground, or open sky), designate a
				// MAX-RANGE point using the world sentinel so the weapon discharges to max
				// range.  Sentinel has no damage-zone table -> no aimed-zone damage, no lock
				// ring (mechs only), per HudSimulation part_013.c:5620.
				MECH_TARGET_ENTITY(this) = gBTTerrainEntity;
				MECH_TARGET_SUBIDX(this) = -1;
				Point3D farPt;
				farPt.x = rs[0] + rd[0] * 1200.0f;	// 1200 = HUD range default (0x44960000)
				farPt.y = rs[1] + rd[1] * 1200.0f;
				farPt.z = rs[2] + rd[2] * 1200.0f;
				MECH_TARGET_POS(this) = farPt;
				targetReticle.targetEntity    = gBTTerrainEntity;
				targetReticle.rayIntersection = farPt;
			}
			else
			{
				MECH_TARGET_ENTITY(this) = 0;			// no world sentinel -> no target
				MECH_TARGET_SUBIDX(this) = -1;
			}
		}

		// DETERMINISTIC FIRE-AT-STRUCTURE TEST (BT_FIRE_AT_STRUCT, task #50 verify):
		// the headless aim ray (BTGetAimRay) is flaky without a focused window, so
		// this bypasses it -- designate the NEAREST world structure (360 sweep) as
		// the target every frame so BT_AUTOFIRE can be tested against a real, stable,
		// in-range building.  Diag only, off by default.
		if (getenv("BT_FIRE_AT_STRUCT"))
		{
			extern Entity *gBTTerrainEntity;
			Point3D eye = localOrigin.linearPosition; eye.y += 30.0f;
			Point3D best; float bestR = 1e30f; bool got = false;
			for (int a = 0; a < 24; ++a)
			{
				double th = (double)a * (6.2831853071795862 / 24.0);
				Vector3D d((float)cos(th), 0.0f, (float)sin(th));
				Point3D hp;
				if (WorldStructurePick(eye, d, 2000.0f, &hp))
				{
					float dx = hp.x-eye.x, dz = hp.z-eye.z;
					float r = (float)Sqrt(dx*dx + dz*dz);
					if (r < bestR) { bestR = r; best = hp; got = true; }
				}
			}
			if (got && gBTTerrainEntity != 0)
			{
				MECH_TARGET_ENTITY(this) = gBTTerrainEntity;
				MECH_TARGET_SUBIDX(this) = -1;
				MECH_TARGET_POS(this)    = best;
				static float s_fas = 0.0f; s_fas += dt;
				if (getenv("BT_FIRE_LOG") && s_fas >= 1.0f)
				{
					s_fas = 0.0f;
					DEBUG_STREAM << "[fireat] nearest struct (" << best.x << ","
						<< best.y << "," << best.z << ") r=" << bestR << "\n" << std::flush;
				}
			}
		}

		// HUD feeds: the range caret + the hotbox (world point + state) + the
		// recovered-Execute instruments (compass, twist tape, group mask).
		// The range caret tracks the PICK (authentic: :5639 computes it from
		// mech+0x37c whatever the target is -- a terrain pick reads the ground
		// distance); the hotbox + lock exist ONLY for a mech target (:5620 --
		// a target with no damage zones gets neither).
		{
			extern void BTSetHudTargetRange(Scalar range);
			// RANGE RATE LIMIT (HudSimulation :5652-5670 [T1]): the DISPLAYED
			// range slides toward the true pick range at 500 m/s -- shown +=
			// clamp(true - shown, +-dt*500) -- so the caret sweeps smoothly as
			// the boresight crosses near/far ground instead of teleporting.
			// (Applies to the no-target 1200 default too.)
			static float sShownRange = 1200.0f;
			float trueRange = 1200.0f;					// no target: the binary default
			Entity *des = MECH_TARGET_ENTITY(this);
			if (des != 0 && des != hotTarget)
			{
				// terrain pick: range to the ground point; no hotbox, no ring.
				Point3D tp = MECH_TARGET_POS(this);
				float hddx = tp.x - localOrigin.linearPosition.x;
				float hddz = tp.z - localOrigin.linearPosition.z;
				trueRange = sqrtf(hddx*hddx + hddz*hddz);
				gBTHudLockState = 0;
			}
			else if (des != 0)
			{
				Mech   *dm = (Mech *)des;
				Point3D dp = dm->localOrigin.linearPosition;
				float hddx = dp.x - localOrigin.linearPosition.x;
				float hddz = dp.z - localOrigin.linearPosition.z;
				trueRange = sqrtf(hddx*hddx + hddz*hddz);
				gBTHudLockWorld[0] = dp.x;			// the HOTBOX point: top-centre
				gBTHudLockWorld[1] = dp.y + (float)dm->CylinderReferenceHeight();
				gBTHudLockWorld[2] = dp.z;

				// AUTHENTIC LOCK (HudSimulation, part_013.c:5619-5634 [T1]):
				// the fire-control LOCK needs (a) a working targeting computer
				// -- your own HUD's host zone below 75% damage -- and (b) a
				// live targeted zone -- its damage below 1.0 (a whole-mech
				// target checks zone 0).  So a shot-up cockpit loses the lock
				// light, and a destroyed wreck's dead zone can't be re-locked.
				int lock = 1;
				{
					MechSubsystem *hud = (MechSubsystem *)GetHudSubsystem();
					if (hud != 0 && hud->GetDamageZoneProxy() != 0)
					{
						Mech__DamageZone *hz =
							(Mech__DamageZone *)hud->GetDamageZoneProxy();
						if (hz->damageLevel >= 0.75f)		// _DAT_004b7ec4
							lock = 0;
					}
					int tz = MECH_TARGET_SUBIDX(this);
					if (tz < 0) tz = 0;						// whole-mech -> zone 0
					if (lock && tz < dm->damageZoneCount && dm->Zone(tz) != 0
						&& dm->Zone(tz)->damageLevel >= 1.0f)	// _DAT_004b7ec8
						lock = 0;
				}
				// 1 = target held (hotbox draws); 2 = LOCKED (box + spin ring).
				// The binary keeps these separate: the box follows HotBoxVector,
				// the ring follows the Lock attr.
				gBTHudLockState = lock ? 2 : 1;
			}
			else
			{
				gBTHudLockState = 0;				// sky: no target (trueRange stays 1200,
			}										//  the binary default @part_013.c:5637)

			// the 500 m/s slide toward trueRange (see the banner above)
			{
				float maxStep = (float)dt * 500.0f;
				if (maxStep < 0.0f) maxStep = -maxStep;
				float step = trueRange - sShownRange;
				if (step >  maxStep) step =  maxStep;
				if (step < -maxStep) step = -maxStep;
				sShownRange += step;
				BTSetHudTargetRange((Scalar)sShownRange);
			}

			gBTHudHeading    = gDriveHeading;					// CompassHeading (attr 0xD)
			gBTHudTwist      = (float)TorsoHeading();			// RotationOfTorsoHorizontal (attr 4)
			gBTHudTwistLimit = (float)GetHorizontalFiringReach();// HorizontalTorsoLimit (attrs 5/6)
			gBTHudGroupMask  = (int)targetReticle.reticleElementMask & 0xF;
			gBTHudPrimary    = ((int)targetReticle.reticleElementMask & 0x20) != 0;
			// task #58: publish the live twist for renderer-side DIAGNOSTICS
			// (correlating [eyefwd] against the twist).  The cockpit eye does
			// NOT consume this -- it inherits the twist authentically through
			// jointtorso's HingeRenderable in the draw traversal (see the note
			// in DPLEyeRenderable::Execute).
			{
				extern float gBTEyeTwist;
				gBTEyeTwist = gBTHudTwist;
			}
		}

		// task #6 ORDER FIX: the scripted block must run BEFORE the fire-push
		// block below -- it writes gBTLaserKey/gBTConfigKey, which the earlier
		// keyboard poll zeroes each frame while the window is unfocused; the
		// old placement AFTER the push block meant the push never saw the
		// scripted press.
		// task #6 scripted verify (BT_CONFIG_TEST=1, headless): drives the same
		// gBTConfigKey/gBTLaserKey states the keyboard would --
		//   t=[8,11)   hold the configure button (session open)
		//   t=[9.5,9.7) one Trigger press edge  -> ChooseButton toggle
		//   t>=13      0.2s Trigger pulses      -> does the weapon fire now?
		if ((Entity *)this == application->GetViewpointEntity()
			&& getenv("BT_CONFIG_TEST"))
		{
			// frame-count driven (this env throttles background windows to a
			// few fps -- wall/sim seconds are useless for scripting)
			static int s_cfgFrame = 0;
			++s_cfgFrame;
			(void)dt;
			// Schedule LATE (frame 2000+): the sim ticks many frames per second
			// and the first-run schedule (30-120) fired before the enemy spawn
			// completed -- targetInArc false -> the fire press never pushed.
			gBTConfigKey = (s_cfgFrame >= 2000 && s_cfgFrame < 2600) ? 1 : 0;
			int fireOn   = (s_cfgFrame >= 2300 && s_cfgFrame < 2320) ? 1 : 0;	// ONE press edge
			if (s_cfgFrame >= 3000)
				fireOn = ((s_cfgFrame / 60) & 1);		// fire pulses after exit
			gBTLaserKey = fireOn;
		}

		// E8: pulse the three fire channels per frame (1,0,1,0...) so each
		// weapon sim's CheckFireEdge sees clean rising edges.  UNCONDITIONAL --
		// NOT inside the enemy block: firing needs only the world PICK (task
		// #41: the beam goes to the scenery downrange with no enemy alive; the
		// weapon's OWN HasActiveTarget gate refuses only a true sky shot).
		// Leaving these inside `if (gEnemyMech)` froze the channels after the
		// wreck buried -> no trigger edges -> "can't fire after the kill"
		// (user-reported regression).  targetInArc is the explicit BT_FIRE_ARC
		// presentation clamp (default true).  BT_AUTOFIRE holds all three.
		{
			// AUTHENTIC FIRE INPUT (task #5): key states become PRESS/RELEASE
			// edges pushed into the LBE4 buttonGroup channels -- the pod's own
			// joystick path (L4CTRL Execute: Update(&v, mode) with v =
			// +(button+1) press / -(button+1) release).  The streamed per-mech
			// controls map binds these buttons DIRECTLY onto each grouped
			// weapon's TriggerState@0x31C; CheckFireEdge does the rest.  This
			// retires the gBT*Trigger globals + the 1,0 pulse hack.
			// Desktop map: SPACE/'1' -> Trigger(0x40) [the main fire group],
			// '2' -> ThumbLow(0x46), '3'/CTRL -> ThumbHigh(0x47) [missiles on
			// most mechs].  BT_AUTOFIRE pulses the Trigger (edge per 2 frames).
			// targetInArc stays the opt-in BT_FIRE_ARC presentation clamp.
			LBE4ControlsManager *cm =
				(LBE4ControlsManager *)application->GetControlsManager();
			ModeMask modeMask = application->GetModeManager()->GetModeMask();
			static int s_prevBtn[4] = {0, 0, 0, 0};
			static int s_afPhase = 0;
			s_afPhase ^= 1;
			// DIAG (BT_AF_PERIOD=<sec>): throttle autofire cadence -- pulse the trigger
			// only briefly every <sec> seconds, to test a slower-than-max fire rate (the
			// coolant/jam mechanic is fire-rate dependent: at max spam the weapon overheats
			// regardless; at a realistic cadence coolant priority decides the jam).
			static float s_afClock = 0.0f;
			s_afClock += dt;
			static const float s_afPeriod =
				getenv("BT_AF_PERIOD") ? (float)atof(getenv("BT_AF_PERIOD")) : 0.0f;
			int afGate = 1;
			if (s_afPeriod > 0.0f)
				afGate = (fmodf(s_afClock, s_afPeriod) < 0.6f) ? 1 : 0;
			const int autofire = (gBTDrive.fireForced && targetInArc && afGate) ? s_afPhase : 0;
			const int buttons[4] = {
				LBE4ControlsManager::ButtonJoystickTrigger,		// 0x40
				LBE4ControlsManager::ButtonJoystickPinky,		// 0x45
				LBE4ControlsManager::ButtonJoystickThumbLow,	// 0x46
				LBE4ControlsManager::ButtonJoystickThumbHigh,	// 0x47
			};
			// DEV: BT_AF_MISSILE=1 pulses the missile group (ThumbHigh).  INDEPENDENT
			// of BT_AUTOFIRE (which drives the main gun group) so a headless rig can
			// exercise MISSILES ONLY -- isolates missile damage/splash from laser+PPC
			// fire (task #62 TTK verification).
			static const int s_afMissile = getenv("BT_AF_MISSILE") ? 1 : 0;
			const int autofireMsl = (s_afMissile && targetInArc) ? s_afPhase : 0;
			int want[4];
			want[0] = ((gBTLaserKey   && targetInArc) || autofire) ? 1 : 0;
			want[1] = (gBTPinkyKey   && targetInArc) ? 1 : 0;	// key '4' (was unmapped)
			want[2] = (gBTPPCKey     && targetInArc) ? 1 : 0;
			want[3] = ((gBTMissileKey && targetInArc) || autofireMsl) ? 1 : 0;
			if (cm != 0)
			{
				for (int b = 0; b < 4; ++b)
				{
					if (want[b] != s_prevBtn[b])
					{
						s_prevBtn[b] = want[b];
						ControlsButton v = (ControlsButton)(want[b]
							? (buttons[b] + 1) : -(buttons[b] + 1));
						cm->buttonGroup[buttons[b]].ForceUpdate(&v, modeMask);	// diag: bypass the prev-diff gate
						if (getenv("BT_FIRE_LOG"))
						{
							static int s_pushN = 0;
							if ((++s_pushN % 60) == 1)
								DEBUG_STREAM << "[push] btn=0x" << std::hex
									<< buttons[b] << std::dec << " v=" << (int)v
									<< " mode=0x" << std::hex << (int)modeMask
									<< std::dec << " #" << s_pushN
									<< " thisMech=" << (void *)this
									<< " viewpoint=" << (void *)application->GetViewpointEntity()
									<< std::endl;
						}
					}
				}
			}
		}


		// task #12 scripted verify (BT_GENSEL_TEST=1): one SelectGeneratorD
		// press at frame ~2500 -- the PPC re-taps from its authored GeneratorA
		// onto GeneratorD; the charging I^2R then lands on GenD ([heat-t]).
		if ((Entity *)this == application->GetViewpointEntity()
			&& getenv("BT_GENSEL_TEST"))
		{
			static int s_gsFrame = 0;
			++s_gsFrame;
			gBTGenSelKey = (s_gsFrame >= 600 && s_gsFrame < 610) ? 7 : 0;
		}

		// task #13 scripted verify (BT_VALVE_TEST=1): one MoveValve press at
		// frame ~600 -- Condenser1's valveState cycles 1 -> 5, so the flow
		// redistribution gives it 5/10 of the total coolant flow ([valve] log
		// + its bank conduction speeds up 5x).
		if ((Entity *)this == application->GetViewpointEntity()
			&& getenv("BT_VALVE_TEST"))
		{
			static int s_vtFrame = 0;
			++s_vtFrame;
			gBTValveKey = (s_vtFrame >= 600 && s_vtFrame < 610) ? 1 : 0;
		}

		// task #13 dev harness: the COOLANT VALVE lever.  'C' edge -> dispatch
		// MoveValve (id 4, the Condenser table @0x50E52C) to the selected
		// condenser -- the same press payload the pod's engineering-screen aux
		// buttons deliver.
		if ((Entity *)this == application->GetViewpointEntity())
		{
			static int s_prevValve = 0;
			if (gBTValveKey != s_prevValve)
			{
				s_prevValve = gBTValveKey;
				if (gBTValveKey != 0)
				{
					Subsystem *condenser = 0;
					const char *slotEnv = getenv("BT_VALVE_SLOT");
					int wantSlot = (slotEnv != 0) ? atoi(slotEnv) : -1;
					for (int s = 1; s < GetSubsystemCount(); ++s)
					{
						Subsystem *sub = GetSubsystem(s);
						if (sub == 0)
							continue;
						if (wantSlot >= 0 ? (s == wantSlot)
							: ((int)sub->GetClassID() == 0xBBD))	// Condenser
						{
							condenser = sub;
							break;
						}
					}
					if (condenser != 0)
					{
						if (getenv("BT_FIRE_LOG"))
							DEBUG_STREAM << "[valve-tx] MoveValve -> "
								<< condenser->GetName() << std::endl;
						ReceiverDataMessageOf<ControlsButton> msg(
							4 /*Condenser::MoveValveMessageID*/,
							sizeof(ReceiverDataMessageOf<ControlsButton>),
							(ControlsButton)1 /*press*/);
						condenser->Dispatch(&msg);
					}
				}
			}
		}

		// task #12 dev harness: the POWER-ROUTING buttons.  F5..F9 edge ->
		// dispatch SelectGeneratorA..D / ToggleGeneratorMode (ids 4..8) to the
		// selected weapon -- the same ReceiverDataMessageOf<ControlsButton>
		// press payload the pod's aux-panel EventMappings deliver.  The
		// handler chain is PoweredSubsystem's (weapons inherit via MechWeapon).
		if ((Entity *)this == application->GetViewpointEntity())
		{
			static int s_prevGenSel = 0;
			if (gBTGenSelKey != s_prevGenSel)
			{
				int pressedID = gBTGenSelKey;			// 0 on release
				s_prevGenSel = gBTGenSelKey;
				if (pressedID != 0)
				{
					Subsystem *weapon = 0;
					const char *slotEnv = getenv("BT_CONFIG_SLOT");
					int wantSlot = (slotEnv != 0) ? atoi(slotEnv) : -1;
					for (int s = 1; s < GetSubsystemCount(); ++s)
					{
						Subsystem *sub = GetSubsystem(s);
						if (sub == 0)
							continue;
						int cid = (int)sub->GetClassID();
						if (wantSlot >= 0 ? (s == wantSlot)
							: (cid == 0xBC8 || cid == 0xBCA || cid == 0xBCE
								|| cid == 0xBD0 || cid == 0xBD4))
						{
							weapon = sub;
							break;
						}
					}
					if (weapon != 0)
					{
						if (getenv("BT_FIRE_LOG"))
						{
							extern void BTGenSelProbe();
							BTGenSelProbe();
							DEBUG_STREAM << "[gensel-tx] id=" << pressedID
								<< " -> " << weapon->GetName() << std::endl;
						}
						ReceiverDataMessageOf<ControlsButton> msg(
							pressedID /*4..8 (powersub.hpp ids)*/,
							sizeof(ReceiverDataMessageOf<ControlsButton>),
							(ControlsButton)1 /*press*/);
						weapon->Dispatch(&msg);
					}
				}
			}
		}

		// task #6 dev harness: the config-session BRACKET.  'G' edge ->
		// dispatch ConfigureMappables (id 9) press/release to the selected
		// weapon -- the exact ReceiverDataMessageOf<ControlsButton> payload
		// (+-(element+1)) the pod's streamed aux-panel EventMapping delivers.
		// Everything downstream is the AUTHENTIC chain: EnterConfiguration
		// flips the global mode to Mapping(0x8000) (normal fire mappings go
		// dormant for the hold), the fire keys deliver ChooseButton (id 10)
		// through the temp 0x8000 event mappings pushed by the SAME live-mode
		// button path above, and the release restores NonMapping(0x10000).
		if ((Entity *)this == application->GetViewpointEntity())
		{
			static int s_prevCfg = 0;
			if (gBTConfigKey != s_prevCfg)
			{
				s_prevCfg = gBTConfigKey;
				Subsystem *weapon = 0;
				const char *slotEnv = getenv("BT_CONFIG_SLOT");
				int wantSlot = (slotEnv != 0) ? atoi(slotEnv) : -1;
				for (int s = 1; s < GetSubsystemCount(); ++s)	// slot 0 = the MAPPER, never a weapon
				{
					Subsystem *sub = GetSubsystem(s);
					if (sub == 0)
						continue;
					// Pick by WEAPON classID (0xBC8 Emitter / 0xBCA ProjectileWeapon /
					// 0xBCE GaussRifle / 0xBD0 MissileLauncher / 0xBD4 PPC).  The old
					// (simulationFlags & 0x8) heuristic matched the MAPPER at slot 0:
					// its MakeViewpointEntity resource is stack-built with only
					// name/classID/size set, so its flags are STACK GARBAGE -- and a
					// config message dispatched to the mapper lands in the mapper's
					// OWN id space (the aux-preset Fail trap) = the abort popup.
					int cid = (int)sub->GetClassID();
					if (wantSlot >= 0 ? (s == wantSlot)
						: (cid == 0xBC8 || cid == 0xBCA || cid == 0xBCE
							|| cid == 0xBD0 || cid == 0xBD4))
					{
						weapon = sub;
						break;
					}
				}
				if (weapon == 0 && getenv("BT_FIRE_LOG"))
				{
					DEBUG_STREAM << "[config] NO WEAPON matched (count="
						<< GetSubsystemCount() << ")" << std::endl;
					for (int s2 = 0; s2 < GetSubsystemCount() && s2 < 33; ++s2)
					{
						Subsystem *p = GetSubsystem(s2);
						if (p != 0)
							DEBUG_STREAM << "  [cfg-scan] " << s2 << " " << p->GetName()
								<< " flags=0x" << std::hex << (int)p->simulationFlags
								<< std::dec << std::endl;
					}
				}
				if (weapon != 0)
				{
					const int kElem = 0x0e;				// an aux-panel element (as authored)
					ReceiverDataMessageOf<ControlsButton> msg(
						9 /*MechWeapon::ConfigureMappablesMessageID (mechweap.hpp)*/,
						sizeof(ReceiverDataMessageOf<ControlsButton>),
						(ControlsButton)(gBTConfigKey ? (kElem + 1) : -(kElem + 1)));
					weapon->Dispatch(&msg);
					if (getenv("BT_FIRE_LOG"))
						DEBUG_STREAM << "[config] " << (gBTConfigKey ? "ENTER" : "EXIT")
							<< " session on " << weapon->GetName()
							<< " mode now=0x" << std::hex
							<< (int)application->GetModeManager()->GetModeMask()
							<< std::dec << std::endl;
				}
			}
		}

		{
			// The VICTIM under the boresight (task #46: any peer mech, not just
			// the solo gEnemyMech).  Range/log/fire all key off THIS mech.
			Entity *victim = hotTarget;
			Point3D victimPos = (victim != 0)
				? ((Mech *)victim)->localOrigin.linearPosition
				: localOrigin.linearPosition;

			float ddx = victimPos.x - localOrigin.linearPosition.x;
			float ddy = victimPos.y - localOrigin.linearPosition.y;
			float ddz = victimPos.z - localOrigin.linearPosition.z;
			float range = (float)Sqrt((double)(ddx*ddx + ddy*ddy + ddz*ddz));

			// THE AUTHENTIC RANGE GATE (FireWeapon @004bace8 :7758 [T1]): damage
			// applies when dist <= the weapon's EFFECTIVE range = (1 - host-zone
			// damage) x its AUTHORED WeaponRange (BLH: lasers 500, missiles 800,
			// PPCs 900 m -- the [hud] pip dump) -- the per-weapon targetWithinRange
			// flag UpdateTargeting computes each frame.  Any live weapon in reach
			// lands the aggregate shot.
			int anyWeaponInRange = 0;
			for (int wi = 0; wi < GetSubsystemCount(); ++wi)
			{
				Subsystem *ws = GetSubsystem(wi);
				if (ws == 0 || !ws->IsDerivedFrom(MechWeapon::ClassDerivations))
					continue;
				if (*(Logical *)((MechWeapon *)ws)->WithinRangePtr())
				{
					anyWeaponInRange = 1;
					break;
				}
			}

			gTargetLogAccum += dt;
			if (gTargetLogAccum >= 1.0f)
			{
				gTargetLogAccum = 0.0f;
				DEBUG_STREAM << "[target] aim=(" << gBTAimX << "," << gBTAimY << ")"
					<< (victim != 0 ? "  MECH under boresight (aimed)"
						: (pickTarget != 0 ? "  terrain downrange (beam at scenery)"
						: (MECH_TARGET_ENTITY(this) != 0 ? "  fire-at-nothing (max-range designate)"
										   : "  no target (no world sentinel)")))
					<< "  range=" << range
					<< (anyWeaponInRange ? "  IN WEAPON RANGE" : "")
					<< "  [mechPicks=" << gAimHits << " groundPicks=" << gAimGround
					<< " noRay=" << gAimNoRay << "]"
					<< "\n" << std::flush;
				gAimHits = 0; gAimNoRay = 0; gAimGround = 0;
			}

			// Resolve the "explode" effect resource once.
			if (gExplodeReady == 0)
			{
				gExplodeReady = -1;
				if (application != 0 && application->GetResourceFile() != 0)
				{
					ResourceDescription *exp =
						application->GetResourceFile()->FindResourceDescription(
							"explode", (ResourceDescription::ResourceType)1, -1);
					if (exp != 0)
					{
						gExplodeRes   = exp->resourceID;
						gExplodeReady = 1;
						DEBUG_STREAM << "[fire] explode effect resolved id="
							<< (long)gExplodeRes << "\n" << std::flush;
					}
					else
					{
						DEBUG_STREAM << "[fire] 'explode' effect not found\n" << std::flush;
					}
				}
			}

			// THE PHANTOM-SHOT BLOCK IS RETIRED (task #11, user-reported): this
			// bring-up stand-in spawned an explosion at the victim on its OWN
			// 0.3s cadence whenever the fire key was down -- matched the old
			// one-frame recharge, but once the AUTHENTIC electrical model
			// landed (task #10: 2-5s recharges + the generator thermal breaker)
			// it kept painting hits while no weapon was discharging ("enemy
			// shows fire even though I'm not producing lasers").  The authentic
			// impact visual flows from each REAL discharge: Emitter::FireWeapon
			// -> SendDamageMessage -> SubsystemMessageManager explosion
			// bundling -> SubmitExplosion (messmgr.cpp).  This block now keeps
			// only its presentation roles above (boresight pick -> the target
			// slots the weapons read, and the [target] log).
		}
	}

	// --- Tick the subsystem roster (BT analog of Entity::PerformAndWatch) -----
	//  The shipped engine's Entity::PerformAndWatch walks subsystemArray[] and
	//  calls PerformAndWatch on every non-null, non-replicant-disabled subsystem
	//  (MUNGA/ENTITY.cpp ~698-790; RP's VTV ticks ITS roster the identical way).
	//  Our drivable override bypasses the unsafe Mech::Simulate, but the
	//  subsystem roster must still tick each frame so heat/weapons/sensors/gyro/
	//  power/etc. run their per-frame Performance.  We mirror the engine loop
	//  here instead of calling Simulate: each Subsystem is a Simulation whose
	//  base PerformAndWatch (engine, safe) computes its OWN time slice from its
	//  own lastPerformance and dispatches activePerformance -- the reconstructed
	//  *::*Simulation method the subsystem's ctor installed via SetPerformance.
	//  Sentinels: index 0 is always NULL; index 1 is the resolved voltage bus;
	//  the real streamed subsystems live at id>=2.  Null + executable guards
	//  make all three cases safe.  (Per-subsystem behaviour deepening is a
	//  separate wave; here we only make the TICK PATH live.)
	int subsystemsTicked  = 0;		// performed this frame (executable)
	int subsystemsPresent = 0;		// non-null roster occupancy (excl. mapper)
	if (subsystemArray != 0 && subsystemCount > 0)
	{
		for (int i = 0; i < subsystemCount; ++i)
		{
			Subsystem *subsystem = subsystemArray[i];
			if (subsystem == 0)
				continue;
			if (i != 0)					// slot 0 = the mapper (task #7)
				++subsystemsPresent;
			if (!subsystem->IsNonReplicantExecutable())
				continue;

			// The controls-mapping subsystem (roster slot 0 via Mech::SetMapping
			// Subsystem -- the [0x10d] mirror is GONE, task #7) is TICKED under BT_REAL_CONTROLS
			// -- its InterpretControls chain is now reconciled: FillPilotArray reads
			// the local player via application->GetMissionPlayer() (the old wild
			// application+0x6c read was THE bypass-causing AV), and the main tick
			// @004afd10 reads the owner through declared members (reverseStride
			// Length/walkStrideLength/forwardThrottleScale, the real Torso analog
			// axes, the real HUD freeAimSlew).  Without the env the historic skip
			// stands (default behavior unchanged).
			{
				static const int s_realControlsTick = BTEnvOn("BT_REAL_CONTROLS", 1);
				if (!s_realControlsTick && i == 0)		// slot 0 = the mapper (task #7)
					continue;
			}

			subsystem->PerformAndWatch(till, update_stream);
			++subsystemsTicked;
		}
	}

	// One-shot: report how many subsystems the tick dispatched to on the FIRST
	// frame, before any DoNothingOnce subsystems latch NeverExecute and opt out.
	if (!gTickFirstLogged)
	{
		gTickFirstLogged = 1;
		DEBUG_STREAM << "[tick] first frame: dispatched to " << subsystemsTicked
			<< " executable subsystem(s) of " << subsystemsPresent
			<< " present / roster " << subsystemCount << "\n" << std::flush;
		// DEV: BT_ROSTER=1 dumps the LOADOUT -- every roster subsystem's class
		// name + id (settles "is this mech supposed to mount weapon X?" from the
		// shipped subsystem stream, not assumptions).
		if (getenv("BT_ROSTER"))
		{
			for (int ri = 0; ri < subsystemCount; ++ri)
			{
				Subsystem *rs = GetSubsystem(ri);
				if (rs == 0)
					continue;
				Derivation *rd = rs->GetDerivation();
				// Resolve the authored splash radius for every weapon so we can
				// confirm the GameModel +0x50 plumbing yields real values without
				// needing a live missile hit + bystander (task #62 verification).
				Scalar splash = -1.0f;
				if (rs->IsDerivedFrom(*MechWeapon::GetClassDerivations()))
					splash = BTResolveSplashRadius((Entity *)this, ri);
				DEBUG_STREAM << "[roster] " << ri
					<< " classID=" << (int)rs->GetClassID()
					<< " " << (rd ? rd->className : "?")
					<< " splashRadius=" << splash << "\n" << std::flush;
			}
		}
	}

	// 1 Hz confirmation that the tick path is live (N subsystems simulated).
	gTickLogAccum += dt;
	if (gTickLogAccum >= 1.0f)
	{
		gTickLogAccum = 0.0f;
		DEBUG_STREAM << "[tick] subsystems simulated: " << subsystemsTicked
			<< " (executable) of " << subsystemsPresent
			<< " present / roster " << subsystemCount << "\n" << std::flush;

		// DEV: BT_SPLASH_TEST=1 -- deterministic splash verification (task #62).
		// Once per second the LOCAL mech synthesizes a missile detonation AT the
		// enemy's position (directVictim=0, so the enemy IS a splash candidate)
		// using its first missile launcher.  Exercises the FULL path (radius
		// resolve -> candidate walk -> distance-scaled burst -> damage delivery)
		// without depending on the live targeting/autofire path.
		static const int s_splashTest = getenv("BT_SPLASH_TEST") ? 1 : 0;
		if (s_splashTest && gEnemyMech != 0 && (Entity *)this != gEnemyMech)
		{
			int mslIdx = -1;
			for (int i = 0; i < subsystemCount; ++i)
			{
				Subsystem *s = GetSubsystem(i);
				if (s != 0 && s->IsDerivedFrom(MissileLauncher::ClassDerivations))
				{ mslIdx = i; break; }
			}
			if (mslIdx >= 0)
			{
				// Detonate as a NEAR-MISS 15 units from the enemy (a realistic
				// splash geometry: the round bursts beside the mech, not at its
				// exact center), so the burst falloff exercises the typical case.
				Point3D ep = ((Mech *)gEnemyMech)->localOrigin.linearPosition;
				Point3D burst = ep; burst.x += 15.0f;
				Damage sd;
				sd.damageType   = Damage::ExplosiveDamageType;
				sd.damageAmount = 0.03f;			// small test dose
				sd.burstCount   = 1;
				sd.impactPoint  = burst;
				extern void BTApplySplashDamage(Entity *, int, const Point3D &,
												Entity *, const Damage &);
				BTApplySplashDamage((Entity *)this, mslIdx, burst, 0, sd);
			}
		}
	}

	//
	// GYRO JOINT-WRITE DISPATCH (task #56, byte-verified @0x4aaf18-0x4aaf89):
	// the binary Mech master performance tail pushes the gyro's integrated state
	// into the skeleton every frame AFTER the animation pass + subsystem tick:
	//   gyro->swayBias = mech sway accumulator (the mech+0x3F0 overspeed model is
	//   NOT yet reconstructed -- feed 0, flagged);
	//   if (!deathAnimationLatched) { if (legAnimationState != 0) WriteEyeJoint();
	//   WriteMechJoint(); }
	// WriteMechJoint drives 'jointeye' (translation = the eye spring offset +
	// hit bounce, rotation = body tip); siteeyepoint rides that joint, so this
	// IS what moves the cockpit eye.  Runs after the roster tick above (the gyro
	// Performance integrated this frame) and after the gait channel wrote the
	// joints (WriteEyeJoint is multiplicative on the animated rotation).
	//
	if (GetInstance() == MasterInstance)
	{
		extern void GyroFrameJointWrite(Subsystem *, Scalar, int, int);
		static int s_gyrodbg = 0;
		if (getenv("BT_GYRO_LOG") && s_gyrodbg++ == 0)
			DEBUG_STREAM << "[gyro] dispatch: gyro=" << (void *)gyroSubsystem
				<< " legAnim=" << legAnimationState
				<< " death=" << deathAnimationLatched << "\n" << std::flush;
		GyroFrameJointWrite(gyroSubsystem, 0.0f /* mech+0x3F0 model TBD */,
							legAnimationState, deathAnimationLatched);
	}
	else if (getenv("BT_GYRO_LOG"))
	{
		static int s_gyrodbg2 = 0;
		if (s_gyrodbg2++ == 0)
			DEBUG_STREAM << "[gyro] dispatch SKIPPED: instance=" << (int)GetInstance()
				<< " != master\n" << std::flush;
	}

	// Keep the simulation/networking bookkeeping consistent (this is exactly
	// what the base "no time / stasis" early-out does).  MASTER ONLY (2026-07-14):
	// replication is master-authoritative -- a replicant must never serialize.
	// The port's replicant runs the leg SM for JOINTS (task #50 accommodation),
	// whose transitions call ForceUpdate() and mark updateModel; serializing those
	// emitted derived/uninitialized state back into the stream (and the type-3
	// writer re-dispatches SetBodyAnimation on the WRITER -- the real-clock crash
	// rode this path with legAnimationState still 0xCDCDCDCD).  Discard replicant
	// marks instead.
	// WATCHER POLL (engine Simulation::PerformAndWatch tail, SIMULATE.cpp:460):
	// the engine path ends Perform() -> ExecuteWatchers() -> WriteSimulationUpdate().
	// This reconstruction replaced the whole performance but DROPPED the
	// ExecuteWatchers() step, so the entity-level POLLED audio watchers
	// (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)
	{
		footStepDecay -= (int)(dt * 1000.0f);
		if (footStepDecay <= 0)
		{
			footStepDecay = 0;
			footStep      = 0;
		}
	}

	if (getenv("BT_AUDIO_LOG")) { static int s_wd=0; if ((s_wd++ % 300)==0)
		DEBUG_STREAM << "[audio] mech watcher poll: delayed=" << (int)AreWatchersDelayed() << " audioWatchers=" << DebugAudioWatcherCount()
			<< " simFlags=0x" << std::hex << (unsigned)simulationFlags << std::dec
			<< "\n" << std::flush; }
	if (!AreWatchersDelayed())
		ExecuteWatchers();

	if (GetInstance() != ReplicantInstance)
		WriteSimulationUpdate(update_stream);
	else
		updateModel = 0;						// drop accommodation-path marks
}


//###########################################################################
//###########################################################################
//					Mech::AuthenticGroundAndCollide
//
// THE AUTHENTIC 1995 GROUND MODEL -- the ground/collision half of the MASTER
// per-frame performance FUN_004a9b5c (@4aa630-4aab5f, decoded from raw asm by
// the ground-model-decode workflow; task #15).  Replaces the bring-up
// gravity+push-out+floor-clamp baseline when GroundReal() is on.
//
// The model (no gravity exists anywhere in the mech):
//  1. MoveCollisionVolume() -- rebuilds localToWorld from localOrigin, places
//     the collision cylinder, and (masters) re-finds containedByNode in the
//     zone's BoundingBoxTree (engine Mover::MoveCollisionVolume, MOVER.cpp:782).
//  2. GROUND PROBE: q = origin + (0, collisionTemplate->minY, 0) -- the
//     ctor-LIFTED template bottom (BLH: 2.0 + 0.37969 = 2.37969 above the feet).
//  3. HEIGHT QUERY: FindBoundingBoxUnder(q, &h) -- the box-tree downward query
//     (BOXTREE.cpp:843); h = distance from q down to the highest solid top
//     under the column; h == -1.0f = MISS.
//  4. THE SNAP (master gate h > 1e-4, const @0x4ab16c): origin.y -= (h - lift)
//     => origin.y = surfaceY EXACTLY.  Absolute placement each frame: walks
//     up-slope within the lift window (implicit step allowance), drops
//     instantly on walk-offs.  On MISS: NOTHING -- Y holds (no gravity to
//     accumulate => the old y=13301 runaway is structurally impossible).
//  5. COLLISIONS: GetCurrentCollisions -> ProcessCollisionList (which calls
//     the Mech::ProcessCollision override below per contact).
//  6. RESPONSE POLICY on the accumulated damage:
//     == 0.00123f  crushable-CulturalIcon sentinel: the frame's move STANDS
//                  (restore the POST-SNAP saves; the bounce is undone).
//     >  0         BLOCKING hit (walls/cliffs/buildings): FULL FRAME REJECTION
//                  -- velocity zeroed, origin restored to START-OF-FRAME.
//                  Walls block by rejection, never by slide/climb.
//
// Deliberate deviations from the binary (each flagged by the verify pass):
//  - GetCollisionVolumeCount()>0 gate + containedByNode NULL-guard cover the
//    WHOLE block (the 1995 code was unconditional and would have crashed for
//    volume-less mechs; GetCurrentCollisions derefs the node unchecked).
//  - impactVel is saved with the post-snap saves (binary saves it after the
//    gather @4aa716 -- provably equivalent, the gather doesn't touch it).
//  - localToWorld is NOT re-synced after a plain snap (binary behavior: it
//    refreshes on the next frame's MoveCollisionVolume or a response path).
//
// DEFERRED (bound together; see the ProcessCollision banner): the pre-list
// collisionTemporaryState zero (@4aa741) + state tail; the 0x4a4/0x4a8/0x4b4
// caches; the self TakeDamageMessage; SetLegAnimation(0x20) crash anim on
// impactVel^2 > 40 (crash-clip slot mapping unverified -- logged instead);
// the gyro crunch feed (Gyroscope is a stub).
//###########################################################################
//###########################################################################
void
	Mech::AuthenticGroundAndCollide(Scalar dt, const Point3D &old_position)
{
	if (GetCollisionVolumeCount() <= 0 || collisionVolume == 0 || collisionTemplate == 0)
		return;

	// 1. place the volume + refresh containedByNode (@4aa630)
	MoveCollisionVolume();
	BoundingBoxTreeNode *node = GetMoverCollisionRoot();
	if (node == 0)
	{
		if (GroundLog())
		{
			static int s_once = 0;
			if (!s_once++) DEBUG_STREAM << "[ground] NO collision tree node -- "
				"zone tree unavailable; block skipped\n" << std::flush;
		}
		return;
	}

	// 2-3. ground probe + height query (@4aa633-4aa67d)
	Point3D q = localOrigin.linearPosition;
	q.y += collisionTemplate->minY;						// the LIFTED template bottom
	Scalar h = -1.0f;
	BoundingBox *floorBox = node->FindBoundingBoxUnder(q, &h);
	(void)floorBox;

	// 4. the snap (@4aa685-4aa6cc)
	if (h > 0.0001f)									// master gate, const @0x4ab16c
	{
		Scalar d = h - collisionTemplate->minY;			// signed: down OR bounded up
		localOrigin.linearPosition.y -= d;				// => origin.y = surfaceY exactly
		collisionVolume->minY -= d;						// keep the placed volume in sync
		collisionVolume->maxY -= d;
	}

	// 5. collisions (@4aa6cf-4aa764)
	// ASSISTANT GUARD (defensive; matches the authentic invariant): the engine
	// GetCurrentCollisions iterates collisionAssistant UNCHECKED (MOVER.cpp:894;
	// Check() compiles out).  In the real game exactly ONE master exists per pod
	// -- the viewpoint mech, which MakeViewpointEntity gave an assistant
	// (btl4app.cpp:591); every other mech was a replicant (no collision half).
	// A master WITHOUT an assistant (the BT_SPAWN_ENEMY dummy before its
	// StartCollisionAssistant) is a configuration the original never had --
	// ground it (probe/snap above) but skip the collision half.
	if (collisionAssistant == 0)
		return;
	Vector3D savedWorldVel = worldLinearVelocity;		// POST-SNAP saves
	Point3D  savedPos      = localOrigin.linearPosition;
	BoxedSolidCollisionList *cols = GetCurrentCollisions();
	Vector3D impactVel = localVelocity.linearMotion;
	// CONTACT EDGE (player only): a knockdown fires only on a FRESH strike (not blocked
	// last frame).  While the mech stays pressed against the obstacle it just BLOCKS --
	// no repeated knockdowns.  gWasBlocked is a single global, so gate it to the
	// viewpoint mech; other masters (the stationary dummy, MP replicant-driven masters)
	// keep the original per-fresh-block behavior and never touch the shared state.
	const bool isPlayer   = (application != 0
		&& (Entity *)this == application->GetViewpointEntity());
	const int  freshBlock = isPlayer ? (gBlockCooldown <= 0.0f ? 1 : 0) : 1;
	if (isPlayer && gBlockCooldown > 0.0f)
		gBlockCooldown -= dt;							// decay the out-of-contact window
	// Per-frame collision-state reset (@4aa741): the walk below re-accumulates it
	// per resolved contact, then it's pushed into the collisionState StateIndicator.
	collisionTemporaryState = 0;
	// COLLISION-DAMAGE ECONOMY GUARD: snapshot the TRUE frame motion for the
	// per-contact velocity restore in Mech::ProcessCollision (see mech.hpp) --
	// the list walk below reflects worldLinearVelocity at every StaticBounce,
	// and a multi-solid frame compounds those reflections into a garbage
	// velocity that priced mech-vs-mech damage 4x-40x too high (mp_a.log:32651).
	frameEntryWorldVelocity = savedWorldVel;
	// ram contact-edge decay: once contact with the last victim has been
	// broken for the linger window, re-arm the one-bump-one-hit gate
	if (ramContactLinger > 0.0f)
	{
		ramContactLinger -= dt;
		if (ramContactLinger <= 0.0f)
			ramLastVictim = 0;
	}
	Damage dmg;											// ctor zeroes damageAmount
	if (cols != 0)
		ProcessCollisionList(cols, dt, old_position, &dmg);

	// Push the frame's accumulated contact state into the audio-bound
	// collisionState indicator: 0->1 on a fresh contact fires the impact sound,
	// 1->0 when contact breaks; SetState only fires on change so a pressed-against
	// contact plays once, not every frame.  (@4aa741 sibling of the reset above.)
	collisionState.SetState((unsigned)collisionTemporaryState);

	// 6. response policy (@4aa76c-4aab5f)
	if (dmg.damageAmount == 0.00123f)					// crushable icon: move STANDS
	{
		worldLinearVelocity = savedWorldVel;
		localOrigin.linearPosition = savedPos;
		MoveCollisionVolume();
		dmg.damageAmount = 0.0f;
		// Binary @4aa7ce-4aa871 (task #56): the gyro CRUNCH.  n = Normalize(
		// dmg.damageForce) (the delta-v across the bounce, filled by the engine
		// Mover::ProcessCollisionList -- no new plumbing), then a torque along
		// n @ 0.4 (@4aa81e, 0x3ecccccd) + an upward impulse @ 0.2 (@4aa86c,
		// 0x3e4ccccd).  Normalize is unguarded in the binary too.
		if (gyroSubsystem != 0)
		{
			extern void GyroApplyDamageTorque (Subsystem *, Scalar, Scalar, Scalar, Scalar);
			extern void GyroApplyDamageImpulse(Subsystem *, Scalar, Scalar, Scalar, Scalar);
			Vector3D n;
			n.Normalize(dmg.damageForce);
			GyroApplyDamageTorque (gyroSubsystem, n.x, n.y, n.z, 0.4f);	// @4aa81e
			GyroApplyDamageImpulse(gyroSubsystem, 0.0f, 1.0f, 0.0f, 0.2f);	// @4aa86c
		}
		if (GroundLog())
			DEBUG_STREAM << "[ground] CRUNCH (crushable icon) at ("
				<< savedPos.x << ", " << savedPos.z << ")" << std::endl;
	}
	else if (dmg.damageAmount > 0.0f)					// blocking hit: FULL FRAME REJECTION
	{
		worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f);
		localOrigin.linearPosition = old_position;
		MoveCollisionVolume();
		if (isPlayer)
			gBlockCooldown = kBlockHysteresis;			// (re)arm the in-contact window
		// DEFERRED: self TakeDamageMessage{0x64, zone=-1} (inert today) +
		// material/normal/approach caches + collisionState indicators.
		Scalar iv2 = impactVel.x * impactVel.x + impactVel.y * impactVel.y
				   + impactVel.z * impactVel.z;
		if (iv2 > 40.0f)								// crash-anim threshold @0x4ab184
		{
			// CRASH / KNOCKDOWN (binary @4aaae2-4aab0b): bind the bump clip
			// (animationClips[0x20] = "bmp"; slot verified 0x5cc+0x80 == 0x64c)
			// on the LEG channel -- the mech plays the fall/stagger instead of
			// grinding into the wall; LegClipFinished case 32 drops back to
			// Standing at end-of-clip (slot1 @0x4a6b4d, binary-verified).  The
			// two action-request calls are the binary's FUN_004a4c54(this,1)
			// then (this,0x20) -- TWO calls, not one OR'd (fidelity verdict).
			// Guard 1: only when the bmp clip resolved (no substitute clip).
			// Guard 2 (BRING-UP, marked): don't REBIND while the crash clip is
			// already playing -- the bmp clip carries ~6.5 u/s of root motion
			// that presses back into the wall, and without the actionRequestFlags
			// consumers (bits 1/0x20 -- likely the drive suppressor during the
			// stagger, NOT yet reconstructed) the re-trigger restarts the clip
			// every frame.  Remove this guard when the flag consumers land.
			if (animationClips[0x20] != 0
				&& legStateAlarm.GetLevel() != 0x20
				&& freshBlock)							// only on a FRESH strike, not continuous grinding
			{
				SetLegAnimation(0x20);					// FUN_004a7fc4(this, 0x20)
				// SYNC THE DISPLAY CHANNEL (fix the persistent post-bump foot-slip): the
				// two-channel gait draws the legs from the BODY channel but moves the mech
				// from the LEG channel.  Staggering only the leg channel froze the motion
				// while the body kept animating -> the channels desynced PERMANENTLY (the
				// [sync] log: advSum 229 vs legSum 112 = displayed legs run ~2x the real
				// travel = foot slip that lasts until a full stop resyncs both to stand).
				// Stagger the BODY channel too so both freeze + recover from the SAME bmp
				// clip IN PHASE.  BodyClipFinished case 32 recovers it (mech2.cpp:308).
				SetBodyAnimation(0x20);
				ForceUpdate(1);							// FUN_004a4c54(this, 1): pose record
				ForceUpdate(0x20);						// FUN_004a4c54(this, 0x20): the
														// type-5 KNOCKDOWN record -- the
														// peer's replicant staggers too
				if (GroundLog())
				{
					static int s_binds = 0;
					DEBUG_STREAM << "[knock] BIND #" << ++s_binds
						<< " iv2=" << iv2 << "\n" << std::flush;
				}
			}
			else if (GroundLog())
			{
				static int s_noclip = 0;
				if (!s_noclip++) DEBUG_STREAM << "[ground] crash-anim trigger but "
					"bmp clip unresolved -- knockdown skipped\n" << std::flush;
			}
			if (GroundLog())
				DEBUG_STREAM << "[ground] BLOCK + CRASH (iv2=" << iv2
					<< " clip=" << animationClips[0x20] << ")" << std::endl;
		}
		else if (GroundLog())
			DEBUG_STREAM << "[ground] BLOCK dmg=" << dmg.damageAmount
				<< " at (" << old_position.x << ", " << old_position.z
				<< ")" << std::endl;
	}

	// 1-Hz telemetry (verification: h / y / hit-miss); INSIDE-SOLID detector:
	// h clamps to EXACTLY 0 when the probe point is at/inside a solid surface
	// (every FindDistanceBelowBounded is Max(...,0)-clamped in BT content) --
	// a sustained h==0 streak means the mech is buried in a solid.
	if (GroundLog())
	{
		static float s_accum = 0.0f;
		static int   s_zeroStreak = 0;
		if (h == 0.0f)
		{
			if (++s_zeroStreak == 10)
				DEBUG_STREAM << "[ground] INSIDE-SOLID streak at ("
					<< localOrigin.linearPosition.x << ", "
					<< localOrigin.linearPosition.y << ", "
					<< localOrigin.linearPosition.z << ")" << std::endl;
		}
		else
			s_zeroStreak = 0;
		s_accum += (float)dt;
		if (s_accum >= 1.0f)
		{
			s_accum = 0.0f;
			DEBUG_STREAM << "[ground] h=" << h
				<< (h == -1.0f ? " (MISS)" : (h == 0.0f ? " (ZERO/inside)" : " (hit)"))
				<< " lift=" << collisionTemplate->minY
				<< " pos=(" << localOrigin.linearPosition.x << ", "
				<< localOrigin.linearPosition.y << ", "
				<< localOrigin.linearPosition.z << ")" << std::endl;
		}
	}
}

//###########################################################################
//###########################################################################
//					Mech::ProcessCollision      (vtable slot +0x3c)
//
// @004abb40 -- THE REAL per-contact collision responder (task #15,
// ground-model-decode workflow; raw part_012.c:15206-15416).  The earlier
// draft here ("ResolveWeaponImpact") misread this function as a weapon sweep;
// it is the override of the ENGINE protected virtual Mover::ProcessCollision
// (MOVER.h:359-365), called per contact by Mover::ProcessCollisionList.
//
// Semantics (all raw-verified):
//  - BoxedSolid resolver (collisionVolume->ProcessCollision, BOXDISKS.cpp) --
//    on a miss NOTHING runs (the binary's entire body is inside the hit branch).
//  - StaticBounce computes the positional resolution + collision damage.
//  - Owner classification: a Mover owner -> separating-contact gate + (deferred)
//    mech-vs-mech damage dispatch; a CulturalIcon owner -> separating gate +
//    (deferred) crunch dispatch + the 0.00123f WALK-THROUGH SENTINEL when the
//    icon has no StoppingCollisionVolume flag (crushable trees/props).
//  - Plain UnscalableTerrain owners (ground/hills/cliff tiers/canyon walls)
//    match NEITHER branch: their StaticBounce damage stands and the caller
//    (AuthenticGroundAndCollide) rejects the whole frame -- walls/cliffs BLOCK.
//
// DEFERRED (marked, to add together in one fidelity pass):
//  - the TakeDamageMessage dispatches (self/other-mech/icon-crunch): zone==-1
//    is dropped by the engine base handler today (the Mech cylinder-lookup
//    override is unreconstructed) AND the binary message carries an inflictor
//    GLOBAL (DAT_0050b9ac) + inline name fields not yet mapped -- do NOT fake.
//  - the collisionTemporaryState tail (:15406-15413) -- BINARY-TAIL-DEFERRED,
//    bound with the pre-list zero @4aa741 in AuthenticGroundAndCollide.
//  - the 0x4a4/0x4a8/0x4b4 caches (material / local-frame surface normal /
//    approach speed) -- HUD/telemetry feeds, deferred with the indicators.
//
// Gated: falls through to the engine base when GroundReal() is off, so the
// baseline BT_COLLISION push-out path is byte-identical.
//###########################################################################
//###########################################################################

//
// Build a zone==-1 (unaimed) collision TakeDamageMessage from a resolved contact
// and dispatch it to 'victim'.  The victim turns the WORLD impact point into a
// damage zone -- a Mech via its cylinder table (STEP 6), an icon via its base
// handler (crushable props have no zones -> the base handler no-ops).  Faithful
// to the binary's mech-vs-mech / icon-crunch dispatches (:15324-15401): those
// built a raw DamageMessage{id=0x64, inflictor=DAT_0050b9ac, zone=-1}; we use the
// engine Entity::TakeDamageMessage ctor (as the weapon path does) with this mech
// as the inflictor.  The impact point is the world centre of the overlap slice.
//
static void
	BTDispatchCollisionDamage(
		Mech				*inflictor,
		Entity				*victim,
		const Damage		*resolved,
		BoxedSolidCollision	&collision)
{
	Damage dmg;
	dmg.damageType    = Damage::CollisionDamageType;
	// RAM ECONOMY NORMALIZATION [T3, evidence-forced 2026-07-12]: StaticBounce
	// prices the kinetic loss with the AUTHORED moverMass (~1.3e6 units) --
	// ~59,000 "points" for a 10 m/s ram, against zones whose armor absorbs
	// 1 point per point (scale = 1/armorPoints, armor 50-140: the [zone-armor]
	// dump).  The BINARY dispatches this raw number [T1 @part_012:15324] --
	// but on the pod network it only ever hit the local REPLICANT of the
	// victim, where it evaporated (the master never heard it; MECH.CPP:986
	// merely warns).  Our MP forwards replicant damage (task #47, required
	// for weapons), which surfaced the dormant 59K as a one-shot ram-kill.
	// Normalize by 1e-3 (the mass-unit/armor-point scale): a walking bump =
	// a few points, a full charge = tens -- the armor-table economy.
	dmg.damageAmount  = resolved->damageAmount * 0.001f;
	dmg.surfaceNormal = resolved->surfaceNormal;
	dmg.impactPoint   = Point3D(
		(collision.collisionSlice.minX + collision.collisionSlice.maxX) * 0.5f,
		(collision.collisionSlice.minY + collision.collisionSlice.maxY) * 0.5f,
		(collision.collisionSlice.minZ + collision.collisionSlice.maxZ) * 0.5f);
	dmg.burstCount    = 1;

	Entity::TakeDamageMessage take_damage(
		Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
		inflictor->GetEntityID(), -1 /*unaimed -> receiver's cylinder resolves*/, dmg);
	victim->Dispatch(&take_damage);

	if (GroundLog())
		DEBUG_STREAM << "[collide] dmg=" << resolved->damageAmount
			<< " -> victim class=" << (int)victim->GetClassID()
			<< " (zone==-1, resolved by receiver)\n" << std::flush;

	// RAM ECONOMY TELEMETRY (the MP ram one-shot investigation): every
	// dispatched ram, with the exact velocity StaticBounce priced it from --
	// the decisive evidence channel for any future out-of-economy amount.
	if (getenv("BT_MP_NET"))
	{
		const Vector3D &v = inflictor->GetWorldLinearVelocity();
		DEBUG_STREAM << "[collide-tx] " << inflictor->GetEntityID()
			<< " rams " << victim->GetEntityID()
			<< " dmg=" << resolved->damageAmount
			<< " mass=" << inflictor->moverMass
			<< " e=" << inflictor->elasticityCoefficient
			<< " |v|=" << (Scalar)sqrtf(v.x*v.x + v.y*v.y + v.z*v.z)
			<< " at(" << dmg.impactPoint.x << "," << dmg.impactPoint.y
			<< "," << dmg.impactPoint.z << ")" << std::endl;
	}
}

void
	Mech::ProcessCollision(
		Scalar				time_slice,
		BoxedSolidCollision	&collision,
		const Point3D		&old_position,
		Damage				*damage)
{
	if (!GroundReal())
	{
		Mover::ProcessCollision(time_slice, collision, old_position, damage);
		return;
	}

	// COLLISION-DAMAGE ECONOMY GUARD (see mech.hpp frameEntryWorldVelocity):
	// this runs once PER CONTACT in the frame's ProcessCollisionList walk, and
	// the StaticBounce below reflects worldLinearVelocity (+= delta_v) every
	// time.  Restore the TRUE frame-entry motion first so a multi-solid frame
	// (mech + terrain solids + debris) cannot compound the reflections into an
	// amplified velocity: every contact -- and every dispatched mech-vs-mech
	// damage amount -- is priced at the mech's real approach speed, which is
	// all the 1995 binary's StaticBounce ever saw (its ground was a heightfield
	// probe, never a collision-list entry).  The post-list velocity is
	// overwritten by the response policy / next frame's derive regardless.
	worldLinearVelocity = frameEntryWorldVelocity;

	// --- the BoxedSolid resolver (:15302-15304); miss => nothing runs -------
	Scalar penetration = 0.0f;
	if (!collisionVolume->ProcessCollision(collision, worldLinearVelocity,
			lastCollisionList, &damage->surfaceNormal, &penetration))
	{
		return;
	}

	if (penetration > time_slice)						// Max_Clamp (:15306-15308)
		penetration = time_slice;
	Scalar r          = penetration / time_slice;
	Scalar elasticity = elasticityCoefficient;			// :15310 (engine Mover member)
	Scalar friction   = frictionCoefficient;			// :15311

	Simulation *ownerSim =
		collision.GetTreeVolume()->GetOwningSimulation();	// tagPointer (:15312)

	damage->damageAmount = StaticBounce(old_position, time_slice, r,
		damage->surfaceNormal, &elasticity, minimumBounceSpeed, &friction);	// :15313-15315

	Entity *owner = (Entity *)ownerSim;

	// CONTACT TELEMETRY (task #15 verification): identify every resolved contact.
	if (GroundLog())
	{
		BoxedSolid *ws = collision.GetTreeVolume();
		DEBUG_STREAM << "[contact] solidType=" << (int)ws->solidType
			<< " mat=" << (int)ws->materialType
			<< " ownerClass=" << (owner ? (int)owner->GetClassID() : -1)
			<< " slice X[" << collision.collisionSlice.minX << "," << collision.collisionSlice.maxX
			<< "] Y[" << collision.collisionSlice.minY << "," << collision.collisionSlice.maxY
			<< "] Z[" << collision.collisionSlice.minZ << "," << collision.collisionSlice.maxZ
			<< "] pen=" << penetration << " dmg=" << damage->damageAmount
			<< "\n" << std::flush;
	}

	// --- Mover owner (another mech / vehicle) (:15316-15358) ----------------
	if (owner != 0 && owner->IsDerivedFrom(*Mover::GetClassDerivations()))
	{
		Vector3D rel;
		Vector3D ownerVel = owner->GetWorldLinearVelocity();	// virtual (+0x20)
		rel.Subtract(worldLinearVelocity, ownerVel);
		// Separating-contact gate.  MEMBER dot (Normal::operator*, NORMAL.h:29)
		// -- NEVER the free Dot() recon stub (a variadic no-op returning 0
		// that would silently defeat this gate).
		if (damage->surfaceNormal * rel < -1.0e-4f)			// _DAT_004ac044 (:15320-15323)
			return;
		// Mech-vs-mech (:15324-15358): the binary gates on owner ClassID == Mech
		// (0xbb9) then dispatches the collision DamageMessage (zone==-1) to the
		// other mech.  STEP 6 (its cylinder table) now resolves the zone, so this
		// is UNBLOCKED (was deferred: zone==-1 used to be dropped).
		if (owner->IsDerivedFrom(*Mech::GetClassDerivations()))
		{
			// RAM CONTACT EDGE (see mech.hpp): dispatch damage only on a FRESH
			// contact with this victim; while pressed, refresh the linger so
			// sustained contact (held stick / respawn overlap) stays a BLOCK,
			// not a 60Hz damage grinder -- the binary's bounce-separation made
			// this edge implicit; our gait-derived velocity needs it explicit.
			if (owner == ramLastVictim && ramContactLinger > 0.0f)
			{
				ramContactLinger = 0.35f;			// still pressed: refresh, no damage
			}
			else
			{
				ramLastVictim    = owner;
				ramContactLinger = 0.35f;
				BTDispatchCollisionDamage(this, owner, damage, collision);
			}
		}
	}

	// --- CulturalIcon owner (buildings/trees/props) (:15361-15404) ----------
	if (owner != 0 && owner->IsDerivedFrom(*CulturalIcon::GetClassDerivations()))
	{
		Logical stopping =
			((CulturalIcon *)owner)->IsStoppingCollisionVolume();	// flags & 0x8000 (:15362)
		// Separating gate vs a STATIC icon (owner velocity == {0,0,0}, inlined
		// by the 1995 compiler) (:15364-15368).
		if (damage->surfaceNormal * worldLinearVelocity < -1.0e-4f)
			return;
		// Crunch (:15369-15401): dispatch the collision damage to the icon
		// (zone==-1) BEFORE the walk-through sentinel overwrites the amount.
		// Buildings with damage zones resolve via their handler; crushable props
		// have none -> the base handler no-ops.  UNBLOCKED by STEP 6.
		BTDispatchCollisionDamage(this, owner, damage, collision);
		if (!stopping)
			damage->damageAmount = 0.00123f;	// walk-through sentinel 0x3aa137f4 (:15402-15404)
	}

	// Contact-state accumulator tail (decomp part_012.c:15406-15413).  The
	// authentic split is InitialHit(1) vs Slide(2), keyed on the two collision
	// floats @0x240/0x244 -- both are still fieldAt() stubs (return 0), so the
	// Slide branch can't be computed faithfully yet.  A resolved contact IS an
	// 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;
}


//###########################################################################
//###########################################################################
//					FeedHeatCapacityGauge / FeedHeatLevelGauge   (cockpit)
//
// @004ac04c / @004ac064
//
// Two-line gauge callbacks: push the current heat capacity / heat level into
// the value field (+0xc) of the object pointed to by this+0x2ec.  Registered
// as a per-frame gauge value source.
//
// NOTE: this+0x2ec is read by Simulate as a terrain "groundRef" (its +8 is a
// base height), so labelling it a GraphicGauge here is uncertain -- the two
// uses share a byte offset.  The heatLevel/heatCapacity *sources* (0x518/0x51c)
// are certain Mech members; the *sink* identity is best-effort.
//###########################################################################
//###########################################################################
void
	Mech::FeedHeatCapacityGauge()
{
	*(Scalar *)(*(int *)(this + 0x2ec) + 0xc) = heatCapacity;		// 0x51c -> +0xc
}

void
	Mech::FeedHeatLevelGauge()
{
	*(Scalar *)(*(int *)(this + 0x2ec) + 0xc) = heatLevel;			// 0x518 -> +0xc
}


//###########################################################################
//###########################################################################
//					LookupDamageState        (static; keyword parse)
//
// @004ac194
//
// Map a damage-zone state keyword to its enum value, using the (name,value)
// table at .rdata:0050de74.  Returns 1 and writes *out on a match, else 0.
// Recognized keywords (from the table):
//     Destroyed=0  Damaged=1  CoolantLeaking=2  Overheating=3
//     AmmoBurning=4  Jammed=5  BadPower=6
//###########################################################################
//###########################################################################
//
// Damage-state keyword table @0x50de74 (shared with mechsub's status states).
//
struct DamageStateEntry { const char *name; int value; };
static const DamageStateEntry kDamageStateTable[] =
{
	{ "Destroyed",		0 },
	{ "Damaged",		1 },
	{ "CoolantLeaking",	2 },
	{ "Overheating",	3 },
	{ "AmmoBurning",	4 },
	{ "Jammed",			5 },
	{ "BadPower",		6 },
	{ 0,				0 }
};

/*static*/ Logical
	Mech::LookupDamageState(const char *keyword, int *out)
{
	for (const DamageStateEntry *e = kDamageStateTable;	// &PTR_s_Destroyed_0050de74
	     e->name != 0; ++e)
	{
		if (Strcmp(keyword, e->name) == 0)				// FUN_004d4b58
		{
			*out = e->value;
			return True;
		}
	}
	return False;
}

//===========================================================================//
// End of recovered mech4.cpp slice.
//===========================================================================//

//
// DrawWeaponBeams (task #51 extraction) -- the per-weapon beam render walk
// (task #33), extracted from the player-only drive block so it runs for
// EVERY mech: the local player, the solo dummy, and MP REPLICANTS (whose
// emitters now carry live replicated discharge state via the Emitter update
// records; without this the peer's beams applied but never drew).
//
void
	Mech::DrawWeaponBeams(Scalar dt)
{
	extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float);
	const Scalar ttl1 = (dt > 1e-4f) ? dt : 1e-4f;	// one-frame life (redrawn while on)
	static int s_beamStateLog = 0;
	int energyOrdinal = -1;						// Nth energy weapon (port assignment)
	for (int wi = 0; wi < GetSubsystemCount(); ++wi)
	{
		Subsystem *ws = GetSubsystem(wi);
		if (ws == 0)
			continue;
		// EXACT class filter: Emitter (0xBC8=3016) or PPC (0xBD4=3028).
		// The derivation check matched too broadly here (the recon
		// derivation chains are shared stubs for some subsystems --
		// a Sensor and the MissileLauncher passed and drew garbage
		// from misinterpreted offsets).
		const int wcid = (int)ws->GetClassID();
		if (wcid != 3016 && wcid != 3028)
			continue;
		++energyOrdinal;
		Emitter *em = (Emitter *)ws;
		// replicant beams: age the replicated discharge locally (a lost
		// beam-END record otherwise pins the beam on forever -- see
		// Emitter::ReplicantServiceBeam)
		if (GetInstance() == Entity::ReplicantInstance)
			em->ReplicantServiceBeam(ttl1);
		if (!em->BeamOn())
			continue;
		Point3D mz;
		em->MuzzlePoint(mz);						// LIVE muzzle (tracks the gun)
		// MOUNT FALLBACK: when the weapon's mount segment doesn't
		// resolve, GetMuzzlePoint returns the mech ORIGIN (feet).
		// Assign this weapon its own gun-port segment by roster
		// ordinal (the same port set the old visual used) so each
		// energy weapon keeps a stable muzzle on the arms.
		if (mz.y - localOrigin.linearPosition.y < 1.0f)
		{
			static const char *const kGunPorts[] =
				{ "siterugunport", "sitelugunport", "siterdgunport",
				  "siteldgunport", "siterbgunport", "sitelbgunport" };
			// PER-MECH cache (task #51): the walk now runs for EVERY mech
			// (player + replicants); the old process-wide statics would serve
			// the PLAYER's segment pointers as the replicant's muzzles.  Key
			// the slot by owner and re-resolve on mismatch.
			static EntitySegment *s_portCache[64];
			static int            s_portTried[64];
			static Mech          *s_portOwner[64];
			if (energyOrdinal >= 0 && energyOrdinal < 64)
			{
				if (!s_portTried[energyOrdinal]
					|| s_portOwner[energyOrdinal] != this)
				{
					s_portTried[energyOrdinal] = 1;
					s_portOwner[energyOrdinal] = this;
					s_portCache[energyOrdinal] = GetSegment(
						CString(kGunPorts[energyOrdinal % 6]));
				}
				if (s_portCache[energyOrdinal] != 0)
				{
					AffineMatrix mw;
					mw.Multiply(s_portCache[energyOrdinal]->GetSegmentToEntity(),
						localToWorld);
					mz = mw;					// Point3D = matrix translation
				}
			}
		}
		const Point3D &bend = em->BeamEndpoint();	// the fire's world hit point
		// authored per-weapon colour; unset (-1) -> the ER-laser red
		RGBColor pc = em->PipColor();
		float r = (float)pc.Red, g = (float)pc.Green, b = (float)pc.Blue;
		if (r < 0.0f || g < 0.0f || b < 0.0f) { r = 0.78f; g = 0.08f; b = 0.02f; }
		// PPC (classID 0xBD4 = 3028): a thicker, brighter bolt than a laser tube.
		const int isPPC = ((int)em->GetClassID() == 3028);
		// ONE draw per beam, at the model's NATURAL width: the weapon's
		// own tube (ERMLASER radius 0.22u, PPC bolt 0.62u) IS the beam
		// -- the old inflated two-layer glow/core (3.0x + 0.9x widths)
		// drew fat cartoon cylinders 13x the authored size.  The tint
		// modulates the scrolling grit; thin natural tubes stay under
		// saturation without a hand-dimmed core.
		extern void BTPushBeamKind(float,float,float, float,float,float,
			unsigned, float, float, int);
		unsigned tint =
			(((unsigned)(40.0f + r * 215.0f) & 0xFF) << 16) |
			(((unsigned)(40.0f + g * 215.0f) & 0xFF) <<  8) |
			 ((unsigned)(40.0f + b * 215.0f) & 0xFF);
		// PHANTOM-BEAM FORENSICS (BT_FIRE_LOG): identify every drawn beam --
		// owner/instance, roster slot, class, discharge state, colour, ends.
		if (getenv("BT_FIRE_LOG"))
		{
			static int s_bd = 0;
			if ((++s_bd % 30) == 1)						// ~2Hz per sustained beam
				DEBUG_STREAM << "[beam-draw] mech=" << GetEntityID()
					<< " inst=" << (int)GetInstance()
					<< " slot=" << wi << " cid=" << wcid
					<< " timer=" << em->DischargeTimer()
					<< " pip=(" << r << "," << g << "," << b << ")"
					<< " mz=(" << mz.x << "," << mz.y << "," << mz.z << ")"
					<< " end=(" << bend.x << "," << bend.y << "," << bend.z
					<< ")" << std::endl;
		}
		BTPushBeamKind(mz.x, mz.y, mz.z, bend.x, bend.y, bend.z,
			tint, ttl1, 1.0f /* natural model width */,
			isPPC ? 1 : 0 /* ppc.bgf : ermlaser.bgf */);
		if (getenv("BT_BEAM_LOG") && (s_beamStateLog++ % 31) == 0)	// 31: coprime with the 5-beam volley (a %30 sampler aliased to one weapon)
			DEBUG_STREAM << "[beam] " << (isPPC ? "PPC" : "laser") << " #" << wi
				<< " mz=(" << mz.x << "," << mz.y << "," << mz.z
				<< ") end=(" << bend.x << "," << bend.y << "," << bend.z
				<< ") rgb=(" << r << "," << g << "," << b << ")" << std::endl;
	}
}
