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
@@ -58,6 +58,7 @@
|
||||
//
|
||||
|
||||
#include <bt.hpp>
|
||||
#include <messmgr.hpp> // SubsystemMessageManager (task #8 damage submission)
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(MECHWEAP_HPP)
|
||||
@@ -376,13 +377,25 @@ Logical
|
||||
// @004b9608 -- rising-edge detector on fireImpulse (threshold _DAT_004b9648 == 0.0f).
|
||||
// Returns True the frame fireImpulse becomes positive while the previous sample
|
||||
// was non-positive, then stores the current sample for next time.
|
||||
// TODO: confirm exact role (discharge vs. recharge-ready edge).
|
||||
//
|
||||
// ⚠ BIT-PATTERN COMPARE (task #8 root-cause): TriggerState carries the raw
|
||||
// ControlsButton INT bit-copied by the streamed direct mapping (+(button+1)
|
||||
// press / -(button+1) release) -- the release value (e.g. -65 = 0xFFFFFFBF)
|
||||
// reads as a NEGATIVE NaN in float terms. The binary's x87 compare (FNSTSW/
|
||||
// SAHF + JBE: unordered sets CF|ZF => branch TAKEN) effectively treated the
|
||||
// NaN as "released", so its edge re-armed; an IEEE-correct float compare
|
||||
// makes NaN<=0 FALSE and the detector LATCHES SHUT after the first release
|
||||
// (observed live: one startup edge, then never again). Compare the BIT
|
||||
// PATTERNS as signed ints instead -- sign-correct for the button ints AND
|
||||
// for genuine float values (0.0f = 0x0, positives > 0, negatives/NaNs < 0),
|
||||
// reproducing the binary's effective behavior exactly.
|
||||
//
|
||||
Logical
|
||||
MechWeapon::CheckFireEdge()
|
||||
{
|
||||
Logical edge =
|
||||
(fireImpulse > 0.0f && previousFireImpulse <= 0.0f) ? True : False;
|
||||
int current = *(int *)&fireImpulse;
|
||||
int previous = *(int *)&previousFireImpulse;
|
||||
Logical edge = (current > 0 && previous <= 0) ? True : False;
|
||||
previousFireImpulse = fireImpulse;
|
||||
return edge;
|
||||
}
|
||||
@@ -465,23 +478,57 @@ void
|
||||
// routing through AddDamageMessage. This body stays dormant meanwhile.
|
||||
//
|
||||
void
|
||||
MechWeapon::DrawWeaponPip(const LinearMatrix &transform)
|
||||
MechWeapon::SendDamageMessage(Entity *target)
|
||||
{
|
||||
// @004b9728 -- register the targeting "pip" with the owning Mech's cockpit
|
||||
// HUD manager. Skipped for non-pip weapons (pipSegment == -1). The HUD
|
||||
// element record {priority 100, kind 0x12, enabled, team colour, pipPosition,
|
||||
// pipExtendedRange, transform, pipSegment} is submitted to the cockpit HUD.
|
||||
//
|
||||
// CROSS-FAMILY (hud): the cockpit HUD manager AddElement entry point lives in
|
||||
// the HUD module; the submission is wired there. We retain the gating and
|
||||
// transform reference here.
|
||||
if (useConfiguredPip) // this+0x3E0 (weapon draws a configured pip)
|
||||
// @004b9728 -- package this weapon's just-fired damageData as an
|
||||
// Entity::TakeDamageMessage and submit it to the owning mech's
|
||||
// SubsystemMessageManager for per-frame consolidation (AddDamageMessage
|
||||
// @0049b6d8) -- the authentic beam damage delivery (task #8; the caller
|
||||
// Emitter::FireWeapon fills damageAmount/damageForce/impactPoint first).
|
||||
//
|
||||
// Binary gate (part_013.c:6746): a Mech victim with NO designated zone
|
||||
// (owner+0x38C == -1) is SKIPPED. The port PASSES -1 through [T3
|
||||
// divergence]: our Mech::TakeDamageMessageHandler cylinder-resolves the
|
||||
// unaimed zone (STEP 6), preserving the verified aiming model.
|
||||
//
|
||||
Mech *mech = (Mech *)GetEntity(); // weapon+0xD0
|
||||
if (mech == 0 || target == 0)
|
||||
{
|
||||
(void)transform;
|
||||
(void)pipPosition;
|
||||
(void)pipExtendedRange;
|
||||
(void)pipColor;
|
||||
// owningHud->AddElement( ...pip record... ); // FUN_0049b6d8(entity+0x434, ..)
|
||||
return;
|
||||
}
|
||||
|
||||
extern int BTMechTargetZone(void *mech); // mech+0x38C (mech4 TU)
|
||||
int zone = BTMechTargetZone(mech);
|
||||
|
||||
Entity::TakeDamageMessage message(
|
||||
Entity::TakeDamageMessageID,
|
||||
sizeof(Entity::TakeDamageMessage),
|
||||
mech->GetEntityID(), // inflicting = owner (+0x184)
|
||||
zone, // designated zone (or -1 -> cylinder)
|
||||
damageData, // the 12-dword Damage @0x3A8
|
||||
subsystemID); // inflictingSubsystemID (+0xD8) --
|
||||
// lights up the messmgr's per-weapon
|
||||
// explosion bundling (weapon+0x3E4)
|
||||
SubsystemMessageManager *manager =
|
||||
(SubsystemMessageManager *)mech->GetMessageManager(); // mech+0x434
|
||||
if (manager != 0)
|
||||
{
|
||||
manager->AddDamageMessage(target, &message); // FUN_0049b6d8
|
||||
}
|
||||
else
|
||||
{
|
||||
target->Dispatch(&message); // no manager: direct (bring-up)
|
||||
}
|
||||
|
||||
// PORT bookkeeping (score lived in the retired mech4 fire block): credit
|
||||
// the delivered amount to the shooter-side score exactly as before.
|
||||
extern void BTPostDamageScore(Entity *victim, Scalar damage);
|
||||
extern Logical BTIsRegisteredMech(Entity *e);
|
||||
if (BTIsRegisteredMech(target)
|
||||
&& !((Mech *)target)->IsMechDestroyed())
|
||||
{
|
||||
BTPostDamageScore(target, damageData.damageAmount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user