Combat: THE AUTHENTIC DAMAGE ECONOMY -- authored per-weapon amounts through the real fire chain (task #8)
Three root causes, all fixed: 1. kDamageScale was 1.0 -- _DAT_004bafbc is an x87 float80 = 1e-7, cancelling the ctor's x1e7: damagePortion = authored DamageAmount x (charge/seekV)^2 (closed form at fire time; madcat AC=25/LRM=50/ERLL=6, bhk1 PPC=12/SRM=35). The observed "0.25" was the degenerate EC=1 fallback, never authored data. 2. CheckFireEdge NaN latch: TriggerState carries ControlsButton INTS; the release value (-65) is a negative NaN. The binary's x87 unordered compare read it as "released"; IEEE-correct float compares latched the edge detector shut after the first release -- the reason the emitter discharge chain NEVER fired in-game. Fixed with bit-pattern sign compares. 3. The weapon-side submission LIVE: Emitter::FireWeapon fills damageData (amount + damageForce=target-muzzle [the gyro directional-bounce feed] + impact) -> MechWeapon::SendDamageMessage (@004b9728 real body) -> messmgr consolidation with per-weapon records + explosion bundling. The mech4 bring-up damage block + flat kShotDamage retired to diag hooks. Bonus: LODReuseHysteresis 0.82 -> 0.33 (double misread). Zone model verified byte-exact (no change). Solo end-to-end: 5-record volleys (2 PPC + 3 ERML with authored amounts), per-weapon zone granularity, explosions, kill. Heat stays bring-up scale pending the heat-calibration audit [T3]. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
77190c93e5
commit
fd055281a8
@@ -85,7 +85,14 @@ extern BTDriveInput gBTDrive;
|
||||
//#############################################################################
|
||||
// Reconstruction support: artifact constants, globals and helper stand-ins.
|
||||
//
|
||||
static const Scalar kDamageScale = 1.0f; // _DAT_004bafbc firing-physics scale (best-effort)
|
||||
static const Scalar kDamageScale = 1.0e-7f; // _DAT_004bafbc -- an x87 80-bit constant
|
||||
// (bytes bc427ae5d594bfd6e73f @0x4bafbc)
|
||||
// = 1e-7 EXACTLY: it cancels the ctor's
|
||||
// energyTotal x1e7, so at recommended
|
||||
// charge damagePortion == the AUTHORED
|
||||
// DamageAmount verbatim (task #8; the old
|
||||
// 1.0f "best-effort" was the whole
|
||||
// damage-economy mystery)
|
||||
static const Vector3D DAT_004e0fa4(0.0f, 0.0f, 0.0f); // zero vector
|
||||
static char DAT_00522524[4] = { 0 }; // default colour tag
|
||||
static int PTR_LAB_00511e6c, DAT_00511e70, DAT_00511e74; // beam-keepalive message template
|
||||
@@ -178,9 +185,27 @@ void
|
||||
|
||||
// E = 1/2 * currentLevel^2 * energyCoefficient
|
||||
Scalar energy = 0.5f * currentLevel * currentLevel * energyCoefficient; // _DAT_004bafb8
|
||||
damagePortion = damageFraction * energy; // 0x44c
|
||||
heatPortion = energy - damagePortion; // 0x450
|
||||
damagePortion = kDamageScale * damagePortion; // firing-physics scale (best-effort)
|
||||
|
||||
// THE AUTHENTIC PER-SHOT DAMAGE (task #8) -- the closed form of the
|
||||
// binary's energy algebra (@004bb120 ctor x @004bace8 fire):
|
||||
// damagePortion = dF x (0.5 V^2 EC) x 1e-7
|
||||
// = authored DamageAmount x (charge / seekV[rec])^2
|
||||
// (kDamageScale = _DAT_004bafbc = 1e-7 EXACTLY cancels the ctor's x1e7;
|
||||
// derived here from the STORED damageFraction/energyTotal so the authored
|
||||
// base never degrades, and the bring-up charge cycle -- calibrated to the
|
||||
// degenerate EC=1 electrical model -- stays untouched. At full charge:
|
||||
// the authored value verbatim; madcat ERLLaser=6, bhk1 PPC=12.)
|
||||
{
|
||||
Scalar vRec = seekVoltage[seekVoltageRecommendedIndex];
|
||||
Scalar chargeRatio = (vRec > 0.0f) ? (currentLevel / vRec) : 1.0f;
|
||||
damagePortion = (damageFraction * energyTotal * kDamageScale)
|
||||
* chargeRatio * chargeRatio; // 0x44c
|
||||
}
|
||||
// Heat stays on the bring-up scale until the heat-calibration audit [T3]:
|
||||
// the authentic heatPortion is heatCostToFire x 1e7 energy units (the
|
||||
// missile path already feeds that scale raw); switching the emitters too
|
||||
// is the heat audit's call, not the damage economy's.
|
||||
heatPortion = energy - damageFraction * energy; // 0x450
|
||||
|
||||
// dump the heat portion into our INHERITED thermal accumulator if heatable/online.
|
||||
// E5: pendingHeat (HeatSink @0x1C8) is the field the heat sim absorbs each frame
|
||||
@@ -223,10 +248,20 @@ void
|
||||
|
||||
if (dist <= effectiveRange) // this+0x328
|
||||
{
|
||||
// The binary writes the impact point/delta/energy into the inherited Damage
|
||||
// damageData (0x3C8/0x3B0/0x3AC); the recon's beamHitPoint/beamImpact/
|
||||
// beamImpactScalar copies were dead (no readers) -> removed. Register the pip.
|
||||
DrawWeaponPip(aimTransform); // FUN_004b9728
|
||||
// THE AUTHENTIC DAMAGE SUBMISSION (task #8; binary @004bace8:7758-7764):
|
||||
// fill the inherited Damage record -- amount = this shot's
|
||||
// damagePortion (== authored DamageAmount at full charge),
|
||||
// damageForce = target - muzzle (ALSO the gyro's directional
|
||||
// hit-bounce source, retiring its random fallback), impactPoint =
|
||||
// the target point -- then submit through SendDamageMessage
|
||||
// (@004b9728) into the owner's SubsystemMessageManager.
|
||||
damageData.damageAmount = damagePortion; // @0x3AC
|
||||
damageData.damageForce.Subtract(targetPoint, muzzlePoint); // @0x3B0
|
||||
damageData.impactPoint = targetPoint; // @0x3C8
|
||||
damageData.burstCount = 1;
|
||||
Entity *submitTarget = (owner != 0)
|
||||
? *(Entity **)((char *)owner + 0x388) : 0; // mech target slot
|
||||
SendDamageMessage(submitTarget); // FUN_004b9728
|
||||
}
|
||||
|
||||
// stash the beam endpoint for replication (world hit point) + the beam's
|
||||
@@ -275,6 +310,21 @@ void
|
||||
// per-type keyboard split are retired -- weapons sharing a button now fire
|
||||
// TOGETHER (the pod's authored groups: e.g. madcat Trigger = 4 weapons).
|
||||
Logical fireEdge = CheckFireEdge(); // @004b9608
|
||||
// task #8 diag: the discharge-chain probe (button->TriggerState->edge->FSM)
|
||||
if (getenv("BT_FIRE_LOG"))
|
||||
{
|
||||
static int s_fp = 0;
|
||||
if ((++s_fp % 97) == 0 || fireEdge) // 97 prime: rotates through all weapons
|
||||
DEBUG_STREAM << "[fire-probe] " << GetName()
|
||||
<< " owner=" << (void *)owner
|
||||
<< " trigState=" << (float)fireImpulse
|
||||
<< " edge=" << (int)fireEdge
|
||||
<< " alarm=" << (int)weaponAlarm.GetState()
|
||||
<< " level=" << (float)currentLevel
|
||||
<< " tgt=" << (void *)((owner != 0)
|
||||
? *(Entity **)((char *)owner + 0x388) : 0)
|
||||
<< std::endl;
|
||||
}
|
||||
targetWithinRange = UpdateTargeting(); // @004b9bdc -> this+0x34c
|
||||
|
||||
// hard failure: powered-off, faulted, or the OWNING MECH destroyed (@004baa88
|
||||
@@ -791,6 +841,10 @@ Emitter::Emitter(
|
||||
(void)energyRampTime;
|
||||
}
|
||||
|
||||
// (task #8 note: energyCoefficient deliberately stays on the bring-up
|
||||
// electrical model -- the charge cycle is calibrated to it; the AUTHENTIC
|
||||
// damage algebra is applied as a closed form at fire time instead.)
|
||||
|
||||
// damageFraction = damageAmount / (damageAmount + heatCostToFire)
|
||||
damageFraction = damageData.damageAmount /
|
||||
(damageData.damageAmount + heatCostToFire); // 0x444
|
||||
|
||||
Reference in New Issue
Block a user