#86: destroyed weapons can no longer fire -- the fire gates read a
never-written cell (the split-cell gotcha) Both weapon fire gates (ProjectileWeapon gate 1 @4bbd36, Emitter hard-failure @4baab9) test the binary's subsystem+0x40 for Destroyed(1). In the 1995 layout that offset sits INSIDE the embedded status alarm (statusAlarm@0x2C + level@+0x14 = 0x40) -- ONE cell, written by ForceCriticalFailure when a zone's crit cascade kills the subsystem. The port models the same address as TWO members: the AlarmIndicator AND a plain int simulationState@0x40. Every destruction path writes the ALARM (so the MFD draws its X correctly) while the gates read the plain int, which nothing ever writes -- so a weapon on a blown-off arm showed destroyed on every panel and kept firing and scoring, locally and on peers (night-7: all three testers, screenshots of a missile leaving a destroyed pod). The tell had been sitting in our own logs for weeks: [ammo] SRM6_1 -> NoAmmo (gate1): destroyed=0 ... on a mech whose launcher was X'd out. Fix: both gates now read statusAlarm.GetLevel()==1 as well as the int. Also added: the crit-cascade log names the destroyed subsystem, a BT_SELF_DAMAGE_ZONE=dz_* named-zone bench mode, and BT_KILL_SUBSYS=<name> (force ForceCriticalFailure on one named subsystem -- the exact call the zone cascade makes, so a bench can ask 'the panel says dead, does it still shoot?' without hunting for the zone that carries a given weapon). Verified A/B in ONE run: before the kill both SRM6 launchers fired 2 salvos each; after, the destroyed launcher fired ZERO (gate log destroyed=1) while its twin kept firing normally. KB: new gotcha #22 (the SPLIT CELL -- one binary offset, two port members, only one written; sibling of #1) + combat-damage entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J
This commit is contained in:
co-authored by
Claude Fable 5
parent
62fc8409b6
commit
5410371b0c
@@ -376,6 +376,17 @@ thruster table (ModelList ids aliased to their type-15 member); both launch path
|
||||
Note: the Black Hawk's "SRM6" fires **strk** (Streak) ammo per its bin -- the ammo model, not
|
||||
the launcher name, decides the flight profile.
|
||||
|
||||
## Destroyed weapons kept FIRING -- FIXED (2026-07-31, issue #86) [T1/T2]
|
||||
The fire gates (`ProjectileWeaponSimulation` gate 1 @4bbd36, `EmitterSimulation` hard-failure
|
||||
@4baab9) test `subsystem+0x40` for Destroyed(1). In the binary that offset IS the status alarm's
|
||||
level cell; the port splits it into `statusAlarm` + a never-written `int simulationState`, so the
|
||||
gates read a dead cell -- a weapon on a destroyed mount showed its X on the MFD and kept firing
|
||||
and SCORING (night-7: all three testers, with screenshots of a missile leaving a destroyed pod).
|
||||
Fixed by reading BOTH cells in both gates; the full pattern is [[reconstruction-gotchas]] §22.
|
||||
Verified A/B in one run (BT_KILL_SUBSYS bench hook -> ForceCriticalFailure, the same call the
|
||||
zone crit-cascade makes): before the kill both SRM6 launchers fired 2 salvos each; after, the
|
||||
destroyed launcher fired ZERO (gate log `destroyed=1`) while its twin kept firing.
|
||||
|
||||
## Ballistic damage type -- FIXED (2026-07-23, issue #27) [T2]
|
||||
Playtest matchlog forensics (2,591 applied-damage events over 2 rounds): the damageType
|
||||
histogram had Collision/Explosive/Laser/Energy but **Ballistic (type 1) NEVER appeared**,
|
||||
|
||||
@@ -717,3 +717,23 @@ capstone over the CODE section for the addressing form (e.g. FPU reads of `[reg+
|
||||
`[reg+reg*4+0x330]`). Minutes of scanning; it found in one pass what three decomp sweeps
|
||||
missed. Corollary: when the decomp and a PRIMARY SOURCE (the manual, a pod veteran) disagree,
|
||||
treat the disagreement as a hole in YOUR evidence first, not in theirs.
|
||||
|
||||
|
||||
## §22 — The SPLIT CELL: one binary offset, two port members (only one gets written)
|
||||
**(2026-07-31, gitea #86 "destroyed weapons keep firing".)** The binary's weapon fire gates test
|
||||
`subsystem+0x40`. In the 1995 layout that offset is *inside* the embedded status alarm
|
||||
(`statusAlarm@0x2C` + the indicator's level at `+0x14` = `0x40`) — **one cell**, written by
|
||||
`ForceCriticalFailure` when a zone's crit-cascade kills the subsystem. The port models the same
|
||||
address as TWO independent members: `AlarmIndicator statusAlarm` **and** a plain
|
||||
`int simulationState@0x40`. Every destruction path writes the ALARM (so the MFD correctly draws
|
||||
its X, the paper doll correctly greys the mount) while the fire gates read the plain int — which
|
||||
nothing ever writes. Result: a weapon on a blown-off arm shows destroyed on every panel and
|
||||
keeps firing and scoring, on the shooter's screen and on peers' (all three night-7 testers).
|
||||
**Detection smell:** a diagnostic that prints the gate's own inputs and shows a state flag
|
||||
reading 0 while the UI bound to "the same" state shows destroyed. (`[ammo] NoAmmo (gate1):
|
||||
destroyed=0` on a mech with an X'd-out launcher was the tell — it sat in the logs for weeks.)
|
||||
**Rule:** when a binary offset falls inside an embedded object in OUR layout, do not mirror it as
|
||||
a sibling scalar — read it through the object that owns it, or (if a duplicate member already
|
||||
exists) make every gate read BOTH and every writer write BOTH. Sibling of gotcha #1 (shadowed
|
||||
base field): same failure shape — two cells where the binary has one, and the readers pick the
|
||||
dead one.
|
||||
|
||||
@@ -428,7 +428,13 @@ void
|
||||
// simulationFlags@0x28, a latent kill-switch), own heatAlarm at FailureHeat
|
||||
// (this+0x184 == 2), or the owning mech disabled (FUN_0049fb54) -- a dead
|
||||
// mech's weapons drop everything.
|
||||
if (simulationState == 1 || GetFaultState() == 2 || BTMechDestroyed((Entity *)owner))
|
||||
// #86 FIX (2026-07-31): the binary's +0x40 IS the statusAlarm level cell
|
||||
// (indicator @0x2C, level at +0x14 = +0x40 -- ONE cell); the port split
|
||||
// them and the destruction path writes only the alarm, so X'd-out energy
|
||||
// weapons on a blown-off arm kept firing. Read BOTH cells (see
|
||||
// projweap.cpp gate 1 for the full note).
|
||||
if (simulationState == 1 || statusAlarm.GetLevel() == 1
|
||||
|| GetFaultState() == 2 || BTMechDestroyed((Entity *)owner))
|
||||
{
|
||||
ResetFiringState(); // @004ba9a8
|
||||
currentLevel = 0.0f; // 0x414
|
||||
|
||||
@@ -6035,6 +6035,14 @@ void
|
||||
if (zz != 0 && zz->IsLegZone()) { sdZone = zi; break; }
|
||||
}
|
||||
}
|
||||
else if (ze[0] == 'd' && ze[1] == 'z')
|
||||
{
|
||||
// #86 bench: a NAMED zone (e.g. dz_rarm) resolved via
|
||||
// the engine's own name->index map -- lets a bench
|
||||
// destroy a specific ARM and watch its weapons brick.
|
||||
int byName = Entity::GetDamageZoneIndex(CString(ze));
|
||||
if (byName >= 0) sdZone = byName;
|
||||
}
|
||||
else
|
||||
sdZone = atoi(ze);
|
||||
}
|
||||
@@ -6052,6 +6060,36 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// #86 BENCH (BT_KILL_SUBSYS=<name>): force the AUTHENTIC destruction of
|
||||
// one named subsystem -- MechSubsystem::ForceCriticalFailure, the exact
|
||||
// call the zone crit-cascade (SendSubsystemDamage) makes, and the thing
|
||||
// that raises the MFD's X. Lets a bench answer "the panel says it's
|
||||
// dead -- does it still shoot?" without hunting for the zone that
|
||||
// happens to carry a given weapon (per-mech, and dz_* names vary).
|
||||
// Env-gated, one-shot, viewpoint mech only.
|
||||
if ((Entity *)this == application->GetViewpointEntity()
|
||||
&& getenv("BT_KILL_SUBSYS"))
|
||||
{
|
||||
static int s_ksFrame = 0;
|
||||
if (++s_ksFrame == 900) // ~15 s in, after the mech settles
|
||||
{
|
||||
const char *want = getenv("BT_KILL_SUBSYS");
|
||||
for (int i = 2; i < GetSubsystemCount(); ++i)
|
||||
{
|
||||
Subsystem *s = GetSubsystem(i);
|
||||
if (s == 0 || s->GetName() == 0) continue;
|
||||
if (stricmp(s->GetName(), want) != 0) continue;
|
||||
if (!s->IsDerivedFrom(MechSubsystem::ClassDerivations)) continue;
|
||||
((MechSubsystem *)s)->SetSubsystemDamageLevel(1.0f);
|
||||
((MechSubsystem *)s)->ForceCriticalFailure();
|
||||
DEBUG_STREAM << "[killsub] '" << s->GetName()
|
||||
<< "' force-destroyed (statusAlarm=1, zone=1.0)"
|
||||
<< std::endl << std::flush;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// task #13 scripted verify (BT_VALVE_TEST=1): one MoveValve press at
|
||||
// frame ~600 -- Condenser1's valveState cycles 1 -> 5, so the flow
|
||||
// redistribution gives it 5/10 of the total coolant flow ([valve] log
|
||||
|
||||
@@ -800,6 +800,7 @@ void
|
||||
}
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[deathfx] crit-subsys " << i
|
||||
<< " '" << (s->GetName() ? s->GetName() : "?") << "'"
|
||||
<< " DESTROYED (vital=" << s->IsVitalSubsystem() << ")\n" << std::flush;
|
||||
}
|
||||
else if (getenv("BT_DEATH_LOG"))
|
||||
|
||||
@@ -752,8 +752,16 @@ void
|
||||
// FailureTemperature for the ballistic family), or the owning mech disabled
|
||||
// (FUN_0049fb54) -> pin recoil at the full rechargeRate + latch alarm 7.
|
||||
// No return: the frame continues into the state machine.
|
||||
// #86 FIX (2026-07-31): in the BINARY, "simulationState@0x40" IS the
|
||||
// statusAlarm's current-level cell (the indicator lives at +0x2C and its
|
||||
// level sits at +0x14 inside it = +0x40) -- ONE cell. The port models
|
||||
// them as TWO members, and the destruction path (ForceCriticalFailure /
|
||||
// the crit sink) writes only the ALARM -- so this gate read a
|
||||
// never-written int and a weapon on a blown-off arm kept firing with its
|
||||
// MFD X'd out (all three night-7 testers). Read BOTH cells.
|
||||
{
|
||||
int gate1Destroyed = (simulationState == 1);
|
||||
int gate1Destroyed = (simulationState == 1
|
||||
|| statusAlarm.GetLevel() == 1);
|
||||
int gate1FailHeat = (heatAlarm.GetLevel() == HeatSink::FailureHeat);
|
||||
int gate1Disabled = (owner != 0 && owner->IsDerivedFrom(*Mech::GetClassDerivations())
|
||||
&& ((Mech *)owner)->IsDisabled());
|
||||
|
||||
Reference in New Issue
Block a user