BT410 5.3.100: the collision economy -- armor is never touched by a crash, and the wall stops one-shotting the mech
The 5.3.99 open question is closed the way the binary closes it, not with a
divisor. Two pieces:
THE DIVERT (damage hub @0x4a0368): type-0 Collision damage NEVER reaches the
armor zones. Our handler was routing wall crashes through the same
cylinder-lottery path as enemy fire, which is why 24k-point kinetic numbers
one-shot vital zones. The binary short-circuits them into
THE DISTRIBUTOR (@0049ffcc): the manual's "collision damage" technician
setting. Gated on the owning player's advanced-damage flag; priced against
a 100 km/h reference impact --
scale = (2000 / mass) / (100 km/h)^2 / (1 - elasticity^2)
amount = raw * scale; under 0.5 the tap is FREE
n = Round(amount * 2) sub-hits of amount/n each
-- and applied as rattle crits to random INTERNAL subsystems (HeatSink /
Gyroscope / Torso families only), drawn by cumulative
collisionCriticalHitWeight against a [0,1) roll. An un-won roll lands
nowhere; the binary does not normalize the weights, so neither do we.
LIVE, same arena mission as 5.3.99's four-death run:
[colldmg] rattle 0.677919 pts in 1 sub-hits (scale=3.6e-05)
[crash] BLOCK dmg=18831.1 iv2=711.428 KNOCKDOWN
[crash] BLOCK dmg=331.878 iv2=12.549 (x6, pressed against a wall)
deaths: ZERO (was four)
An 18.8k-point crash becomes 0.68 points of internal rattle; the low-speed
wall grind prices as free taps under the 0.5 floor; the knockdown stagger
still fires above the velocity threshold. Walls now block, stagger, and
rattle -- they no longer execute.
Plumbing: MechSubsystem::CollisionCritWeight() accessor (the weight was
already streamed at +0x10C, protected), Mech::DistributeCollisionDamage
declared, random.hpp include.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,9 @@
|
||||
#if !defined(BOXSOLID_HPP)
|
||||
# include <boxsolid.hpp> // BoxedSolid extents -- the ground-snap probe
|
||||
#endif
|
||||
#if !defined(RANDOM_HPP)
|
||||
# include <random.hpp> // Random -- the collision-rattle draw
|
||||
#endif
|
||||
#include <dmgtable.hpp> // DamageLookupTable -- the cylinder hit table
|
||||
#include <player.hpp> // Player::VehicleDeadMessage -- the death notification
|
||||
#include <btplayer.hpp> // BTPlayer::ScoreMessage -- the kill credit
|
||||
@@ -778,6 +781,106 @@ Mech::~Mech()
|
||||
// Entity handler which routes damageZones[zone]->TakeDamage.
|
||||
//#############################################################################
|
||||
//
|
||||
//
|
||||
//#############################################################################
|
||||
// DistributeCollisionDamage (binary @0049ffcc, reached ONLY from the damage
|
||||
// hub's type-0 divert) -- the collision-damage economy.
|
||||
//
|
||||
// Collision damage is the manual's "collision damage" TECHNICIAN SETTING:
|
||||
// gated on the owning player's advanced-damage flag, priced against a
|
||||
// 100 km/h reference impact, and applied as 0.5-point RATTLE CRITS to random
|
||||
// internal subsystems. Armor is never touched by a collision.
|
||||
//
|
||||
// scale = (2000 / mass) / (100 km/h)^2 / (1 - elasticity^2)
|
||||
// amount = raw * scale; under 0.5 the tap is FREE
|
||||
// n = Round(amount * 2) sub-hits of amount/n each
|
||||
//
|
||||
// Each sub-hit lands on ONE roster subsystem drawn by cumulative
|
||||
// collisionCriticalHitWeight against a [0,1) roll, restricted to the
|
||||
// HeatSink / Gyroscope / Torso families. An un-won roll lands nowhere --
|
||||
// faithful: the binary does not normalize the weights.
|
||||
//#############################################################################
|
||||
//
|
||||
void
|
||||
Mech::DistributeCollisionDamage(Damage *damage)
|
||||
{
|
||||
Check(this);
|
||||
Check_Pointer(damage);
|
||||
|
||||
BTPlayer
|
||||
*player = (BTPlayer *)GetPlayerLink();
|
||||
if (player == NULL || !player->IsAdvancedDamageOn())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scalar
|
||||
reference = 100.0f * 0.27777779f; // 100 km/h in world u/s
|
||||
Scalar
|
||||
denominator =
|
||||
1.0f - elasticityCoefficient * elasticityCoefficient;
|
||||
Scalar
|
||||
scale = ((2000.0f / moverMass) / (reference * reference)) / denominator;
|
||||
|
||||
damage->damageAmount *= scale;
|
||||
if (damage->damageAmount < 0.5f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
sub_hits = (int)(damage->damageAmount * 2.0f + 0.5f);
|
||||
if (sub_hits < 1)
|
||||
{
|
||||
sub_hits = 1;
|
||||
}
|
||||
damage->damageAmount /= (Scalar)sub_hits;
|
||||
|
||||
if (getenv("BT_MECH_LOG"))
|
||||
{
|
||||
DEBUG_STREAM << "[colldmg] rattle "
|
||||
<< (damage->damageAmount * sub_hits) << " pts in "
|
||||
<< sub_hits << " sub-hits (scale=" << scale << ")"
|
||||
<< endl << flush;
|
||||
}
|
||||
|
||||
int
|
||||
hit,
|
||||
slot;
|
||||
for (hit = 0; hit < sub_hits; ++hit)
|
||||
{
|
||||
Scalar
|
||||
threshold = (Scalar)Random,
|
||||
accumulated = 0.0f;
|
||||
for (slot = 0; slot < GetSubsystemCount(); ++slot)
|
||||
{
|
||||
Subsystem
|
||||
*subsystem = GetSubsystem(slot);
|
||||
if (subsystem == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!subsystem->IsDerivedFrom(HeatSink::ClassDerivations) &&
|
||||
!subsystem->IsDerivedFrom(Gyroscope::ClassDerivations) &&
|
||||
!subsystem->IsDerivedFrom(Torso::ClassDerivations)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
accumulated +=
|
||||
((MechSubsystem *)subsystem)->CollisionCritWeight();
|
||||
if (accumulated < threshold)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
((MechSubsystem *)subsystem)->ApplyDamageAndMeasure(*damage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
void
|
||||
Mech::TakeDamageMessageHandler(TakeDamageMessage *message)
|
||||
{
|
||||
@@ -795,6 +898,20 @@ void
|
||||
lastInflictingID = message->inflictingEntity;
|
||||
lastInflictingDamage = message->damageData.damageAmount;
|
||||
|
||||
//
|
||||
// THE COLLISION DIVERT (binary @0x4a0368 -> @0049ffcc): type-0 Collision
|
||||
// damage NEVER reaches the armor zones. It is priced against a 100 km/h
|
||||
// reference impact and applied as internal RATTLE crits -- which is the
|
||||
// answer to the raw kinetic numbers being thousands of points where a
|
||||
// PPC is ~12. Without this divert a hard wall crash one-shots a vital
|
||||
// zone through the cylinder lottery (observed live, 5.3.99).
|
||||
//
|
||||
if (message->damageData.damageType == Damage::CollisionDamageType)
|
||||
{
|
||||
DistributeCollisionDamage(&message->damageData);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// The cylinder resolve (binary @0x4a0264 tail): an UNAIMED hit arrives
|
||||
// with invalidDamageZone set -- map its world impact point onto a hull
|
||||
|
||||
@@ -378,6 +378,14 @@
|
||||
unsigned callback_arg,
|
||||
Scalar carryover,
|
||||
int move_joints);
|
||||
//
|
||||
// The collision-damage economy (binary @0049ffcc): type-0 damage is
|
||||
// diverted here by the damage hub and rattles internal subsystems --
|
||||
// armor is never touched by a collision.
|
||||
//
|
||||
void
|
||||
DistributeCollisionDamage(Damage *damage);
|
||||
|
||||
static Scalar
|
||||
BodyClipFinished(
|
||||
Mech *mech,
|
||||
|
||||
@@ -175,6 +175,14 @@
|
||||
Scalar
|
||||
ApplyDamageAndMeasure(Damage &damage);
|
||||
|
||||
//
|
||||
// The collision-rattle draw weight (binary subsystem+0x10C) -- read
|
||||
// by Mech::DistributeCollisionDamage's cumulative roll.
|
||||
//
|
||||
Scalar
|
||||
CollisionCritWeight() const
|
||||
{ Check(this); return collisionCriticalHitWeight; }
|
||||
|
||||
Logical
|
||||
IsVitalSubsystem() const { Check(this); return vitalSubsystem; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user