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) }