#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:
Joe DiPrima
2026-07-31 12:11:09 -05:00
co-authored by Claude Fable 5
parent 62fc8409b6
commit 5410371b0c
6 changed files with 86 additions and 2 deletions
+11
View File
@@ -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**,
+20
View File
@@ -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.