Collision damage applied: mech-vs-mech + icon-crunch via the STEP-6 unaimed path

The two deferred TakeDamage dispatches in Mech::ProcessCollision now fire,
unblocked by STEP 6 (the cylinder hit-location override that resolves zone==-1):

  - Mover branch (:15324-15358): on a collision with another Mech, dispatch the
    collision damage to it.
  - CulturalIcon branch (:15369-15401): crunch dispatch to a building/tree/prop,
    before the walk-through sentinel overwrites the amount.

Both go through a new file-scope helper BTDispatchCollisionDamage, which builds
an Entity::TakeDamageMessage{zone==-1} (the engine ctor -- same idiom as the
weapon-impact path) with the world centre of the overlap slice as the impact
point and this mech as the inflictor, then Dispatch()es it to the victim.  The
receiver 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 -> a
harmless no-op).  Terrain (walls/hills) matches neither branch, so it still
BLOCKS without damage (faithful to the binary).

Faithful to the binary's raw DamageMessage dispatch (:15324-15401) but via the
engine's named TakeDamageMessage API (no databinding-trap field-by-field build);
the binary's inflictor global DAT_0050b9ac is a sentinel EntityID (only ever
read, never set -> a collision has no "shooter"), so this mech is the inflictor.

Verify: builds clean; reachability GUARANTEED (Mover::ProcessCollisionList calls
the VIRTUAL ProcessCollision -> Mech::ProcessCollision, on the active
AuthenticGroundAndCollide path); stable across runs; both mechs build their
cylinder tables.  The live dispatch was not captured headlessly (the solo
auto-walker never rammed a tree/mech), but the path is proven reachable and
composed of runtime-verified pieces (the weapon TakeDamage path + STEP 6).

Also validated STEP 6's height ref: collisionTemplate->maxY ~= 7.1 (a real mech
height), confirming CylinderReferenceHeight reads a height (not the heat value
the mech+0x2ec dual-labeling hinted at).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-08 08:54:21 -05:00
co-authored by Claude Opus 4.8
parent d07ac7dd49
commit 2af401eef8
2 changed files with 63 additions and 7 deletions
+52 -3
View File
@@ -2600,6 +2600,45 @@ void
// 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;
dmg.damageAmount = resolved->damageAmount;
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;
}
void
Mech::ProcessCollision(
Scalar time_slice,
@@ -2660,8 +2699,14 @@ void
// that would silently defeat this gate).
if (damage->surfaceNormal * rel < -1.0e-4f) // _DAT_004ac044 (:15320-15323)
return;
// DEFERRED: mech-vs-mech TakeDamageMessage{0x64, len 0x12, zone=-1}
// dispatch to the other mech (:15324-15358) -- see the banner.
// 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()))
{
BTDispatchCollisionDamage(this, owner, damage, collision);
}
}
// --- CulturalIcon owner (buildings/trees/props) (:15361-15404) ----------
@@ -2673,7 +2718,11 @@ void
// by the 1995 compiler) (:15364-15368).
if (damage->surfaceNormal * worldLinearVelocity < -1.0e-4f)
return;
// DEFERRED: crunch TakeDamageMessage to the icon (:15369-15401).
// 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)
}