Beams CUT on a destroyed target (the real FUN_0049fb54, was a mislabeled stub)

Answers "can I keep targeting the dead mech?": the lock isn't cleared by
anything we've found, but the binary REFUSES to sustain energy beams on a
destroyed mech: ServiceDischarge (@004ba8d0) and ContinueDischarge (@004baa20)
check the beam's target EVERY discharge frame -- IsDerivedFrom(Mech) then
Mech::IsDestroyed (FUN_0049fb54 = movementMode 2||9) -- and kill the beam the
moment it dies.  EmitterSimulation (@004baa88) applies the same check to the
OWNER (a dead mech's own weapons drop everything).

Our recon had FUN_0049fb54 as a mislabeled "cockpit/HUD query" no-op stub
called with NULL, and never set the beam's target (0x474) at fire -- all three
authentic gates were dead.  Now: FireWeapon stashes the owner's target entity;
both discharge paths + the owner gate run the real check (BTMechDestroyed).

Verified: 40 beam samples before the kill, 0 after -- lasers flash-fail on the
wreck instead of cutting it like butter (heat is still spent per the binary's
FireWeapon, which fires blind; only the DISCHARGE checks the corpse).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-08 18:46:08 -05:00
co-authored by Claude Fable 5
parent cba4c3098f
commit c2f70f6348
+26 -7
View File
@@ -51,7 +51,7 @@
// FUN_004b0bd0 PoweredSubsystem simulation step FUN_004b0d50 PoweredSubsystem scalar query
// FUN_004ad7d4 HeatableSubsystem "is online/active" FUN_004ac9c8 HeatableSubsystem heat-state query
// FUN_00417ab4 follow a SharedData link (voltage source / ammo bin)
// FUN_0049fb54 cockpit/HUD query FUN_0041a1a4 IsDerivedFrom(classDerivations)
// FUN_0049fb54 Mech::IsDestroyed (movementMode 2||9) FUN_0041a1a4 IsDerivedFrom(classDerivations)
// FUN_0041bbd8 AlarmIndicator::SetLevel(n) (weaponAlarm @0x350, state @0x364)
// FUN_0041c350 Subsystem::SendMessage(template, a, b)
// FUN_00408440 Vector copy FUN_00409968 Vector set FUN_0040a968 Vector negate/copy
@@ -102,7 +102,20 @@ static void CopyColor(void *, const void *) {}
// engine-internal artifacts the decomp called by address (stand-ins)
static int FUN_004ad7d4(void *) { return 1; } // HeatableSubsystem active?
static int FUN_0049fb54(int) { return 0; } // cockpit/HUD query
// FUN_0049fb54 == Mech::IsDestroyed (movementMode 2||9; reconstructed as
// Mech::IsDisabled). Was a mislabeled "cockpit/HUD query" no-op stub -- so a
// live beam never CUT when its target mech died (@004ba8d0/@004baa20 check
// this every discharge frame), and a destroyed mech's own weapons kept firing
// (@004baa88). The binary's call sites first verify the object IS a Mech
// (IsDerivedFrom vs 0x50bdb4 = Mech::ClassDerivations), reproduced here.
static int BTMechDestroyed(Entity *e)
{
if (e == 0)
return 0;
if (!e->IsDerivedFrom(*Mech::GetClassDerivations()))
return 0;
return ((Mech *)e)->IsDisabled() ? 1 : 0;
}
static Scalar FUN_004b0d50(void *) { return 1.0f; }// PoweredSubsystem dt scale
static int FUN_004ac9c8(void *) { return 0; } // heat-state query
static void *FUN_00417ab4(void *) { return 0; } // follow SharedData link
@@ -215,8 +228,13 @@ void
DrawWeaponPip(aimTransform); // FUN_004b9728
}
// stash the beam endpoint for replication (world hit point)
// stash the beam endpoint for replication (world hit point) + the beam's
// TARGET (0x474 -- the discharge checks read it every frame: the beam cuts
// the moment this mech is destroyed). The owner's target slot is the same
// raw 0x388 the rest of the weapon path uses (HasActiveTarget/mech4).
beamEndpoint = targetPoint; // 0x460 <- targetPoint
targetEntity = (owner != 0)
? *(Entity **)((char *)owner + 0x388) : 0; // 0x474 <- mech target entity
targetLocalFlag = 1; // 0x470
SetDirty(); // this+6 |= 1 (needs replication)
@@ -259,8 +277,9 @@ void
Logical fireEdge = CheckFireEdge(); // @004b9608
targetWithinRange = UpdateTargeting(); // @004b9bdc -> this+0x34c
// hard failure: powered-off, faulted, or destroyed -> drop everything
if (GetFlags() == 1 || GetFaultState() == 2 || FUN_0049fb54(0))
// hard failure: powered-off, faulted, or the OWNING MECH destroyed (@004baa88
// checks IsDestroyed on the owner -- a dead mech's weapons drop everything)
if (GetFlags() == 1 || GetFaultState() == 2 || BTMechDestroyed((Entity *)owner))
{
ResetFiringState(); // @004ba9a8
currentLevel = 0.0f; // 0x414
@@ -382,7 +401,7 @@ void
weaponAlarm.SetLevel(0); // Firing
dischargeTimer -= time_slice; // 0x440
if (dischargeTimer <= 0.0f // _DAT_004ba9a4
|| (targetEntity != 0 && FUN_0049fb54(0))) // or the targeted subsystem is gone
|| BTMechDestroyed(targetEntity)) // the beam CUTS when its target mech dies
{
firingActive = 0;
weaponAlarm.SetLevel(2); // Loaded
@@ -420,7 +439,7 @@ void
{
if (beamFlag != 0) // 0x46c
{
if (targetEntity != 0 && FUN_0049fb54(0)) // targeted subsystem gone
if (BTMechDestroyed(targetEntity)) // the beam CUTS when its target mech dies
{
ResetFiringState(); // @004ba9a8
return;