Fix two Jul-16 regressions: BT_DEV_GAUGES crash + -net dead controls mapper

Both surfaced today (dormant ~1 day) the moment the pod launch flags
(BT_DEV_GAUGES=1 BT_START_INSIDE=1, tools/mp_launch.sh) were used to drive in
multiplayer -- which made the same-day paint change look guilty.  Confirmed on
the pre-paint build too; the trigger was the Jul-16 audio attribute work.

1) BT_DEV_GAUGES crash (cdb: CoolingLoopConnection::Update, ~15s in as the gauge
   builds lazily).  The dev-gauge cooling-loop lamp reached the cooling master by
   RAW attribute index -- GetAttributePointer(3) + *(master+0x1d4).  The audio
   commits (cc2b109 ReportLeak etc.) inserted rows into the chained attribute
   tables, so index 3 shifted onto a scalar, the resolve walked garbage, and the
   background pass AV'd.  Fix: route through a complete-type bridge reading NAMED
   members (heat.cpp BTCoolingLoopFrame: linkedSinks.Resolve() +
   Condenser::condenserNumber) -- the databinding rule; never a raw numeric
   attribute index.  Removed the now-orphaned GetAttributePointer-by-index
   helper.  Name-keyed samplers (InputVoltage) were unaffected.

2) -net dead controls mapper (mech wouldn't walk; turning/weapons still worked).
   The RIO mapper is built from a stack SubsystemResource in btl4app.cpp that set
   only name/classID/modelSize -- leaving subsystemFlags as stack GARBAGE, which
   Subsystem::Subsystem copies into simulationFlags.  A stray DontExecuteFlag
   (0x2) froze the mapper (speedDemand stuck at 0).  Config-dependent: clean in
   SP, dirty in -net; the audio commits shifted the stack and flipped the bit.
   Fix: memset each hand-built control-mapper resource to 0 (flags 0 =
   AlwaysExecute, the faithful value -- a mapper must tick every frame).

Verified with the pod launch flags: SP alive 32s no crash; MP mech walks
(speedDemand 61.5 at full throttle, ~430u traveled), both nodes alive, no crash.

KB: reconstruction-gotchas.md gains gotcha 18 (uninitialized stack-resource
flags + the raw-attribute-index-into-a-growing-table variant) and a
verify-under-the-user's-launch-flags note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-17 13:58:32 -05:00
co-authored by Claude Opus 4.8
parent e0474ff92a
commit 63250aee41
4 changed files with 104 additions and 27 deletions
+35
View File
@@ -250,6 +250,11 @@ state transitions: charge/seek loops, snap comparisons, timers compared with `==
ucrtbased **abort() dialog** ("Debug Error!"), not an AV — `sxe av` won't break there; the box
blocks the event loop (a headless node just "stops logging"). cdb: run with a config that does
`g` then `kb 40` — the int3 lands ON the aborting thread. [T2]
- **Verify under the USER'S launch flags, not a bare run:** a "30 s regression: stable" check that
omits `BT_DEV_GAUGES=1` / `BT_START_INSIDE=1` (the `tools/mp_launch.sh` set) misses any bug on the
gauge/cockpit paths those flags gate. The task-#7 gauge crash was DORMANT for a day because every
post-commit check ran flagless; it surfaced the instant the pod launch config was used. Reproduce
with the real launcher's env, and drive (`-net` keyboard) — not just spawn-and-look. [T2]
---
@@ -345,6 +350,36 @@ hundreds of pixels off-scope (the radar looked simply "empty"; nothing crashed,
decompile) before assigning it an engine method — a wrong-but-plausible identity survives
every compile and every "it runs" test.
## 18. Uninitialized stack-built resource → garbage `simulationFlags` (the "worked for weeks then broke" trap)
**Symptom:** a subsystem installed from a HAND-BUILT (stack) `*__SubsystemResource` silently stops
ticking — `IsNonReplicantExecutable()` returns false because a stray `DontExecuteFlag` (0x2) bit is
set. Config-dependent and nondeterministic: it "works for weeks" then an UNRELATED commit that shifts
the stack layout before the install flips the garbage bit. Archetype (task #7, 2026-07-17): the RIO
controls mapper built on the stack in `btl4app.cpp MakeViewpointEntity``Subsystem::Subsystem`
copies `model->subsystemFlags` into `simulationFlags`, but the stack struct only set
`subsystemName`/`classID`/`subsystemModelSize`, leaving `subsystemFlags`+`segmentIndex` = stack
garbage. When the garbage carried 0x2 the mapper froze → `speedDemand` stuck at 0 → no forward motion
(MASKED in single-player when the garbage happened to be clean; surfaced in `-net` where the stack
differed; the July-16 audio commits shifted the stack and flipped it). **Cause:** partial init of a
struct the engine reads in full. **Fix:** `memset(&res, 0, sizeof(res))` before filling ANY
stack-built resource — flags 0 = `AlwaysExecute`, the faithful value for a mapper (it MUST tick every
frame). **Tell:** a subsystem present in the roster but absent from the executed set; instrument the
tick's `!IsNonReplicantExecutable()` branch to name the slot + dump `simulationFlags` (bit 0x2 =
DontExecute). **Rule:** zero every hand-built resource struct; never rely on a partially-filled one.
[T2]
**Related — raw numeric attribute index into a GROWING table (same root as gotcha 11):** the dev
gauges' `CoolingLoopConnection` reached the cooling master with a RAW `GetAttributePointer(3)` +
`*(master+0x1d4)` walk. The July-16 audio work inserted attribute rows (`ReportLeak` etc.), shifting
the chained ids — index 3 landed on a scalar, the resolve walked garbage, and the gauge background
pass AV'd (only with `BT_DEV_GAUGES=1`, the pod launch flag, ~15 s in when the gauge builds lazily —
the cdb stack pinned it to `CoolingLoopConnection::Update`). **Fix:** route through a complete-type
bridge reading NAMED members (`heat.cpp BTCoolingLoopFrame`: `linkedSinks.Resolve()` +
`Condenser::condenserNumber`) — the databinding rule (gotcha 8), never a raw numeric attribute index.
The name-keyed samplers (`PowerSourceConnection`/`GeneratorVoltageConnection`, which look up
`"InputVoltage"` by NAME) were unaffected — name lookup survives table growth. [T2]
---
## Diagnostic recipe (the standard loop)