From 2af401eef894efc76d6a142a5529ad0dd0a27d55 Mon Sep 17 00:00:00 2001 From: arcattack Date: Wed, 8 Jul 2026 08:54:21 -0500 Subject: [PATCH] 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 --- context/open-questions.md | 15 +++++++--- game/reconstructed/mech4.cpp | 55 ++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/context/open-questions.md b/context/open-questions.md index 40c9afa..b669baf 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -71,10 +71,17 @@ authentic path scoped. - Authentic per-mech TURN-RATE constant (currently a bring-up constant rate). - Body-callback gimp handlers (states 16-19, targets 0x70b2/0x7161 undecoded → fall back to stand); airborne callbacks (`FUN_004a6344`/`FUN_004a7970`). -- Wall-block-vs-climb tuning; collision DAMAGE application (computed, not applied). **UNBLOCKED 2026-07-08:** - STEP 6 (cylinder hit-location) now resolves an unaimed impact point → zone, so collision damage can be - applied through `Mech::TakeDamageMessageHandler` (a zone==−1 hit now resolves instead of dropping). The - remaining piece is issuing the computed collision Damage into the TakeDamage path (currently computed only). +- Wall-block-vs-climb tuning. **✅ Collision DAMAGE application — DONE 2026-07-08 [T2].** The two deferred + dispatches in `Mech::ProcessCollision` (mech4.cpp: mech-vs-mech `:15324-15358` + icon-crunch `:15369-15401`) + now fire: on a collision with another Mech or a CulturalIcon, an `Entity::TakeDamageMessage{zone==−1}` is + dispatched to the victim (via `BTDispatchCollisionDamage`, the engine ctor — same as the weapon path), and + STEP 6's cylinder table resolves the impact point → a zone on the receiver. Terrain (walls/hills) matches + neither branch → blocks without damage (faithful). Reachability is guaranteed (`Mover::ProcessCollisionList` + calls the **virtual** `ProcessCollision` → `Mech::ProcessCollision`); built + stable. The live dispatch + wasn't captured headlessly (the solo auto-walker never rammed a tree/mech), but the path is proven reachable + and composed of runtime-verified pieces. **Note:** validated STEP 6's height ref along the way — + `collisionTemplate->maxY ≈ 7.1` (a real mech height), confirming `CylinderReferenceHeight` reads a height, + not the heat value the `mech+0x2ec` dual-labeling (heat-gauge sink vs groundRef, mech4.cpp:2697) hinted at. - **✅ Cylinder hit-location (STEP 6) — DONE 2026-07-08 [T2].** Built + runtime-verified: the `dmgtable.cpp` classes now have real storage + a working `ResolveHit→zone`, the mech ctor loads the type-0x1d table by the DamageZoneStream name (`[cyl] table 'bhk1' layers=7`), and `Mech::TakeDamageMessageHandler` resolves diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 6f3e9bc..1b582bc 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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) }