#165 ROOT-CAUSED after three failed fixes -- the binary keeps ONE destroyed cell per subsystem (+0x40: fire gate, dial X, wire header, every writer) and the port split it into FOUR stores; the everyday CRIT-LOTTERY kill (MechWeapon::TakeDamage) bypassed the reconstructed base and wrote only the X store, so X'd weapons kept firing while every prior fix aimed at a gate that was never broken (the 4,213-refusal bench drove BT_KILL_SUBSYS, which writes all cells -- it validated readers, not writers). FIX: MechWeapon::TakeDamage chains to MechSubsystem::TakeDamage (@0x4ac0bc semantics); the port-authored FSM-from-damage tail is DELETED (it parked partial-crit weapons at level 1 -- no FSM case, the silent freeze, and with beamFlag armed the 883 hanging beam: #164's upstream writer, caught red-handed by berserker's 20x STUCK-ON receipts); the phantom uninitialized simulationState int is ctor-initialized and kept in lockstep at every destroyed write; always-on [crit] DESTROYED receipt makes crit kills field-visible. BT_CRIT_SUBSYS bench hook drives the REAL writer path; weaponx_bench PASS: kill phase 0 FIRED post / 5,664 refusals / X+gate agree, partial phase keeps firing. Gotcha 30; #174 filed (cascade re-descend)

