#83: the COLLISION DIVERT -- type-0 damage prices as internal rattle, never armor
The crash self-damage reconstruction (9c83a46) exposed its missing second
half: the binary's TakeDamage hub DIVERTS damageType 0 (@0x4a0368) into
FUN_0049ffcc -- an export-gap distributor recovered by raw disasm -- so
collision damage NEVER reaches the zone/armor loop. Without the divert the
mover's raw kinetic-energy figure (60-ton mech: ~1000+ per wall tap, 90k+ on
a hard slam) fell into the WEAPON loop: 'tapped a wall and died instantly'.
The distributor, byte-anchored: gate on the owning player's advancedDamage
copy (+0x268 -- the manual's 'splash/collision damage' technician setting);
scale = (2000/moverMass) / (100 km/h in u/s)^2 / (1 - elasticity^2)
(tbyte 1/3.6 @0x4a0148; mass @+0x20c; elasticity @+0x244); scaled < 0.5 is
FREE; else Round(2x) sub-hits of 0.5 land on random INTERNAL subsystems
(HeatSink family / Gyroscope / Torso, drawn by collisionCriticalHitWeight
@0x10C) via ApplyDamageAndMeasure (@0x4ac07c). Bridges: family derivations
(heat/gyro/torso.cpp), BTPlayerAdvancedDamageOn (btplayer.cpp).
Verified: bhk1 wall test -- 94k slam = 4.2 rattle pts in 8 sub-hits, taps
free (0.049 < 0.5), armor untouched. A/B: arena wall-grind now 0 deaths,
27 rattle events, the limper STAYS GIMPED and peers' replicants re-enter the
gimp cycle between bounded staggers; grass circling unchanged (198 replicant
gimp entries). [crashdmg]/[colldmg] probes document the pricing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9c83a468db
commit
14ff3519ac
@@ -1829,6 +1829,14 @@ Logical
|
||||
// these read the COMPILED named members the scoring handlers write, replacing the
|
||||
// earlier raw-offset reads (pilot+0x27c / +0x200) that don't match our layout.
|
||||
//
|
||||
// #83 (collision rattle): the technician "splash/collision damage" setting --
|
||||
// the binary's collision distributor gates on the owning player's
|
||||
// advancedDamage copy 2 (@0x268, FUN_0049ffcc @0x49ffde). TU-safe bridge.
|
||||
int BTPlayerAdvancedDamageOn(void *player_v)
|
||||
{
|
||||
return (player_v != 0) ? ((BTPlayer *)player_v)->advancedDamageOn2 : 0;
|
||||
}
|
||||
|
||||
int BTPilotKills(void *pilot)
|
||||
{
|
||||
int k = pilot ? ((BTPlayer *)pilot)->GetKillCount() : 0;
|
||||
|
||||
@@ -436,6 +436,7 @@ class DropZone__ReplyMessage;
|
||||
friend int BTPlayerRoleLocksAdvanced(void *); // the FUN_004ac9c8 bridge (task #12): experienceLevel == 0
|
||||
friend int BTPlayerExperienceSimLive(void *); // +0x25c reader (issue #2): jams / lights / powersub short path
|
||||
friend int BTPlayerExperienceHeatModelOn(void *); // +0x260 reader (issue #2): FUN_004ad7d4 heat-model gate
|
||||
friend int BTPlayerAdvancedDamageOn(void *); // +0x268 reader (#83): FUN_0049ffcc collision-rattle gate
|
||||
// TARGET DESIGNATION (Gitea #48/#57): the mapper used to write the target
|
||||
// RAW at `pilot + 0x284` -- the BINARY's objectiveMech offset, which on our
|
||||
// compiled object is `deathPending` (the respawn latch). These bridges
|
||||
|
||||
@@ -1092,3 +1092,15 @@ void
|
||||
{
|
||||
ResetToInitialState(); // @004b2678 (takes no arg)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// #83 bridge: the collision-damage distributor (mech.cpp, FUN_0049ffcc) tests
|
||||
// roster members against this family's derivation chain (binary GUID 0x50fdc0).
|
||||
// Lives here because Gyroscope is a complete type only in this TU.
|
||||
//
|
||||
Derivation *
|
||||
BTGyroscopeFamily()
|
||||
{
|
||||
return &Gyroscope::ClassDerivations;
|
||||
}
|
||||
|
||||
@@ -1282,3 +1282,15 @@ int BTCondenserNumber(::Subsystem *sub)
|
||||
{
|
||||
return (sub != 0) ? ((Condenser *)sub)->condenserNumber : -1; // +0x1D4
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// #83 bridge: the collision-damage distributor (mech.cpp, FUN_0049ffcc) tests
|
||||
// roster members against this family's derivation chain (binary GUID 0x50e590).
|
||||
// Lives here because HeatSink is a complete type only in this TU.
|
||||
//
|
||||
Derivation *
|
||||
BTHeatSinkFamily()
|
||||
{
|
||||
return HeatSink::GetClassDerivations();
|
||||
}
|
||||
|
||||
@@ -807,6 +807,77 @@ static int
|
||||
// id-0x16 damage/kill reports (see the tail comment) [T3/T4 -- decoded in
|
||||
// context/combat-damage.md].
|
||||
//
|
||||
//
|
||||
// @0049ffcc -- the COLLISION-DAMAGE DISTRIBUTOR (#83; raw disasm
|
||||
// scratchpad/night6/gap_49ffcc.txt -- an export-gap fn reached only from the
|
||||
// TakeDamage hub's damageType==0 divert @0x4a0368). Collision damage is the
|
||||
// manual's "splash/collision damage" TECHNICIAN SETTING: gated on the owning
|
||||
// player's advancedDamage copy (+0x268, @0x49ffde), priced against a
|
||||
// 100 km/h reference impact, and applied as 0.5-point rattle crits to random
|
||||
// INTERNAL subsystems -- armor is never touched.
|
||||
// scale = (2000 / moverMass) / (100 * 1/3.6)^2 / (1 - elasticity^2)
|
||||
// (tbyte @0x4a0148 = 0.2777778 = 1/3.6 km/h->u/s; 2000f @0x4a0154;
|
||||
// mass @+0x20c; elasticity @+0x244 -- Mover layout walk)
|
||||
// amount = raw * scale; amount < 0.5f (@0x4a015c) -> FREE (taps cost nothing)
|
||||
// n = Round(amount * 2) sub-hits (@0x4a0051) of amount/n each
|
||||
// each landing on ONE roster subsystem drawn by cumulative
|
||||
// collisionCriticalHitWeight (@0x10C) vs a [0,1) roll, restricted to the
|
||||
// HeatSink family / Gyroscope / Torso (derivation GUIDs 0x50e590 / 0x50fdc0 /
|
||||
// 0x510b08 -- all MechSubsystem-based, which is why +0x10C is valid on every
|
||||
// eligible entry). An un-won roll (total weight < roll) lands nowhere --
|
||||
// faithful: the binary does not normalize the weights.
|
||||
//
|
||||
void
|
||||
Mech::DistributeCollisionDamage(Damage *dmg)
|
||||
{
|
||||
extern int BTPlayerAdvancedDamageOn(void *player_v); // btplayer.cpp (+0x268)
|
||||
void *player = GetPlayerLink(); // mech+0x190 (ENTITY.h:430)
|
||||
if (player == 0 || !BTPlayerAdvancedDamageOn(player)) // @0x49ffde the technician gate
|
||||
return;
|
||||
|
||||
const Scalar c = 100.0f * 0.27777779f; // 100 km/h in u/s
|
||||
Scalar denom = 1.0f - elasticityCoefficient * elasticityCoefficient; // @0x244
|
||||
Scalar scale = ((2000.0f / moverMass) / (c * c)) / denom; // @0x49ffff-0x4a0027
|
||||
dmg->damageAmount *= scale; // @0x4a0030
|
||||
if (dmg->damageAmount < 0.5f) // @0x4a003c: a tap is FREE
|
||||
return;
|
||||
|
||||
int n = Round(dmg->damageAmount * 2.0f); // @0x4a0051 (FUN_004dcd94)
|
||||
if (n < 1)
|
||||
n = 1;
|
||||
dmg->damageAmount /= (Scalar)n; // @0x4a0065
|
||||
|
||||
if (getenv("BT_DMG_LOG"))
|
||||
DEBUG_STREAM << "[colldmg] rattle " << (dmg->damageAmount * n)
|
||||
<< " pts in " << n << " sub-hits (scale=" << scale << ")"
|
||||
<< std::endl << std::flush;
|
||||
|
||||
extern Derivation *BTHeatSinkFamily(void); // heat.cpp (0x50e590)
|
||||
extern Derivation *BTGyroscopeFamily(void); // gyro.cpp (0x50fdc0)
|
||||
extern Derivation *BTTorsoFamily(void); // torso.cpp (0x510b08)
|
||||
for (int i = 0; i < n; ++i) // @0x4a006b
|
||||
{
|
||||
Scalar threshold = RandomUnit(); // @0x4a007d (FUN_00408050)
|
||||
Scalar acc = 0.0f;
|
||||
for (int b = 0; b < GetSubsystemCount(); ++b) // @0x4a0124 (count @+0x124)
|
||||
{
|
||||
Subsystem *s = GetSubsystem(b); // roster @+0x128
|
||||
if (s == 0)
|
||||
continue;
|
||||
if (!s->IsDerivedFrom(*BTHeatSinkFamily()) // @0x4a0092
|
||||
&& !s->IsDerivedFrom(*BTGyroscopeFamily()) // @0x4a00b2
|
||||
&& !s->IsDerivedFrom(*BTTorsoFamily())) // @0x4a00d2
|
||||
continue;
|
||||
acc += ((MechSubsystem *)s)->CollisionCritWeight(); // @0x4a00f2 (+0x10C)
|
||||
if (acc < threshold) // @0x4a010a
|
||||
continue;
|
||||
((MechSubsystem *)s)->ApplyDamageAndMeasure(*dmg); // @0x4a0117 (FUN_004ac07c)
|
||||
break; // @0x4a0121 -> next sub-hit
|
||||
}
|
||||
}
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
void
|
||||
Mech::TakeDamageMessageHandler(TakeDamageMessage *message)
|
||||
{
|
||||
@@ -860,6 +931,23 @@ void
|
||||
(float)(message->damageData.impactPoint.z - localOrigin.linearPosition.z));
|
||||
}
|
||||
|
||||
//
|
||||
// @0x4a0368 -- the COLLISION DIVERT (#83, closing the #82 chain). Damage
|
||||
// type 0 == the mover's collision damage (what the crash response policy
|
||||
// forwards). It NEVER reaches the zone/armor loop below: it is priced +
|
||||
// distributed as INTERNAL RATTLE by DistributeCollisionDamage (@0049ffcc)
|
||||
// and the handler is done. This was the missing half of the crash
|
||||
// self-damage reconstruction -- without the divert, the raw kinetic-energy
|
||||
// amounts (a 60-ton mech prices ~1000+ per wall tap) fell through into the
|
||||
// WEAPON loop and a single bump killed the mech (run-14 field report).
|
||||
//
|
||||
if (message->damageData.damageType == 0)
|
||||
{
|
||||
DistributeCollisionDamage(&message->damageData);
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
|
||||
DamageLookupTable *table = (DamageLookupTable *)damageLookupTable; // named member (Wword absorbs!)
|
||||
if (message->invalidDamageZone && table != 0)
|
||||
{
|
||||
|
||||
@@ -1197,6 +1197,11 @@ protected:
|
||||
// Mech override of Entity::TakeDamageMessageHandler: resolve an unaimed
|
||||
// (invalidDamageZone) hit's zone via the cylinder table, then base-route.
|
||||
void TakeDamageMessageHandler(TakeDamageMessage *message);
|
||||
// @0049ffcc (#83): the COLLISION-damage distributor the handler's
|
||||
// damageType==0 divert (@0x4a0368) routes to -- prices the mover's
|
||||
// kinetic figure vs a 100 km/h reference and lands 0.5-point rattle
|
||||
// crits on weighted random internal subsystems. Never touches armor.
|
||||
void DistributeCollisionDamage(Damage *dmg);
|
||||
// Mech override of Entity::PlayerLinkMessageHandler (@0049f624): the
|
||||
// base resolves mech->player; BT adds the REVERSE link
|
||||
// player->playerVehicle = this on EVERY node (replicants included --
|
||||
|
||||
@@ -6871,6 +6871,15 @@ void
|
||||
fallScalar = -(fallDirection.x * impactVel.x
|
||||
+ fallDirection.y * impactVel.y
|
||||
+ fallDirection.z * impactVel.z);
|
||||
// ALWAYS-log the crash pricing (rare event; the arbiter for the
|
||||
// wall-damage economy -- run 14 field report: "tapped a wall and
|
||||
// died instantly", so the forwarded amount is suspect vs binary).
|
||||
DEBUG_STREAM << "[crashdmg] amount=" << dmg.damageAmount
|
||||
<< " mass=" << moverMass
|
||||
<< " |v|=" << (float)Sqrt(impactVel.x * impactVel.x
|
||||
+ impactVel.y * impactVel.y + impactVel.z * impactVel.z)
|
||||
<< " force=(" << dmg.damageForce.x << "," << dmg.damageForce.y
|
||||
<< "," << dmg.damageForce.z << ")\n" << std::flush;
|
||||
Entity::TakeDamageMessage td(
|
||||
Entity::TakeDamageMessageID,
|
||||
sizeof(Entity::TakeDamageMessage),
|
||||
|
||||
@@ -291,6 +291,11 @@ class Damage;
|
||||
// (binary @0x4a0473: tally += critted->+0x108).
|
||||
Scalar CriticalScoreBonus() const { return criticalReference; }
|
||||
|
||||
// #83 (collision rattle): the authored lottery weight the collision-
|
||||
// damage distributor (FUN_0049ffcc @0x4a00f2) accumulates per eligible
|
||||
// subsystem against a [0,1) roll.
|
||||
Scalar CollisionCritWeight() const { return collisionCriticalHitWeight; }
|
||||
|
||||
// task #2 (crit propagation): the zone-side crit paths
|
||||
// (Mech__DamageZone::CriticalHit @0049ccc4 / SendSubsystemDamage
|
||||
// @0049c9a8) reach these subsystem facets.
|
||||
|
||||
@@ -1101,3 +1101,15 @@ void
|
||||
{
|
||||
ResetToInitialState(); // @004b5bf8 (takes no arg)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// #83 bridge: the collision-damage distributor (mech.cpp, FUN_0049ffcc) tests
|
||||
// roster members against this family's derivation chain (binary GUID 0x510b08).
|
||||
// Lives here because Torso is a complete type only in this TU.
|
||||
//
|
||||
Derivation *
|
||||
BTTorsoFamily()
|
||||
{
|
||||
return &Torso::ClassDerivations;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user