44 KiB
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 |
|
|
|
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 0xCSubsystemConnection- 0x54
GaugeAlarm54(alarm level at +0x14). [T1]
- 0x54
- Watcher branch:
Torso/Gyroscope/Searchlight/ThermalSight/HUD : PowerWatcher : HeatWatcher : MechSubsystem;AmmoBin : HeatWatcher. Uses a 0xCWatchedConnection+ 0x54WatcherGaugeAlarm. [T1] Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER in one build, eachstatic_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 VISUALS complete 2026-08-05 — cockpit fog swap + external spot.bgf beam cone, both MP-replicated; mountSegment@0x1DC identified (= resource segmentIndex, the cone's mount joint — was "commandedOn, role unidentified"). Full story: rendering §SEARCHLIGHT.
- ✅ 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 chainsPowerWatcher::GetMessageHandlers()with its own id-3ToggleLamp. Verified live: pad0x14→requestedOn 0→1→lightState 0→1; pad0x12→requestedOn 0→1→thermalActive 0→1. - ❌ RETRACTED: the "ORIGINAL 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'sToggleLamp(@004b860c, toggles0x1DC) 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 ofcontent/BTL4OPT.EXE(scratchpad/dis838c.py) — it toggles 0x1E0, raisesupdateModel(+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 bindingThermalSight/LightOncurrently 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 — ✅ MYOMER SYSTEM COMPLETE (2026-07-31). The "mover cutover" concept is DEAD — it
was built on a misattribution.
@004b9550/@004b95b8(the "ConnectToMover/DisconnectFromMover" bodies) are MechWeapon::ConfigureMappables/ChooseButton — their+0x31Cis the weapon TriggerState and**(mech+0x128)is the CONTROLS MAPPER's button roster (proven by the MECHWEAP.CPP assert string + the trigger-edge detector @004b9608 adjacent). ⚠ SAME-DAY CORRECTION (the seek audit): the binary DOES have a dynamic myomer→speed feed — it lives in the un-exported master-perf gap (@0x4a9cf2-0x4a9da4, raw capstone; the same gap that hid the #93 crash block). Per frame: MAXspeedEffect@0x31Cover the myomer chain (+0x7AC) →mech+0x79C→mapper->speedDemand@0x128 *= it, and at |drive| ≤ 1e-4 (@0x4ab16c) the mapper'sturnDemand@0x12Cis ZEROED — dead/overheated myomers freeze speed AND turn. The morning's "no feed" verdict was export-gap blindness (text sweeps cannot see un-exported functions; the decisive tool was a raw-image capstone scan for FPU reads of +0x31C). Field truth agrees: Oracle (pod veteran) — seek-4 humanoids ran 182 kph (the manual's printed Super Charged figure), overheat = "freezing up". The port restored the demand scale byte-grounded (mechmppr.cpp): MAX over the chain, UNCLAMPED (gear 4 rides ~1.43× demand into the RegisterMaxOutput cap = SUPERCHARGE), turn-freeze included; damage slows (speedEffect carries 1−damage — gitea #75's original premise was RIGHT). The rest of the myomer system, all live in the port:- Heat — the drive-heat integrator (#85, below).
- Vital death — myomers are the ONLY
vital=1subsystem in the authored records (BTL4.RESres+0x48scan): destroying them KILLS the mech (the #80 vital-crit path). This is also the collision ram-death mechanism (myomers are a 0.35-weight rattle target). - The assembly cap —
RegisterMaxOutput @004b8ef0:mech+0x7A0 = max(cap, AvailableOutput(topGear))= base × ~1.4284, the gait rise-clamp both channels bound against (part_012:12103/12334). Port: each myomer registers per tick (idempotent max(); the 0x358 layout lock leaves no latch room) — state-identical to once-at-assembly. The old per-framereverseSpeedMax2 = reverseStrideLengthheal (which would have clobbered it) is garbage-guarded only now.Mech+0x34C(base) = portreverseStrideLength,Mech+0x7A0(cap) = portreverseSpeedMax2— both documented misnomers. - The ENG-page power curve — SeekVoltageResponse, now at the correct absolute scale (OwnerBaseSpeed reads the real base instead of the 1.0 stub).
- Electrical sourcing — the dial's REAL stake is TORSO TWIST. A gear's voltage must be
<= the generator's measured output (
(1 − generator damage) × rated, thermal breaker on FailureHeat with recovery gated on NormalHeat — trip at T>2000, re-arm at T≤1000, exactly the manual p23 "cooled down half way" rule; full trip/recovery/warning-edge audit in decomp-reference §Generator trip/recovery (#154); there is NO load model — demand never pulls the bus down), or the myomers leave Ready. The Torso is a PowerWatcher on the myomers:TorsoSimulationZEROES the twist rate while the watched myomers are not Ready (halves it at DegradationHeat) — so an over-high gear on a damaged generator freezes the pilot's AIM until they downshift. On a healthy mech, gears 1-3 are literally identical (heat ratio floors at 1, no speed coupling); gear 4 = double heat + a tighter dropout threshold for nothing but the taller graph — a trap. (AspeedDemand *= speedEffectmultiplier was removed in the morning commit and RESTORED, byte-grounded and improved, the same day — see the correction above. Verified live: healthy drive=1.0, collision-rattled myomers drive 0.89→0.74 with demand scaling in lockstep.) ✅ MYOMER DRIVE HEAT LIVE (#85, 2026-07-31) [T2]. The five Mech motion operands of the drive-heat integrator@004b8d18werereturn 0.0fcross-family shims, so the (fully reconstructed) integrator accumulatedratio² · damageGain · 0every 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 bridgeBTMechMyomerMotionSample(mech4.cpp):+0x1C4vec =localVelocity.linearMotion;+0x82Cvec =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 (=Mover::localEnvironment, MOVER.h;FUN_00421e2c= ApplyAirResistanceAndGravity doesworldAccel.y -= **(+0x250)each frame — onto an accumulatorMover::PerformAndWatch@00422360 zeroes every frame, so it is a constant acceleration integrated ×dt, NOT an accumulating per-tick term; velocity/dt audit 2026-08-13). 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
fabses 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, an original 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::localEnvironmentis 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 to0x4b8b9c, whose first instruction iscall 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 entersNoVoltage(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, sotorso.cpp:570heldeffectiveTwistRateat 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 aPTR_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/PoweredSubsystemlead withHeatSink::HeatSinkSimulation; the Torso's own perf @004b5cf0 leads withUpdateWatch). The full wrapper is now reconstructed [T1/T2, 2026-07-28] — five blocks in binary order:@004b8babchainPoweredSubsystemSimulation;@004b8bb9the un-powered self-repair (see below);@004b8c1erepublishoutputVoltage@0x344from the resolved source when the alarm is Ready;@004b8c5arepublishspeedEffect@0x31C=AvailableOutput(clamped V) / OwnerBaseSpeed()— the Mech base speed CANCELS, sospeedEffectis a 0..1 fraction of full drive carrying gear ratio, thermal curve and zone damage;@004b8cebrun the inner integrator only whenoutputVoltage > 0. Verified live: healthyoutV=10000 speed=1, un-poweredoutV=0 speed=0, and 96/96 torso samples atelec=4across two death/respawn cycles. ✅ CORRECTED 2026-07-29 (#80):@004b8bb9— the un-powered self-repair — is LIVE, in the original 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 theReconDamageZoneproxy at struct offsets +4/+8 instead of the engine members at +0x140/+0x144, so the realdamageScale[]stayed zero). The binary ctor (@0x4ac7bb) initializes them from the resource keysWeaponDamagePoints+ 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 at0.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 hardcoded0.0f, which pinnedAvailableOutput's(1 - damage)factor at 1 — a shot-up myomer drove exactly as well as a fresh one. Now routed to the base bridgeGetSubsystemDamageLevel(); measureddmg=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_004b2980damage 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 toMech[0x10d]=0x434; the factory builds the REAL class andmech.hppnames the slotmessageManager— the oldcontrolsMappermislabel 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)); bindswatchedLink(+0x114).Add( owner->roster[+0x128][watchedSubsystem]). Port:BTWatcherWatchedIndex/BTWatcherBindTargetbridges (heatfamily_reslice.cpp) called from the mech.cpp factory loop. - Tick:
@004b181c= the REALPowerWatcherPerformance (PTR @0050f5fc → 004b181c) =UpdateWatch(): heat mirror (FUN_004aeac4) +watchdogAlarm.SetLevel(watched->electrical level @+0x278)+ brownout downgrade to 1 whengen->outputVoltage(+0x1DC) <= minVoltage(+0x180) × gen->ratedVoltage(+0x1D8). The Torso sims (@004b5cf0/@004b65f8) call it first-line. (@004b1804is 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-gateseffectiveTwistRate— 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
SubProxystub whoseIsDerivedFromreturns 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.
The central pull-through is ALSO live (same day): HeatSink::DrawCoolant was a return 0
stub; the real base @0x4add00 (disassembled) RECURSES UP the sink linkage — return resolve(linkedSinks)->DrawCoolant(requested × coolantFlowScale@0x15C) — terminating at the
Reservoir's supply (@0x4af3b0, already faithfully ported as Reservoir::DrawCoolant: clamp to
[0, coolantLevel], drain, return granted). Binary terminal hop = the bank's slot-14 override
@0x4ae8b0 resolving its reservoir connection @0x1D8 (the port's helper member — NOT vestigial);
the port terminates equivalently via the Reservoir-ctor Attach into the bank's linkedSinks +
the Reservoir override (the extra bank hop scales by its ctor-default flowScale 1.0 — a no-op).
Verified live ([resdraw]): a destroyed myomer's leak pulled the central Reservoir tank
6.0 → 5.58 over ~30 s — Reservoir/CoolantMass, the cockpit coolant BAR, now visibly drops
as a damaged mech bleeds coolant, and DeathReset refills it on respawn.
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.
Myomer drive-heat calibration — VERIFIED FAITHFUL (2026-08-09, #137) [T1]
The integrator @004b8d18 accumulates into pendingHeat@0x1C8:
gear² × (1 + X) × [ (1−velEff)·|vy|·m·g·dt + (1−velEff)·work + (1−accEff)·|v|·|a|·m·dt ]
with work = mass · |v|² · 0.5. Its constants, read byte-exact from .rdata
(section_dump.txt row 4b8ee0 5dc30000 0000003f 00000000 0000803f):
_DAT_004b8ee4 = 0.5f (the kinetic ½), _DAT_004b8ee8 = 0.0f (the Abs() idiom),
_DAT_004b8eec = 1.0f (the 1 − efficiency complements and the gear-ratio clamp floor).
All three match what the port computes — the formula and its authored inputs
(VelocityEfficiency 0.995, AccelerationEfficiency 0.8, thermalMass 2.5e5, myomers linked
Condenser5) are reconstructed correctly.
The one deliberate deviation, and why it is the faithful choice. The binary applies no
time_slice to the kinetic term (fVar5 * fVar1) while the climb and accel terms both carry
param_2 — it is a per-frame energy add at the pod's fixed ~28 Hz. The port uses
work × (time_slice × 28), which is identical at 28 Hz (dt·28 = 1.0) but holds the same
heat-per-SECOND at any frame rate. A literal transcription would add the full term once per
frame, so at 170 fps it would inject ~6× the heat the pod ever did. BT_MYO_HZ overrides the
reference rate for bracketing.
Consequence for #137 — CORRECTED 2026-08-09, the paragraph that stood here was wrong. The
"players read 'respawned with heat maxed' as acceleration to top speed" claim did not survive
the data: the deposits were e9-scale within 30 frames of the reset, physically impossible from
motion input (~6.5e5/frame ceiling). The actual cause was a dropped binary re-seed: the
binary Reset's second instruction (FUN_00408440(mech+0x58c, origin)) re-seeds the
previous-position memory of the AccelerationLastFrame ring feed (+0x81c..+0x82c); the port
reconstructed the ring but not the re-seed, so the first post-respawn sample computed
TELEPORT-DISTANCE/dt (~1e5) into the velocity ring, the ring-mean derivative spiked
AccelerationLastFrame, and termAccel = (1-accEff)·|v|·|a|·m·dt deposited ~3e9 into
pendingHeat in one tick → myomers snapped from 77 to ~9000 (failT 2000) → speedEffect 0 →
frozen until cooled. Fixed by restoring the re-seed (accelPrevPos = origin.linearPosition in
Mech::Reset). The ~8% field rate was the |v| factor: only pilots whose throttle was still
forward at the respawn (physical lever / HOTAS — exactly who reported it) had gait-republished
speed in the spike frames. The calibration facts above (constants byte-exact, dt-normalised
kinetic term) all STAND; the in-life governor (running hot at sustained top speed derates the
myomers) is authentic and remains.
Spawn-discharge loop heat + per-chassis generator bias — ✅ CONFIRMED (2026-08-13, #173 bench lane) [T2 live receipts + T1 authored data]
Oracle's explanation for the long-standing "missiles cook off on one side first" reports
(Lynx): at spawn ALL weapons start discharged, and the initial CHARGE of the energy weapons
dumps heat into their loops; generator assignments are biased per chassis (Avatar: BOTH large
lasers on GeneratorA), so that side's loops start hot and a missile launcher sharing the side
cooks off first even if the player fires only missiles. All three links verified:
(1) Weapons spawn discharged — Emitter ctor @004bb120 AND ResetToInitialState @004ba4d0 both
seed currentLevel=0 + weaponAlarm Loading [T1]; (2) charge heat lands in the powering
generator — TrackSeekVoltage @004ba838: src->pendingHeat += seekRate² · dtScale · dt,
and the generator conducts to its authored condenser (GenA→C1, GenB→C2, GenC→C3, GenD→C5,
uniform all 18 variants) [T1]; (3) the Avatar bias is authored — BTL4.RES avatar chain:
ERLLaser_1 AND ERLLaser_2 both VoltageSource=GeneratorA (plus ERMLaser_1) [T1].
Live receipt (solo avatar, BT_HEAT_LOG, ZERO shots fired, first 5 s census,
m173_ava_s4.log): GeneratorA T=559 load pegged 1.0 vs GeneratorC T=172 / GeneratorD T=79;
Condenser1 (GenA's loop) 390→459 while C4/C6 sit at 75; and LRM10_1 (authored to loop 1)
reads 144 vs LRM10_2 (loop 3) at 94 — the asymmetric missile-side heating with no trigger
pulled. Loops also BACK-HEAT their weapons (ConductHeat is two-way): vulture's ERMLasers on
hot loop 2 climbed 76→182 in 10 s, zero firing (m173_vul_s1.log). [T2]
Related open defect/question: #173 -- Vulture (and per Oracle ALL large chicken walkers) cannot run seek-4 myomers without immediate overheat, loop 5 pegged, heat only sheds near standstill (Elengil; Oracle repro). ⚑ SETTLED 2026-08-13 by the three lanes below (authored data + cooling-side audit + live bench matrix): AUTHENTIC authored balance, reproduced and quantified; one bounded [T3] severity residual (accel-term ring cadence) and the locomotion top-speed cross-check remain flagged. See the BENCH lane block for the numbers.
#173 COOLING-SIDE AUDIT (2026-08-13) -- the binary heat chain leans AUTHENTIC [T1 data/formulas, T2 equilibrium]
Full authored heat-family dump (content/BTL4.RES type-17 walk, scratchpad heatscan3.py) + the dissipation chain from the decomp. The chain: Myomers -> Condenser5 -> Bank -> ambient-300.
- Cooling is authored CHASSIS-FLAT [T1]: every one of the 18 variants streams IDENTICAL condensers (k=315000, mass=420000, refrig=3, degT=failT=2000), myomers (k=190000, mass=250000, velEff .995/accEff .8, gears 0.3/0.5/0.7/0.9999 rec 2 at res+0x198..0x1AC), generators (rated 10000, GeneratorD AND Myomers both sinkIdx=8 = Condenser5 on ALL chassis), reservoir, and bank (k=231000, mass=1.39e6) -- the ONLY per-chassis field is bank HeatSinkCount (owens 5, blkhawk 6, mad2 10, mad1/thr1/sunder 13, madcat 14, avatar/loki 15, vulture/vul1/lok1/lok2 18, thor 28, snd1 30), and at balanced valves it moves max shed by <2% (bottleneck is condenser->bank). moverMass (type-15 GameModel +0x0): owens 35k, blkhawk/VULTURE 60k, loki 65k, avatar/thor 70k, madcat 75k, sunder 90k. No per-chassis gear cap exists (shared 4-gear table + sentinel).
- NO speed-dependent cooling anywhere in the binary [T1]: full operand walks of
ComputeHeatFlow @004ad9ec (dt, k, coolantLvl/cap, flowScale, masses only), the bank radiator
@004ae73c (adds zone damage + frozen ambient-300 target), UpdateCoolant @004adbf8 (damage-driven
leak). No airflow term. "Cools only near standstill" = generation (~0.07·m·v²·ratio²/s kinetic
- 0.2·m|v||a| gait term) vs a FIXED-capacity chain. Authentic behavior, not a port break.
- Seek-4 heat multiplier is 2.04x, NOT 16x [T1]: ratio = gears[cur]/gears[rec] CLAMPED >= 1 (@004b8d18), so gears 1-3 are heat-identical and gear 4 = (0.9999/0.7)^2 = 2.04x -- times the supercharge speed feedback (v x1.4284 -> v^2 x2.04) = ~4.2x total at unrestricted cruise.
- Equilibrium math validated against live data [T2]: linearized per-second stage rates (massScale=3 refrigeration included): myo->cond 44.4k/K-of-dT, cond->bank 28.8k x valveShare x (2.73·Tc - 0.91·Tb), bank->ambient 23.1k x hsCount x (Tb-300). Solver (scratchpad equilib.py) reproduces the measured BLH gear-4 governed equilibrium (predicted 1783 @ v=44 vs measured 1718-1770, seek4.log) and the radiator shed line. Balanced-valve chain capacity ~= 1.9e7/s at Tm=2000 on EVERY chassis; loop-5 boost (detent 50) = 2.7x capacity (5.07e7/s) -- the authored lever that sustains unrestricted gear-4 on a vulture (0.67-0.83x of capacity).
- Why the big chicken walkers cook: gear-4 unrestricted generation / failure-line capacity = 1.77x for blkhawk (hovers at ~1770, keeps moving) vs 2.2-2.8x for madcat/thor/vulture-if-faster (blows THROUGH failT 2000 -> myomers fail -> speed+turn freeze; ~6 s to degT, ~12 s to failT from cold; Condenser5's MFD bar pegs in ~3 s since load full-scale ~= T 300). GeneratorD shares loop 5 everywhere, so a pegged loop back-heats it toward its own trip -- the freeze amplifier.
- Residual for the verdict [T3]: vulture mass = blkhawk mass (60k), so the field split
vulture-vs-blkhawk requires the vulture's CLIP-DERIVED base speed (mech+0x34C, run-cycle avg
root speed @0x4a80d4) and/or gait |a| bob to exceed the BLH's measured 43.6 u/s -- at equal
speed the two compute identically. Type-15 gamedata splits 33-36/43-44 into exactly two motion
groups (avatar/madcat3/sunder2/thor2/vulture2 vs blkhawk2/loki3/owens*2) [T4 field
meaning]. Bench lane: measure vulture v/|a| at gear 3/4 vs BLH.
#173 AUTHORED-DATA lane result (2026-08-13) [T1]: the heat/cooling plumbing is
BYTE-IDENTICAL across ALL 18 mech variants (parsed from
content/BTL4.REStype-17 SubsystemModelStream, parserscratchpad/night17/myoaudit.py; walk verified byte-exact -- every stream consumed to the byte via the self-describingsubsystemModelSize@+0x24). Every chassis gets the SAME: Myomers record (velEff 0.995 / accEff 0.8 / tmass 2.5e5 / cond 1.9e5 / seekV 0.3/0.5/0.7/0.9999 rec 2, hsi=8→Condenser5, vsi=13→GeneratorD), six identical Condensers (tmass 4.2e5, cond 3.15e5, refrig 3.0, degT=failT=2000), identical Generators (rated 10000 V, tap 6, GenD always hsi=8→Condenser5), identical Reservoir (cap 20, squirt 0.5). Loop 5 = GeneratorD + Myomers ONLY on madcat/vulture/vul1/thor/ thr1/loki/lok1/lok2 (the fielded chicken walkers carry the MINIMUM possible loop-5 load); the variants with extra loop-5 weapons are mad1 (2 MLaser), mad2 (ERPPC), ava1 (2 ERMLaser), own1 (AFC25), sunder (2 MLaser) -- no walker bias. Manual COOLANT LOOPS pages print the same GenD+Myomers loop-5 topology for every mech [T1 primary]. The ONLY chassis-varying heat inputs are moverMass (type-15 GameModel +0x00: owens 35t, vulture/blkhawk 60t, loki 65t, thor/avatar 70t, madcat 75t, sunder 90t) and top speed/accel (manual stat sheets: Loki/Thor 143 N / 182 SC kph, Vulture ~175 N [OCR-fuzzy T3], Owens 154, Blackhawk ~190 [OCR-suspect]; maxAccel authored: madcat 20, loki 15, vulture/thor/avatar/sunder 10, blkhawk 30, owens 50). m·v² through the verified formula puts the 60-75 t / 143-175 kph walkers at ~1.6-2.2x the light-humanoid kinetic heat rate into the SAME fixed-size loop -- seek-4 zero-cooling degradation bound ~7-10 s (vulture worst) vs ~16 s (owens). Verdict lean: AUTHENTIC authored balance (fast-heavy chassis cook by design); residual port-defect risk sits ONLY in the runtime velocity magnitude (locomotion lane -- cross-check the port's per-chassis top speed vs the manual print figures; bench lane #9).
#173 BENCH lane result (2026-08-13) [T2 measured, live receipts]: Elengil's report
REPRODUCED and quantified; the equilibrium is a GOVERNOR THERMOSTAT, and it is chassis-blind
above a speed threshold. Rig: solo grass/day expert, full forced throttle + constant 0.12
turn, BT_FORCE_SEEK, 150 s/run (scratchpad/night16/myo173.sh + myo173b.sh, digest
digest173.py, logs content/m173_*.log). Streaming receipts on every chassis matched the
authored data exactly ([heat-link] Myomers sinkIdx=8→Condenser5 k=1.9e5 m=2.5e5; [spec]
hsCount 18/14/28/13/15/90t...; C5 mScale=3 flowScale=0.1667 balanced). Matrix (peak v /
steady v u/s, time to degT 1000, equilibrium T, C5 end):
- vulture seek-1: 24.5/21.1, never, eq 423, C5 333 — gears 1-3 are harmless (ratio floors 1).
- vulture seek-4: 56.9/31.9, 13.9 s, eq 1801, C5 1410 (loop-load bar full-scale ~15-20 s).
- madcat seek-4: 56.8/28.4, 12.1 s, eq 1820, C5 1420. avatar seek-4 (HUMANOID, same 143-kph speed class in the port): 56.2/29.5, 14.3 s, eq 1819, C5 1424 — identical cook.
- thor seek-4: 34.6/34.3, 79.9 s, 1311 still climbing; sunder (90 t): 34.6/34.0, 49.4 s, 1592 climbing — the port's slow (87-kph-class) chassis merely take longer.
- STRAIGHT-line control (no turn): eq 1802, |a|≈20 — same as turning, so the accel term is GAIT-SURGE noise in the velocity ring, not commanded motion (see residual below).
- Valve boost, the authored counter-play (one MoveValve press on Condenser5, detent 1→5 = share 0.5): myomers still governor-pinned ~1715 but sustained drive 0.68 vs 0.54 balanced (v 39-41 vs ~32) and C5 968 vs 1410 — boost buys back speed, not myomer temp. The run's accidental map-edge stop measured the standstill shed: 1723→586 in ~35 s (−10/s) — "heat only sheds near standstill" is the fixed-capacity chain being honest (slope +0.02/s while cruising). Mechanics of the thermostat: T rises past degT → AvailableOutput derates → v falls → m·v² generation falls → equilibrium lands ~1700-1820, just UNDER failT 2000, on EVERY chassis fast enough to out-run the loop — better cooling raises the sustained SPEED at the same pinned T. Steady seek-4 cruising never freezes (failT) solo; the field freezes need adders (damage dmgGain ≤2x, combat weapon heat, respawn transients, a GenD trip on the shared loop). Manual cross-check settles the speed residual in favor of AUTHENTIC [T1 print, re-read via fitz p24/25]: MadCat prints 175 N / 220 SC, Vulture 175 N, Thor/Loki 143/182 — the machine's big chicken walkers were FASTER than the port's measured 143 N / 205 SC (thor port 87/124 is far under its print), so authored m·v² generation on the pod was ≥ what these benches produced; the phenomenon (manual p21 warns of it explicitly) is authored. The port's per-chassis top-speed shortfalls remain a LOCOMOTION-lane item but cannot rescue a "port too hot" theory — correcting them makes it hotter. Verdict: AUTHENTIC mechanism + authored balance [T2]; ONE bounded port-side severity residual [T3] — the accel term's |a| operand (15-sample velocity-ring derivative) is frame-cadence-sensitive: at ~59 fps a straight flat cruise reads |a|≈14-20 (thor/sunder's smoother gait ≈4) and the accel term carries 50-65% of steady seek-4 generation; the same gait bob sampled through the pod's 28 Hz ring would read ~2-3x smaller, so the pod's equilibrium may have sat ~1200-1500 (shallow governor, near-full supercharge speed held) instead of the port's ~1800 (deep governor, speed sags to ~0.54). Same phenomenon, softer edge — this is the gap that would reconcile Oracle's "ran 182 kph sustained" pod memory. If field calibration is ever wanted, the knob is the |a| feed's cadence (sample the ring at 28 Hz), NOT the formula constants (byte-exact, #137). Also note the CLIMB term is inert in the port (g=0, EnvironmentZone unwired) — the pod added heat on slopes that we do not.
#173 DECISIVE VALVE-BOOST + CADENCE EXPERIMENT (2026-08-13 night17;
scratchpad/night17/m173v_bench.sh/m173v2_bench.sh/m173v_check.py, logs
content/m173_vul_[ABCEFG]*.log, captures content/vlv_*.png) — the residual above is now
MEASURED and the verdict sharpens:
- Valve delivery PORT-FAITHFUL, receipt-PROVEN [T2 live / T1 operands]: the
BT_VALVE5=Nhook (mech4.cpp, env-gated) presses Condenser5's MoveValve N times; every landed detent is receipted ([valve] Condenser5 valve -> detent 5/50/0+ the zero-sum redistribute lines: share 0.5 → 0.909091 → 0, others 0.1 → 0.0182 → 0.2, exactly @0049f788's flow=valve/total). C5 responds as flow dictates (cruise Tc5: balanced 1396 → detent-50 712 → CLOSED 1902 pegged). - The bottleneck SHIFTS to the valve-independent stage [T1+T2]: myomer→Condenser5 conduction measures 4.1–5.0e4 heat-units/K of dT in EVERY run (balanced/boosted/closed, both cadences) — constant because @004ad9ec's exponent reads the CALLER's own flowScale@+0x15C (the myomer's, pinned 1.0), never the condenser's valve share [T1 raw pseudocode]. The solver's 2.7x boosted whole-chain throughput is CONFIRMED live (4.8e7/s through the chain at 60fps detent 50) but its "sustains under degT 1000" corollary is WRONG at any cadence: sub-1000 Tm caps stage-1 flux at ~3e7/s < supercharge generation, so the governor always engages; the boost buys SPEED at a lower pinned Tm, never sub-1000 Tm.
- The cadence residual is REAL and ~2x [T2 measured] (BT_MYO_RING_HZ=28 knob landed in mech4.cpp — cadence only, constants untouched): at ~59fps the |a| gait-surge noise carries 64–66% of steady seek-4 generation (|a|≈17–26 flat grass); at 28Hz it drops to 24–32% (|a|≈6–7) and generation at equal speed HALVES. The six curves (solo vulture seek-4, t1000 / equilibrium / held v): 60fps bal 12.3s/1800/28 · 60fps d50 12.3s/1675/44 · 60fps CLOSED 13.9s/NO-EQ-1936-still-climbing/11.7 (drive 0.21, C5 pegged 1902 — the wrap-trap signature) · 28Hz bal 18.3s/~1710/39–41 · 28Hz d50 22.5s/~1500 STABLE/54–57 = FULL supercharge speed sustained indefinitely (equilibrium sits on the soft governor shoulder, zero speed cost; 500 under failT). The machine's loop-5 boost genuinely sustained unrestricted seek-4 — the manual's Super Charged print is vindicated at pod cadence; the port at ~59fps over-punishes by ~2x. Severity verdict flips to PORT CADENCE DEFECT (fix = 28Hz ring sampling; default promotion = owners' decision). Balance verdict unchanged: balanced-valve seek-4 cooks at BOTH cadences (authentic, manual-warned).
- Field "3–4s to governor" ≠ spawn preheat [T2]: field-shaped run (autofire at scenery from frame 0, no pre-settle) still takes 15.9s to degT from cold; GenD/loop-5 preheat is real but slow (GenD 666 by 29s). The 3–4s feel is a WARM-START baseline (T≥800 → <3s) plus the deep 59fps governor making the derate obvious.
- Heat-MFD valve slider IN SYNC [T2 pixel-verified]: the vertNormalSlider renders
coolantFlowScale@0x15C — the SAME cell the flow model consumes (single-cell design, no
split possible); measured bar fractions 0.20/0.51/0.90/0.04 for receipt shares
0.167/0.5/0.909/0.0 (perfectly linear;
vlv_*.png). Detent 50 (≈90% up) and detent 0 (bottom) are clearly distinguishable; the residual legibility trap is CLOSED-vs-STARVED (share 0 vs 0.018–0.1 ≈ 1–2px apart) and the silent 4th-press wrap — "maxed and couldn't shed" field reports match the run-C wrap signature, not a display desync.