This commit is contained in:
Joe DiPrima
2026-08-13 00:35:33 -05:00
parent 2fe58b8b89
commit fcd592c0f2
7 changed files with 175 additions and 24 deletions
+16
View File
@@ -1328,3 +1328,19 @@ record shipped penalty-off for entire steam nights, #162); (3) the bench for ANY
here is scratchpad/night15/kd_bench.sh -- five HARD invariants (deaths==increments,
cost-per-death, credit-per-kill, zero swallowed, arithmetic witness); its predecessor's
assertions were greps that matched nothing, which is how both halves shipped broken.
## Weapon destroyed-state: the ONE-cell contract (#165 root cause, night-16) [T1]
Binary: subsystem `+0x40` (statusAlarm level) is THE destroyed cell -- all writers (base
TakeDamage @0x4ac0bc zone-saturation tail; cascade descent @0049c9a8; slot-8 pump watchdog),
all readers (Emitter gate @004baa88 `==1` strict; small-dial X @004c7844; techstat; the wire
header). Four alarm cells exist per weapon and are NOT interchangeable: +0x40 status/health,
+0x184 temperature (gate clause `==2`), +0x278 power feed (eng-page X only!), +0x364 weaponAlarm
FSM (level 7 "unavailable" = AMMO weapons only, @004bb9b8/@004bbd04 -- the #30 answer). The
eng-page energy X (@004c8990) draws on `+0x40==1 OR power!=4`: an X over a LOADED emitter that
legally fires its one loaded shot is AUTHENTIC binary behavior (power-fault case). Destroyed
emitters park at weaponAlarm=3 (Loading) via the gate's per-frame refusal -- "recharging under
the X" is authentic. Replication: weapon statusAlarm NEVER crosses the wire; destroyed-ness
reaches peers via ZONE records only; replicant emitters run a gateless Simulate (@004ba8d0) --
fire legality is master-only. Port fix + the four-store split it repaired: [[reconstruction-gotchas]] SS30;
bench: `scratchpad/night16/weaponx_bench.sh` (crit-path kill + partial-crit freeze regression).
+9
View File
@@ -1226,5 +1226,14 @@ consistent with internals-only, see #103). Logs: `scratchpad/night16/` + Oracle
no flash on any MFD, died ~9:38PM CT -- the match's only death, receipt findable in night16).
Distinct from the closed #135: this is the SET-edge / first-flash arming path.
- **#173 -- chicken-walker seek-4 myomer overheat** (see [[subsystems]] night-16 note).
- **#165 ROOT-CAUSED + FIXED + BENCHED (2026-08-13, the 4-agent hunt):** gotcha SS30 -- the
binary's ONE destroyed cell was split into four port stores and the crit-lottery kill path
(MechWeapon::TakeDamage) wrote none the gate reads. Fixed (chain to MechSubsystem::TakeDamage,
FSM tail deleted, phantom simulationState initialized + bridged); weaponx_bench PASS both
phases. RESIDUAL UNKNOWNS from the hunt: (i) Elengil's 20:17 ET no-X facet needs ALIA's
PRE-ROTATION steam_20260812.log (ask Elengil -- staged file starts 22:20 ET); (ii) cascade
re-descend re-entrancy (zone 18 x7 re-rolls, own ticket); (iii) incident-(b) "no damage on
target": shooter log computed FULL damage (11.76) -- wire/victim-side loss, needs paired
matchlogs (fleet-wide BT_DMG_LOG + matchlog next night would make this attributable).
- Nanook lost the coolant MFD glass window, recovered only by config edit -> #76 comment
(in-game layout-reset recovery path).
+30
View File
@@ -1014,3 +1014,33 @@ sampled a cell whose write timing differs from the binary's". Fix shape: run the
transition inline at the destruction edge (mech.cpp death_tail → `UpdateDeathState(0)`), keep the
per-frame caller for maintenance. Verified: kd4_bench 9/9/9/9 + kd8_stress 26/26/26/26, both
SWALLOWED=0 (the receiver dedup tripwire now never fires — no duplicate is ever GENERATED).
## 30. ONE binary cell, FOUR port stores: the destroyed-state split that survived three "fixes" (#165, night-16 2026-08-13)
The binary keeps ONE destroyed cell per subsystem: `+0x40` = the statusAlarm level -- written by
EVERY destruction path (base TakeDamage @0x4ac0bc at private-zone saturation; zone-cascade
descent @0049c9a8; the per-frame watchdog in the slot-8 message pump), read by EVERY consumer
(the Emitter fire gate @004baa88, the small weapon-dial X @004c7844, the techstat scan, the wire
via the Simulation record header). The port split that cell into FOUR stores that never talked:
the private DamageZone.damageLevel (drives the X), the ReconAlarm statusAlarm (gate clause 2 --
written ONLY by the cascade + bench hook), a phantom `int simulationState` (gate clause 1 --
ZERO writers, NEVER ctor-initialized: uninitialized pool memory, MEMBLOCK recycles unzeroed),
and the weaponAlarm FSM cell. `MechWeapon::TakeDamage` then bypassed the reconstructed
`MechSubsystem::TakeDamage` (which HAS the destroyed tail) for the raw engine base -- so the
everyday CRIT-LOTTERY kill saturated the zone (X lit) without ever writing a gate cell: X'd
weapons kept firing. Three prior "fixes" all aimed at the GATE, and the bench (BT_KILL_SUBSYS)
wrote both cells directly -- 4,213 refusals proving a gate that was never broken.
**Sibling defect in the same function:** the port-authored tail drove the weapon FSM from zone
state -- saturation -> level 0 (=Firing!), partial damage -> level 1 (NO case in either FSM:
silent freeze; with beamFlag armed = the 883 hanging beam, #164's upstream writer).
**Rules:** (1) when the binary reads/writes ONE offset, the port must have ONE authority --
alias, don't duplicate; if layout forces a twin field, every writer writes BOTH (grep-audit the
writer census, not the reader census). (2) Never let an override "simplify away" the
reconstructed base -- `MechWeapon::TakeDamage` calling `Subsystem::TakeDamage` instead of
`MechSubsystem::TakeDamage` dropped the invariant silently. (3) A bench hook that writes state
directly validates READERS only; destruction/state benches must drive the REAL writer path
(BT_CRIT_SUBSYS: Damage -> ApplyDamageAndMeasure -> virtual TakeDamage). (4) Every ctor
initializes every reconstructed field -- an unwritten int in pooled memory is a time bomb.
Verified: weaponx_bench P1 (crit kill: 0 FIRED after, 5,664 refusals, X+gate agree) + P2
(partial crit keeps firing -- the freeze is gone). Full mechanism: the night-16 workflow dossier
(4 agents, cross-examined) + docs/GEN_WARNING_AUDIT-style lanes in the #165 ticket thread.
+8
View File
@@ -226,3 +226,11 @@ wrote); affinity masks 2-cores-each up to 4 nodes, 1-each at 8.
- Uses: [[build-and-run]] (parity, env gates, BT_SHOT capture) · [[experience-levels]] (expert vs novice gating)
- Informs: [[reconstruction-method]] (step 4 "verify honestly" — this file is the how)
- Incident sources: [[reconstruction-gotchas]] §23 (pixels), §25 (same-pass latch timing — caught by the 4-node bench), §26 (silent stubs); [[multiplayer]] (replication layers)
## weaponx_bench (night16) -- #165 crit-path destruction
`scratchpad/night16/weaponx_bench.sh`: single node, vulture, BT_AUTOFIRE; P1 kills ERSLaser_1
via BT_CRIT_SUBSYS (the REAL writer path: Damage -> ApplyDamageAndMeasure -> virtual TakeDamage)
and asserts [crit] DESTROYED + [techstat] X + zero named FIRED after + REFUSED>0 + survivor
fires; P2 partial-crits to 0.5 and asserts the weapon KEEPS firing (freeze regression, the #164
upstream writer). LESSON (gotcha SS30): BT_KILL_SUBSYS writes the cells directly and can only
validate READERS -- state benches must drive the real writer path.
+60
View File
@@ -7494,6 +7494,66 @@ void
}
}
// #165 BENCH (BT_CRIT_SUBSYS=<name>[,<name>...]): kill named subsystems
// through the REAL crit path -- Damage record -> ApplyDamageAndMeasure ->
// virtual TakeDamage -- NOT the direct cell writes BT_KILL_SUBSYS does.
// This is the path the field takes (the crit lottery), and the path the
// old 4,213-refusal bench never exercised: BT_KILL_SUBSYS wrote
// statusAlarm AND the zone directly, so it validated the fire gate while
// the field's writer (MechWeapon::TakeDamage) silently dropped the
// destroyed-side write. Applies one energy-damage chunk per frame from
// frame 900 until the subsystem's own zone saturates ([crit] receipt
// fires from MechSubsystem::TakeDamage on the edge), then stops.
// Env-gated, viewpoint mech only.
if ((Entity *)this == application->GetViewpointEntity()
&& getenv("BT_CRIT_SUBSYS"))
{
static int s_csFrame = 0;
if (++s_csFrame >= 900) // same settle window as killsub
{
char want_list[256];
strncpy(want_list, getenv("BT_CRIT_SUBSYS"), sizeof(want_list) - 1);
want_list[sizeof(want_list) - 1] = 0;
for (char *want = strtok(want_list, ","); want != 0;
want = strtok(0, ","))
{
// PARTIAL form ("Name=0.5"): stop at that zone level -- the
// freeze-regression phase (a partially-critted weapon must
// keep recycling; the old port tail froze it at alarm 1).
float target = 1.0f;
char *eq = strchr(want, '=');
if (eq != 0)
{
*eq = 0;
target = (float)atof(eq + 1);
}
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 *ms = (MechSubsystem *)s;
if (ms->GetSubsystemDamageLevel() >= target) break; // done
Damage d;
d.damageType = Damage::EnergyDamageType;
d.damageAmount = 0.75f; // FINE-GRAINED: a small-laser
// private zone saturates on a
// single 20.0 chunk (first run);
// ~0.04 level/frame ramps let
// the PARTIAL stop actually stop
d.burstCount = 1;
ms->ApplyDamageAndMeasure(d); // FUN_004ac07c -> virtual TakeDamage
if ((s_csFrame % 30) == 0)
DEBUG_STREAM << "[critsub] '" << s->GetName()
<< "' zone=" << (float)ms->GetSubsystemDamageLevel()
<< 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
+23
View File
@@ -142,6 +142,11 @@ MechSubsystem::MechSubsystem(
{
refCount = NewRefCount(0x10); // this[0x3d]
this->owner = owner; // this[0x34] -- the owning Mech (canonical)
simulationState = 0; // #165: was NEVER initialized anywhere --
// MemoryBlock pools recycle unzeroed
// (MEMBLOCK.cpp), so stale garbage ==1
// could brick a weapon from spawn and
// draw the seek-graph destroyed bitmap
controlDestination = 0; // this[0x3a] = param_7 (ctor pass-through;
controlMessageID = 0; // this[0x3b] = param_8 weapons override -- task #6)
vitalSubsystem = 0; // this[0x39]
@@ -187,6 +192,8 @@ MechSubsystem::MechSubsystem(
// ctor only set hostEntity; subclasses read
// `owner` widely -> must be set or it is garbage.)
statusAlarm.SetLevel(0); // FUN_0041bbd8(this+0xb, 0)
simulationState = 0; // #165: uninitialized-landmine fix (see
// the sibling ctor)
controlDestination = 0; // this[0x3a]=param_6 (ctor pass-through;
controlMessageID = 0; // this[0x3b]=param_7 weapons override -- task #6)
resource = subsystem_resource; // this[0x3c]
@@ -279,6 +286,7 @@ Scalar MechSubsystem::GetSubsystemDamageLevel() const
void MechSubsystem::ForceCriticalFailure()
{
statusAlarm.SetLevel(1); // +0x2C: 1 = Destroyed
simulationState = 1; // #165 twin (see TakeDamage)
if (printSimulationState) // +0x104 gate
{
PrintState(); // vtable slot 0x34 @4ac8c0
@@ -303,6 +311,7 @@ void MechSubsystem::ForceCriticalFailure()
void MechSubsystem::RespawnRepair()
{
statusAlarm.SetLevel(0); // operational
simulationState = 0; // #165 twin (see TakeDamage)
if (damageZone != 0)
{
((DamageZone *)damageZone)->damageLevel = 0.0f;
@@ -358,6 +367,7 @@ Logical
if (StatusThreshold <= ((DamageZone *)damageZone)->damageLevel) // _DAT_004ac140 (engine view)
{
statusAlarm.SetLevel(1); // FUN_0041bbd8(this+0xb, 1)
simulationState = 1; // #165 twin (see TakeDamage)
if (printSimulationState != 0) // this[0x41]
{
OnAlarmChanged(); // (*this.vtable+0x34)(this)
@@ -386,6 +396,7 @@ void
((DamageZone *)damageZone)->damageLevel = 1.0f; // dz+0x158 = 1.0 (engine view)
}
statusAlarm.SetLevel(1); // FUN_0041bbd8(this+0xb, 1)
simulationState = 1; // #165 twin (see TakeDamage)
if (printSimulationState != 0)
{
OnAlarmChanged(); // (*this.vtable+0x34)(this)
@@ -407,6 +418,7 @@ void
((DamageZone *)damageZone)->damageLevel = 0.0f; // dz+0x158 = 0 (engine view)
damageZone->alarm.SetLevel(0); // FUN_0041bbd8(dz+0x10, 0)
statusAlarm.SetLevel(0); // FUN_0041bbd8(this+0xb, 0)
simulationState = 0; // #165 twin (see TakeDamage)
if (printSimulationState != 0)
{
OnAlarmChanged();
@@ -520,7 +532,18 @@ void MechSubsystem::TakeDamage(Damage &damage)
if (dz->damageLevel >= 1.0f) // _DAT_004ac140
{
// #165: named destruction receipt on the EDGE only (damage keeps
// landing on dead subsystems; the write below is idempotent) -- the
// crit-path kill had NO field receipt, which is how an X'd-but-firing
// weapon hid for a month. Always-on: once per subsystem per life.
if (statusAlarm.GetLevel() != 1)
DEBUG_STREAM << "[crit] '" << (GetName() ? GetName() : "?")
<< "' DESTROYED (zone saturated)\n" << std::flush;
statusAlarm.SetLevel(1); // Destroyed
simulationState = 1; // #165: the binary's +0x40 is ONE
// cell; the port split it -- keep
// the twin in lockstep (gate clause
// 1, seek-graph destroyed bitmap)
if (printSimulationState) // +0x104
{
PrintState(); // vtable +0x34 @4ac8c0
+29 -24
View File
@@ -411,34 +411,39 @@ Logical
// @004b96d4 -- slot 9 (TASK #51 RENAME: this body was mislabeled slot-6
// ReadUpdateRecord; @004b96d4 chains @004b0efc = PoweredSubsystem::TakeDamage,
// which dispatches on *param==4 == Damage::damageType -- damage semantics).
// The PoweredSubsystem::TakeDamage body (electrical-short handling) is not yet
// reconstructed; chain to the engine Subsystem base, and keep the damage-zone
// alarm tracking this port has shipped with (best-effort; the binary's alarm
// display on weapon damage flows through the unreconstructed @004b0efc chain).
//
// #165 ROOT CAUSE (night-16 hunt, 2026-08-13). The old body here called the
// raw ENGINE Subsystem::TakeDamage (zone accumulation ONLY) and then drove
// weaponAlarm from the zone state -- two defects in one:
// (1) it DROPPED the destroyed-side invariant: the binary keeps ONE
// destroyed cell per subsystem (+0x40 = statusAlarm level) that every
// destruction path writes and the fire gate reads. A weapon killed by
// the CRIT LOTTERY (the everyday field path) saturated its private
// zone (-> the MFD X, [techstat] condition 0) but never wrote the gate
// cell -- the X'd weapon kept firing (Oracle 20260812: ER PPC firing
// indefinitely; the PPC+SL pair firing until the arm CASCADE finally
// wrote the cell). The 4,213-refusal bench passed because
// BT_KILL_SUBSYS writes both cells -- it validated the gate, not the
// writers.
// (2) the weaponAlarm tail wrote the FSM cell BACKWARDS: saturation ->
// level 0 (Firing!), partial damage -> level 1 -- a state with NO case
// in either weapon FSM: the silent freeze (berserker's 20x [beam]
// STUCK-ON, and the state-1+beamFlag combination that drew Conn Man's
// 883 hanging beam -- #164's upstream cause). The binary never drives
// the FSM from damage: a destroyed weapon parks at Loading(3) via the
// fire gate's per-frame refusal (ResetFiringState, emitter.cpp).
// FIX: chain to MechSubsystem::TakeDamage -- the correctly-reconstructed
// binary base (@0x4ac0bc: zone TakeDamage, then damageLevel>=1.0 ->
// statusAlarm=1 + pin + vital-kill) -- and do NOT touch the FSM. The
// binary's electrical-short middle layer (@004b0efc) remains unreconstructed
// (a marked gap, unchanged); what is restored is the destruction invariant.
// MechSubsystem::TakeDamage guards damageZone==0 itself (the 2026-07-29
// Owens NULL-zone field crash stays guarded).
//
void
MechWeapon::TakeDamage(Damage &damage)
{
// FIELD CRASH GUARD (2026-07-29, build 641): the engine base derefs
// damageZone unguarded; an Owens weapon reached here with zone == NULL
// the first night crits could land (#80). See ApplyDamageAndMeasure.
if (this->Subsystem::damageZone != 0)
Subsystem::TakeDamage(damage); // engine base (real chain: @004b0efc)
// Port behavior (kept): a destroyed weapon clears its display alarm;
// otherwise the alarm tracks the resolved damage-zone state.
::DamageZone *dz = this->Subsystem::damageZone;
if (dz != 0
&& dz->GetGraphicState() == DamageZone::DestroyedGraphicState)
{
weaponAlarm.SetLevel(0);
}
else
{
weaponAlarm.SetLevel(
(dz != 0 && dz->damageLevel > 0.0f) ? 1 : 0
);
}
MechSubsystem::TakeDamage(damage); // @0x4ac0bc semantics
}
//