in the leak chain HeatSink::UpdateCoolant prices the damage-driven leak from the QUALIFIED engine member (Subsystem::damageZone @0xE0): coolantDraw = zoneDamage * heatLoad, with the coolantActive hysteresis (= the ReportLeak attribute the 19 authored leak watchers ride). But the MechSubsystem ctor only ever filled its re-declared SHADOW member (gitea #64, gotcha #1), so the engine member stayed NULL forever, zoneDamage pinned 0, and a coolant leak was STRUCTURALLY IMPOSSIBLE no matter how much damage landed -- Oracle's night-7 report exactly. The #80 crit fix and the #83 collision rattle write real subsystem-zone damage, but into an object the leak reader could never see. Fix: alias the ENGINE base member to the same zone in both MechSubsystem ctors. Every shadow reader keeps working (same object); the engine base Subsystem::TakeDamage latent null-AV (#64 consequence 2) is disarmed; the full #64 de-shadow sweep remains the long-term cleanup. Verified live (solo, BT_COOL_LOG/BT_CRIT_LOG): collision rattle drove a damaged Myomers to [cool] draw = dmg*heatLoad with the level draining; sustained weapon fire rolled real crits ([critroll]) and produced 588 leak-pricing lines. Authored routing extracted from BTL4.RES: collision rattle targets HeatSinkBank 0.3 / Gyro 0.35 / Torso 0.25 / Myomers 0.35 (Condenser+Reservoir authored 0 for collisions); weapon crits select via the zone's crit-entry list. Known-remaining fidelity item (documented): HeatSink::DrawCoolant (slot 14, central top-up) is still a return-0 TODO. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J
251 lines
21 KiB
Markdown
251 lines
21 KiB
Markdown
---
|
||
id: subsystems
|
||
title: "Subsystems — the factory + the reconstruction waves"
|
||
status: established
|
||
source_sections: "PROGRESS_LOG.md §10d; docs/SUBSYS_PLAN.md; docs/VEHICLE_SUBSYSTEMS.md; docs/RESOURCE_AUDIT.md"
|
||
related_topics: [decomp-reference, reconstruction-gotchas, combat-damage, gauges-hud]
|
||
key_terms: [subsystem, factory, ClassID, subsystem-roster, PoweredSubsystem, bridge]
|
||
open_questions:
|
||
- "Myomers coupling (gates on owning-player @mech+0x190); factory capability-roster loops 2-4 still dead (SubProxy stub)"
|
||
---
|
||
|
||
# Subsystems
|
||
|
||
The mech's components (heat/power/weapons/sensor/gyro/torso/myomers/…), built by the Mech-ctor
|
||
factory. Full per-family plan: `docs/SUBSYS_PLAN.md`; the engineering-cluster panels:
|
||
`docs/VEHICLE_SUBSYSTEMS.md`; the layout audit: `docs/RESOURCE_AUDIT.md`. ClassID map:
|
||
[[decomp-reference]] §2.
|
||
|
||
## The factory
|
||
The Mech ctor switches on the resource ClassID to build each subsystem into the [[subsystem-roster]]
|
||
(`subsystemArray@0x128`). **The `case <Name>ClassID` LABELS are systematically MISLABELED** — the
|
||
real class is the `// FUN_004xxxxx` ctor-address comment reconciled via `CLASSMAP.md`, not the case
|
||
name (e.g. 0xBBE case="Sensor" is actually AggregateHeatSink). Each real class is wired via a
|
||
`Create<Class>Subsystem(Mech*,int,void*)` **bridge** in the class's own `.cpp` (do NOT `#include` the
|
||
real subsystem headers into mech.cpp — the local stubs collide). Keep the alloc SIZE + special-cache.
|
||
[T2]
|
||
|
||
## Class hierarchy (two branches, shared only at MechSubsystem)
|
||
- **Heat leaf:** `PoweredSubsystem : HeatSink : HeatableSubsystem : MechSubsystem`. Emitter/PPC/
|
||
Sensor/Myomers/ProjectileWeapon/MissileLauncher/GaussRifle chain here. Uses 0xC `SubsystemConnection`
|
||
+ 0x54 `GaugeAlarm54` (alarm level at +0x14). [T1]
|
||
- **Watcher branch:** `Torso/Gyroscope/Searchlight/ThermalSight/HUD : PowerWatcher : HeatWatcher :
|
||
MechSubsystem`; `AmmoBin : HeatWatcher`. Uses a 0xC `WatchedConnection` + 0x54 `WatcherGaugeAlarm`. [T1]
|
||
Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER in one build, each
|
||
`static_assert`-locked (the P7 alarm-unification: HeatSink 0x1D0 → PoweredSubsystem 0x31C → MechWeapon
|
||
0x3F0 → Emitter 0x478). [T2]
|
||
|
||
## Reconstruction waves (state: ALL 20 factory cases wired to real classes — 0xBD3 = the real SubsystemMessageManager since task #7, 2026-07-11; the mapper lives in roster slot 0)
|
||
- **WAVE 1** — HeatableSubsystem re-based onto MechSubsystem (de-shadow cascade).
|
||
- **WAVE 2** — heat family (Condenser/AggregateHeatSink/Reservoir) + HUD + MechTech (un-swap).
|
||
- **WAVE 3** — power bus (Generator/PoweredSubsystem) + Emitter/PPC fire-path (end-to-end fire; heat
|
||
conducts to the central sink via the linked-sink roster).
|
||
- **WAVE 4** — standalone readouts: Sensor/Searchlight/ThermalSight/AmmoBin (de-shim, gate fixes).
|
||
- ✅ **Searchlight + ThermalSight buttons WIRED 2026-07-25 (#61)** — both classes' handler sets were
|
||
default-constructed blackholes (systemic cause #1, `docs/INPUT_PATH_AUDIT.md`). Each now chains
|
||
`PowerWatcher::GetMessageHandlers()` with its own id-3 `ToggleLamp`. **Verified live**: pad `0x14`
|
||
→ `requestedOn 0→1` → `lightState 0→1`; pad `0x12` → `requestedOn 0→1` → `thermalActive 0→1`.
|
||
- ❌ **RETRACTED: the "ORIGINAL 1995 latent bug" (old task #63) NEVER EXISTED.** It was an artifact of
|
||
a **swapped table attribution**: the claim compared Searchlight's Performance (@004b841c, reads
|
||
`requestedOn`@0x1E0) against **ThermalSight's** `ToggleLamp` (@004b860c, toggles `0x1DC`) and
|
||
concluded "no 0x1DC→0x1E0 bridge, so the lamp can never light". @004b860c is not Searchlight's.
|
||
Searchlight's own handler is **@004b838c** (table @0x51117C) and — like every sibling — toggles the
|
||
field its OWN Performance reads, `requestedOn`@0x1E0. Each class is internally self-consistent.
|
||
[T1 throughout. Attribution: un-pooled `"ToggleLamp"` strings adjacent to their own class names,
|
||
`section_dump.txt:69661-69711`. Body: @004b838c is a Ghidra EXPORT GAP (#60) but was **recovered by
|
||
raw disasm** of `content/BTL4OPT.EXE` (`scratchpad/dis838c.py`) — it toggles 0x1E0, raises
|
||
`updateModel` (+0x18) unconditionally, and has **NO novice gate** (ThermalSight-only).] Consequence: the searchlight→fog swap was **NOT** inert in the arcade, and
|
||
making it work in the port is FAITHFUL, not a designer-intent deviation. See [[open-questions]] +
|
||
[[rendering]] fog, both corrected.
|
||
- ⚠ Still open on Searchlight: `commandedOn`@0x1DC (ctor-seeded from subsystem resource +0x28,
|
||
@004b84dc) has **no identified role** — it is neither the toggle target nor read by @004b841c, and
|
||
it measured **21** live (not a boolean), which hints our SubsystemResource +0x28 ≠ the binary's.
|
||
- ⚠ ThermalSight's published attributes are mis-named: we publish `"LightState"→thermalActive@0x1D8`,
|
||
but the binary's table @0x511268 has **`"LightOn"`(id 3)→0x1D8** and `"LightState"`(id 4)→**0x1E0**
|
||
(the alarm). A gauge binding `ThermalSight/LightOn` currently misses. Not yet changed (gauge-binding
|
||
change needs its own verification pass).
|
||
- **WAVE 5** — Torso (aim twist/elevation; joint-I/O reconstructed; the BLH record disables torso →
|
||
faithfully no visible twist).
|
||
- **WAVE 6** — Myomers (mover-coupled; the mover feed is still inert — WAVE 6 cutover — but **DRIVE
|
||
HEAT IS LIVE**, #85 fix 2026-07-31, see below).
|
||
**✅ MYOMER DRIVE HEAT LIVE (#85, 2026-07-31) [T2].** The five Mech motion operands of the
|
||
drive-heat integrator `@004b8d18` were `return 0.0f` cross-family shims, so the (fully
|
||
reconstructed) integrator accumulated `ratio² · damageGain · 0` every tick — myomers ice cold at
|
||
any seek (Oracle's night-7 panel-confirmed report). Operands re-grounded from the raw disasm +
|
||
the decomp and mapped to named members via the complete-TU bridge `BTMechMyomerMotionSample`
|
||
(mech4.cpp): `+0x1C4` vec = `localVelocity.linearMotion`; `+0x82C` vec =
|
||
`localAcceleration.linearMotion` (the authentic 15-ring smoothed AccelerationLastFrame);
|
||
`+0x20C` = `moverMass` (the collision divert's cell — the old "motion gain" label was wrong);
|
||
`*(+0x250)` = the environment **gravity pointer** (`FUN_00421e2c` does `vy -= **(+0x250)`/tick).
|
||
Physics: `heat += ratio²·(1+dmg)·[0.005·½mv² + 0.2·m·g·|vy|·dt + 0.2·m|v||a|·dt]` — kinetic work
|
||
+ climb power + acceleration power. The binary `fabs`es v.y (the port's old draft didn't).
|
||
**Authored tuning extracted from BTL4.RES** (all 18 mech variants share one record):
|
||
VelocityEfficiency=0.995, AccelerationEfficiency=0.8, seek gears 0.3/0.5/0.7/0.9999 rec idx 2;
|
||
myomer thermal profile startT=77 / degradation=1000 / failure=2000 / conductance=1.9e5 /
|
||
thermalMass=2.5e5. Live-verified (solo arena, BT_MYO_LOG): standstill ≈ 0 generation, hard
|
||
circling drove T 77→1225 (past degradation) and the coolant loop pulled it back to ~520
|
||
equilibrium on slowing. ⚠ Two carried caveats: (1) the kinetic work term has **no dt** in the
|
||
binary (per-tick accumulate, a 1995 fixed-frame assumption) → heat/s scales mildly with frame
|
||
rate; kept verbatim, field-calibrate against pod veterans. (2) the climb term is wired but
|
||
inert: `Mover::localEnvironment` is never populated in the port (EnvironmentZone res type 23
|
||
unwired), so g reads 0 — see [[open-questions]].
|
||
⚠ **The registered Performance is the WRAPPER @004b8b9c, not the inner integrator @004b8d18**
|
||
[T1, fixed 2026-07-28]. The ctor @004b8fec stores `[0x511620]` into activePerformance, and that
|
||
pointer resolves to `0x4b8b9c`, whose first instruction is `call 0x4b0bd0` =
|
||
`PoweredSubsystem::PoweredSubsystemSimulation`; @004b8d18 is the drive-heat integrator it then
|
||
runs. The port had registered the INNER function, so Myomers never advanced its own electrical
|
||
state machine — and since that machine is the only thing that both enters `NoVoltage`
|
||
(`source == 0`) and walks it back (`NoVoltage → Starting → Ready`), a Myomers that lost power in
|
||
the death/reset window stayed there **forever**. The Torso is a PowerWatcher that MIRRORS its
|
||
watched subsystem, so `torso.cpp:570` held `effectiveTwistRate` at 0 = **Gitea #70, "torso twist
|
||
stops working after a death/respawn."** Measured: 2 dead windows per respawn before, 0 after;
|
||
pristine missions never showed it. **Order is load-bearing** — the base call precedes the
|
||
heat-model gate, and gating first also denied the electrical machine to every non-expert pilot
|
||
(`OwnerAdvancedDamage()` is the +0x260 flag, off below veteran).
|
||
**General rule this establishes:** when a subsystem's Performance address in a `PTR_LAB_*` slot
|
||
does not match the function you reconstructed, check whether the real target is a wrapper that
|
||
chains the base sim — the whole family does (`Generator`/`PoweredSubsystem` lead with
|
||
`HeatSink::HeatSinkSimulation`; the Torso's own perf @004b5cf0 leads with `UpdateWatch`).
|
||
**The full wrapper is now reconstructed** [T1/T2, 2026-07-28] — five blocks in binary order:
|
||
`@004b8bab` chain `PoweredSubsystemSimulation`; `@004b8bb9` the un-powered self-repair (see below);
|
||
`@004b8c1e` republish `outputVoltage@0x344` from the resolved source when the alarm is Ready;
|
||
`@004b8c5a` republish `speedEffect@0x31C` = `AvailableOutput(clamped V) / OwnerBaseSpeed()` — the
|
||
Mech base speed CANCELS, so `speedEffect` is a **0..1 fraction** of full drive carrying gear ratio,
|
||
thermal curve and zone damage; `@004b8ceb` run the inner integrator **only when `outputVoltage > 0`**.
|
||
Verified live: healthy `outV=10000 speed=1`, un-powered `outV=0 speed=0`, and 96/96 torso samples at
|
||
`elec=4` across two death/respawn cycles.
|
||
✅ **CORRECTED 2026-07-29 (#80): `@004b8bb9` — the un-powered self-repair — is LIVE, in 1995 and
|
||
now in the port.** The 2026-07-28 "dead code in the original too" verdict here was wrong about the
|
||
original: the frozen-at-0.6 experiment was measuring a PORT bug (the subsystem ctor wrote the
|
||
zone's armour/scales through the `ReconDamageZone` proxy at struct offsets +4/+8 instead of the
|
||
engine members at +0x140/+0x144, so the real `damageScale[]` stayed zero). The binary ctor
|
||
(`@0x4ac7bb`) initializes them from the resource keys `WeaponDamagePoints` + the five per-type
|
||
`...DamagePoints`; the port now does the same through the engine's named members, and the CSS
|
||
parses the keys. An un-powered, not-yet-destroyed myomer heals at `0.011 × damageScale[Explosive]`
|
||
per tick. The retracted "cannot damage a subsystem's own zone" rule is superseded — see
|
||
[[combat-damage]] §CORRECTED for the full mechanism (this also revived the crit sink, #80).
|
||
Also un-stubbed here: `Myomers::DamageStructureLevel()` returned a hardcoded `0.0f`, which pinned
|
||
`AvailableOutput`'s `(1 - damage)` factor at 1 — a shot-up myomer drove exactly as well as a fresh
|
||
one. Now routed to the base bridge `GetSubsystemDamageLevel()`; measured `dmg=0.6 → speed=0.4`.
|
||
- **WAVE 7** — projectile/missile weapons (byte-exact; flying projectiles are a PORT reconstruction —
|
||
the 2007 Entity is 0x1BC vs the binary's 0x300, so raw base-offset reads fail).
|
||
- **TASK #56 (2026-07-11)** — Gyroscope LIVE byte-exact (ctor @004b3778, sizeof 0x3D0 locked;
|
||
integrators + `FUN_004b2980` damage fan-out; joint dispatch from the Mech performance tail
|
||
@0x4aaf74/83; `BT_GYRO_LOG`/`BT_GYRO_TRACE`) — see [[cockpit-view]].
|
||
- **✅ DONE (task #7, `afefaee`)** — SubsystemMessageManager 0xBD3 (a
|
||
**damage/explosion consolidation hub** cached to `Mech[0x10d]`=0x434; the factory builds the REAL
|
||
class and `mech.hpp` names the slot `messageManager` — the old `controlsMapper` mislabel is swept,
|
||
the real mapper is roster slot 0. NOT the valve/heat-model gate — that's the owning **BTPlayer**
|
||
@mech+0x190 carrying the EXPERIENCE-level flags, see [[experience-levels]]). [T2]
|
||
|
||
## The watcher electrical chain (task #57, 2026-07-13 — the torso power gate)
|
||
The Watcher branch is POWERED indirectly: a watcher WATCHES another roster subsystem and mirrors
|
||
its electrical state. The full chain, byte-verified [T1]:
|
||
- **Data:** the model entry `WatchedSubsystem=<name>` → segment index **+2** at resource+0xE4
|
||
(`HeatWatcher::CreateStreamedSubsystem` @004aec54); the ctor @004aeb40 stores it at
|
||
watcher+0x128 (`watchedSubsystem`).
|
||
- **Bind (factory post-roster loop 1):** vtable slot 14 (+0x38) — `@004aee2c` (HeatWatcher) /
|
||
`@004b1a40` (PowerWatcher/Torso override, byte-identical); **Ghidra missed both function
|
||
starts** (recovered from raw bytes; the vtables.tsv rows have GAPS where the exporter skipped
|
||
slots — dump the exe bytes at vtable+slot*4 when a slot looks missing). Master-gated
|
||
(`(owner->simulationFlags & 0xC)==0 && (flags & 0x100)`); binds `watchedLink(+0x114).Add(
|
||
owner->roster[+0x128][watchedSubsystem])`. Port: `BTWatcherWatchedIndex`/`BTWatcherBindTarget`
|
||
bridges (heatfamily_reslice.cpp) called from the mech.cpp factory loop.
|
||
- **Tick:** `@004b181c` = the REAL `PowerWatcher` Performance (PTR @0050f5fc → 004b181c) =
|
||
`UpdateWatch()`: heat mirror (FUN_004aeac4) + `watchdogAlarm.SetLevel(watched->electrical
|
||
level @+0x278)` + brownout downgrade to 1 when `gen->outputVoltage(+0x1DC) <= minVoltage(+0x180)
|
||
× gen->ratedVoltage(+0x1D8)`. The Torso sims (@004b5cf0/@004b65f8) call it first-line.
|
||
(`@004b1804` is slot-10 **ResetToInitialState**, NOT the Simulation — old recon mislabel, fixed.)
|
||
- **MinVoltageScale = 0.01** — a 10-byte x87 literal at 0x4b1924 (`0a d7 a3 70 3d 0a d7 a3 f8 3f`);
|
||
the port had 1.0f, making minVoltage 100× too big → the brownout latched every watchdog at 1.
|
||
- **PowerWatcher::GetClassDerivations chains HeatWatcher** (real base) — the old HeatableSubsystem
|
||
stand-in broke `IsDerivedFrom(HeatWatcher)` for Torso/Searchlight/ThermalSight and silently
|
||
skipped them in the connect pass.
|
||
- **Effect:** `Torso::ElectricalStateLevel()==Ready(4)` un-gates `effectiveTwistRate` — the MadCat
|
||
torso twists at its authored 50°/s (±140° limits, roster 17 watching 15 → generator @10000V);
|
||
the BLH is authentically fixed (`horizontalEnabled=0`, limits ±0.01°). [T2 live-verified]
|
||
- **STILL DEAD:** factory loops 2-4 (heatable/weapon/damageable capability rosters) go through the
|
||
`SubProxy` stub whose `IsDerivedFrom` returns 0 — they add NOTHING. See [[open-questions]].
|
||
|
||
## Coolant LEAKS (Gitea #88 fix, 2026-07-31) — the #64 shadow was the single break [T2]
|
||
`HeatSink::UpdateCoolant` (@004adbf8) prices the damage-driven leak: `coolantDraw =
|
||
ownZone->damageLevel × heatLoad`, floor 0.0025, and the `coolantActive@0x138` hysteresis
|
||
(ON >0.003) IS the `ReportLeak` attribute the 19 authored leak watchers (3-note warning) ride.
|
||
The read is the **qualified ENGINE member** `Subsystem::damageZone@0xE0` — which the port's
|
||
MechSubsystem ctor never assigned (it filled only its re-declared shadow, gitea #64 / gotcha #1),
|
||
so `zoneDamage` pinned 0 and a leak was **structurally impossible** regardless of damage. Fixed by
|
||
ALIASING the engine base member to the same zone in both MechSubsystem ctors (mechsub.cpp) — every
|
||
shadow reader is untouched (same object), the engine base `Subsystem::TakeDamage` null-AV is
|
||
disarmed, and the #64 full de-shadow sweep remains the long-term cleanup. Verified live: collision
|
||
rattle and weapon crits (`[critroll]`) both drive `[cool]` leak lines (draw ≈ dmg×heatLoad, level
|
||
draining, hysteresis arming). **Authored damage routing** (BTL4.RES): collision rattle targets
|
||
HeatSinkBank 0.3 / Gyro 0.35 / Torso 0.25 / Myomers 0.35 — Condenser + Reservoir are authored 0
|
||
for collisions; weapon crits select via the ZONE's crit-entry list. Remaining fidelity item:
|
||
`HeatSink::DrawCoolant` (vtable slot 14, the central-system top-up) is still a `return 0` TODO, so
|
||
a leaking subsystem drains its own loop; the pull-through to the central Reservoir gauge rides the
|
||
loop-flow machinery.
|
||
|
||
## The coolant FLUSH (Gitea #7, 2026-07-19) — Reservoir InjectCoolant end-to-end [T2 live-verified]
|
||
The manual-p24 coolant button (coolant MFD top-right; punch or HOLD): message id **4 "InjectCoolant"**
|
||
on the Reservoir (handler table @**0x50e680**, one entry → @4aee70 — same per-receiver id space as the
|
||
Condenser's MoveValve @0x50E52C). Handler @4aee70: novice-lockout gate (FUN_004ac9c8), press
|
||
(data≥1) → `reservoirAlarm.SetLevel(1)` **gated on coolantLevel@0x12C > 0** (+300 decimal = 0x12C =
|
||
coolantLevel — the old "currentTemperature" reading was a misdecode), release → level 0; ForceUpdate.
|
||
While level==1 the CoolantSimulation Performance (@4aef78) runs **InjectCoolant @4aefa4** each frame:
|
||
work list = condenser chain @+0x7cc + weapon chain @+0x7bc + heatable chain @+0x7ac + linkedSinks
|
||
master (port walks the roster with the same membership tests — chains are SubProxy-dead; condensers/
|
||
weapons/bank get the binary's duplicate-visit weighting; watcher-branch members are filtered to
|
||
HeatSink [T2 guarded]); per sink with flowScale@0x15C≠0: move `squirtMass@0x224 × flowScale × dt`
|
||
clamped to [-resLevel, 0] (reservoir drains → the coolant vertBar C `Reservoir/CoolantMass` drops),
|
||
sink level clamped [0, capacity], and `pendingHeat@0x1C8 += -(sinkE×|Δ|/sinkLvl − resE×|Δ|/resLvl)`
|
||
capped at `sinkMass@0x154 × res->startingTemperature@0x13C`, clamped ≤0 (only cools) — the heat
|
||
relief rides the existing heat model. **Two ctor corrections surfaced:** (1) the master gate @4af408
|
||
(and @4aeb40 HeatWatcher) reads `*(param_2+0x28)` = the **owner MECH's simulationFlags**, not the
|
||
resource's subsystemFlags (the resource read left the Reservoir Performance unregistered → no drain);
|
||
(2) `(float10)*(int*)(link+0x1d0)` is a **FILD of the bank's integer HeatSinkCount** — capacity =
|
||
0.05 (`_DAT_004af518`) × heatSinkCount × streamed CoolantCapacity (BLH: 0.05×6×20 = 6), not a float
|
||
reinterpret (which gave ~1e-44 → empty tank). Visual: the binary's mode-1 coolant-effect renderable
|
||
(FUN_00456a68, built for classID 0xBC0 on the "ReservoirState" attr, part_014.c:5439; tick
|
||
@part_007.c:8780) spawns **psfx 19 = FLUSH.PFX** ("Coolant flush", BTDPL.INI:1018, bluish smoke) on
|
||
the state's 0→1 edge — port: `BTSpawnFlushCloud` (mech4.cpp) on the same alarm edge, attached emitter
|
||
at torso height. Audio (CoolantDump layers) rides the ReservoirState watchers. Desktop input: **'H'
|
||
HELD = Flush** (CONTROLS.MAP action; press+release dispatch). Diags: `BT_FLUSH_LOG` ([flush-tx]
|
||
press/release, [flush] level drain, cloud spawn), `BT_FLUSH_TEST=<frame>` scripted ~2 s hold.
|
||
Verified live (FOGDAY): level 6→0 over ~0.6 s held (≈3-4 short punches to empty — matches the
|
||
manual), bluish cloud screenshot-confirmed (scratchpad/flush_cloud.png). Replicant note: the state
|
||
receive @4aeef8 sets the alarm from the state stream — the port's reservoir state replication is not
|
||
wired, so remote clouds are deferred (solo/master path complete).
|
||
|
||
## Valve boost A/B/C (2026-07-23, field "boosted loop still cooked" audit) [T2 measured]
|
||
Blackhawk, sustained BT_AUTOFIRE, 150 s soaks, `[heat-t]` 5 s cadence. Valve shares are
|
||
zero-sum (`valveState/Σ`); detent cycle per press = **1 → 5 → 50 → 0 (CLOSED) → 1** (@4ae464,
|
||
one past max turns the loop OFF). SRM6s (+ GeneratorB + Avionics) are on **Condenser2**
|
||
([heat-link] dump; the MFD loop readout matches the heatSinkIndex truth). Results, SRM6_1:
|
||
- **boost 2 (share 0.91)**: plateau ~531, then COOLS under nonstop fire — never jams/fails.
|
||
- **balanced (0.167)**: ~1162 @ 70 s and climbing (the jam band; field jams logged 1043-1469).
|
||
- **starved (0.018)**: ~1440 @ 70 s, slope → the 2000 FailureHeat line.
|
||
The valve mechanic is REAL and powerful; MoveValve reruns the redistribute on every press
|
||
(not a placebo), veteran role passes the guard. The field double-failure with "loop 2
|
||
boosted" is therefore a DETENT question — most likely one press past max (detent 0 = closed,
|
||
share 0) — now diagnosable: valve presses log always-on (`[valve] ... -> detent N`, with a
|
||
CLOSED warning). Solo clock shim verified the same day (60 s egg self-ends;
|
||
`[mission] solo game clock expired`).
|
||
|
||
## The four systemic checks (every subsystem)
|
||
See [[reconstruction-gotchas]]: (1) shadowing (re-declared engine-base fields), (2) the `Wword` trap,
|
||
(3) message-handler chaining, (4) entity validity. Plus resource-struct layout (must mirror the class
|
||
hierarchy, byte-exact — the RESOURCE_AUDIT found 8 bugs) and object-layout (alias/phantom/interior
|
||
fields). A `+0x128`-style owner offset is the ROSTER, not the segment table (the heat-link type
|
||
confusion that caused a heap-corruption via OOB `ConductHeat` writes). [T2]
|
||
|
||
## Engineering cluster panels (`vehicleSubSystems`)
|
||
Not a widget — a per-subsystem FACTORY (`FUN_004cbaf0`) building a status CLUSTER onto one of 12 aux
|
||
MFD positions, dispatching on classID → HeatSinkCluster/MyomerCluster/EnergyWeaponCluster/
|
||
BallisticWeaponCluster. Reads `PoweredSubsystem::auxScreenNumber/Placement/Label` (res +0x104/8/C) via
|
||
the `BTGetSubsystemAuxScreen` bridge. See `docs/VEHICLE_SUBSYSTEMS.md` + [[gauges-hud]]. [T2]
|
||
|
||
## Key Relationships
|
||
- Data: [[decomp-reference]] (ClassIDs/hierarchy). Bugs: [[reconstruction-gotchas]].
|
||
- Feeds: [[combat-damage]] (weapons/damage), [[gauges-hud]] (attribute state).
|
||
- Plan: `docs/SUBSYS_PLAN.md`.
|