Files
BT411/context/subsystems.md
T
arcattackandClaude Opus 4.8 86e387b6c7 Valve forensics + KB: the boost mechanic is real; the field cook-off was a detent question
Measured A/B/C (Blackhawk, sustained autofire, 150s soaks): SRM6 on
Condenser2 plateaus ~531 and COOLS at share 0.91 (boost 2), climbs to
the jam band at balanced (0.167), and runs at the 2000 failure line
starved (0.018).  MoveValve reruns the redistribute every press;
veteran passes the novice guard.  The field 'boosted loop 2 still
cooked' therefore means the valve was NOT at the 50 detent during the
fight -- the 1->5->50->0 cycle turns the loop OFF one press past max.
MoveValve presses now log always-on ('[valve] <name> -> detent N' +
a CLOSED warning) so the next field session records the detents.

Also: solo mission clock verified live (60s egg self-ends,
'[mission] solo game clock expired' -> RunMissions returns).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:59:04 -05:00

12 KiB
Raw Blame History

id, title, status, source_sections, related_topics, key_terms, open_questions
id title status source_sections related_topics key_terms open_questions
subsystems Subsystems — the factory + the reconstruction waves established PROGRESS_LOG.md §10d; docs/SUBSYS_PLAN.md; docs/VEHICLE_SUBSYSTEMS.md; docs/RESOURCE_AUDIT.md
decomp-reference
reconstruction-gotchas
combat-damage
gauges-hud
subsystem
factory
ClassID
subsystem-roster
PoweredSubsystem
bridge
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 has an ORIGINAL latent bug [T1, task #63]: SearchlightSimulation (@004b841c) reads requestedOn@0x1E0 (never written) while ToggleLamp (@004b860c) toggles commandedOn@0x1DC — no bridge, so lightState@0x1D8 is perpetually 0 (lamp never lights). Present in the 1995 binary (sibling ThermalSight reads the field it toggles, 0x1DC); the port reproduces it faithfully. Left as-is by decision; it makes the searchlight→fog swap inert. See open-questions + rendering fog.
  • WAVE 5 — Torso (aim twist/elevation; joint-I/O reconstructed; the BLH record disables torso → faithfully no visible twist).
  • WAVE 6 — Myomers (mover-coupled; structural un-stub is INERT — no live mover/heat coupling).
  • 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.

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