Files
TeslaRel410/restoration/source410/BT/HEAT.NOTES.md
T
CydandClaude Fable 5 c01e57ab22 BT410 Phase 5.3.13: cockpit-button message layer -- valves, cooling, flush LIVE
The subsystem-family cockpit buttons now work through the authentic Receiver
dispatch -> per-class handler-table path. The id space decodes cleanly:
Receiver::NextMessageID == 3, so ToggleCooling = 3 on the HeatSink chain and
the per-class id 4 is MoveValve on a Condenser / InjectCoolant on the
Reservoir -- same number, different class, the binary's per-receiver-class
convention.

- HeatSink::ToggleCoolingMessageHandler (@004ad6f8, id 3): novice-locked,
  press-only; toggles coolantAvailable + coolantFlowScale together.
- Condenser::MoveValveMessageHandler (@4ae464, id 4): novice-locked; cycles
  the valve 1->5->50->0->1 and calls Condenser::RecomputeValves (@0049f788):
  every condenser's coolantFlowScale = valve / sum-of-valves. The ctor now
  streams the AUTHENTIC flowScale=0; the Mech ctor seeds equal shares once at
  spawn -- the flowScale=1 interim is retired.
- Reservoir::InjectCoolantMessageHandler (@4aee70, id 4): novice-locked;
  press arms the flush when the tank holds charge, release drops it.
- MechSubsystem::NoviceLockout() (@4ac9c8): owner -> playerLink -> experience
  == novice; unlinked mechs read unlocked.
- DEV harness BT_PRESS_VALVE / BT_PRESS_FLUSH: dispatch the REAL messages ~6s
  into the mission from Mech::Simulate.

VERIFIED: spawn shares 6 x 0.166667; one MoveValve press -> Condenser1
valve=5 flow=0.5, others 0.1 (valve/sum exact); flush arms via the real
button message; NOVICE locks both presses (valve lines stay spawn-only, zero
flush). Zero Fail throughout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 08:43:01 -05:00

203 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HEAT.CPP / POWERSUB.CPP — the power/heat wave (Phase 5.3.10, 2026-07-21)
The mech's thermal + electrical economy is LIVE: weapons dump firing heat into
their own sinks, the sinks conduct through the Condenser bank into the central
HeatSink, temperatures drive the degradation/failure alarms, and every powered
subsystem tracks its generator through the electrical state machine.
## The heat topology (wire-verified on TEST.EGG)
`linkedSinkIndex` (the previously-missing HeatSink resource int — see below)
wires the authored conduction graph at construction:
weapons / equipment -> Condenser1..6 (slots 4-9) -> central HeatSink (slot 2)
GeneratorA..D (10-13) -> Condensers
Reservoir (3) -> central HeatSink
Authored values match the BT411 calibration audit EXACTLY: central bank mass
1.39e6, PPC's own sink 174000, thresholds 77 start / 1000 degradation / 2000
failure, PPC conductance 86800. (The central sink's own `linkedSinkIdx=5` is a
FORWARD reference — Condenser2 isn't constructed yet at slot 2's ctor — so its
drain is unwired; a post-walk fixup is a small future item. Everything drains
INTO it fine.)
## Reconstructed
- **HeatSink::HeatSinkSimulation** (@004ad924): absorb pendingHeat → T =
E/mass → UpdateHeatLoad (15-sample filter) → ConductHeat → alarm thresholds.
Installed by the HeatSink ctor (derived classes override and chain it).
- **ConductHeat / ComputeHeatFlow** (@004ad8ac/@004ad9ec): the two-body
equilibrium relaxation — `(T·massScale (E_other+pending+E_own)/denom)·tau·
(1exp(dt·conductance·(coolant/capacity)·flowScale/denom))`. Verified:
flow == 0 exactly at uniform temperature; a fired PPC bleeds ~170K units/step
into Condenser4.
- **PoweredSubsystem ctor + PoweredSubsystemSimulation** (@004b0f74/@004b0bd0):
resolves `voltageSourceIndex` from the roster (GeneratorA-D — wire-verified),
attaches the tap (`Generator::TapVoltageSource`, 1 when full), and runs the
electrical FSM (Starting=0 / NoVoltage=1 / Shorted=2 / GeneratorOff=3 /
Ready=4) watching the generator state. Deferred: the AutoConnect
replacement-generator hunt (needs the status flags / damage wave).
- **Generator::GeneratorSimulation** (PARTIAL): heat step + start/short-recovery
timers; the load model (voltage sag, I²R self-heat feeding the charge
integration) joins the electrical-charge wave (TrackSeekVoltage).
- **Sensor::SensorSimulation** upgraded to the authentic gating (@004b1c4c):
power step first, radar = 1 damage, electrical-Ready gate (badVoltage),
heat-state switch (Degradation ×0.5, Failure → 0). Verified: voltState=4,
radar 100% on the healthy mech.
- Weapons: Emitter/Projectile sims run the power step at their head; the
Loading recharge is gated on electrical Ready (authentic @4bbdf5); FireWeapon
dumps the firing heat.
## Heat units (1e7-native) — TWO authoring conventions
The heat chain is 1e7-unit-native (BT411 audit). The stream stores
`heatCostToFire` in TWO conventions:
- **Energy weapons: small units** (PPC = 11) — the ×1e7 comes from the energy
closed form ((1dF)·E ≈ heatCost·1e7 at full charge). Our partial multiplies
by 1e7 pending the charge model.
- **Ballistics: native units** (SRM6 = 5.06e7) — dumped RAW. (Double-scaling
this to 5e14 was the runaway-temperature bug during bring-up.)
Both land ~+640K on the weapon's own sink — the consistent design magnitude.
## The resource-alignment resolution (wire-verified, closes the +3 mystery)
The raw-stream dumps pinned the FULL ancestry alignment:
- `HeatSink__SubsystemResource.linkedSinkIndex` — THE missing int that shifted
every descendant block +1 (PoweredSubsystem's voltageSourceIndex previously
read the linked-sink index — hence "power sources" appearing to be
Condensers).
- The MechWeapon pip tail is `pipPosition(int) + pipColor(3 floats) +
pipExtendedRange(int)` (+2) — matches BT411's verified overlay exactly.
- +1 +2 = the +3 the interim ProjectileWeapon pad compensated; the pad is gone.
- True bhk1 reads: PPC recharge **5.0 s** (not the misaligned 1.0), discharge
0.99 s, range 900, damage 12, heatCost 11, pipColor (0,0,1); SRM6 recharge
5 s, range 800, salvo damage 35, missileCount 6.
## VERIFIED (BT_FORCE_FIRE + BT_POWER_LOG)
PPC: 77° → fires → ~709° → relaxes to 441° across its 5 s reload → sustained
fire climbs the residual 441 → 674 → 838 → 960 — brushing the 1000° degradation
threshold: the authentic fire-discipline game. SRM salvos spike +641K and
cross degradation after three. Condensers absorb and pass to the central
bank. Neutral run: sensor Ready/100%, mech holds, zero Fail.
## Heat consequences (Phase 5.3.11, 2026-07-22)
The weapon-side consequences are wired and VERIFIED under sustained fire:
- **Ballistic gate 1** (@4bbd36): weapon destroyed-state or its own sink at
FailureHeat pins full recoil + the unavailable alarm (the NoAmmo roach-motel
re-asserts). (Mech-disabled half joins the damage wave.)
- **CheckForJam** (@4bbfcc): `p = 0.41·T/failT` clamped [minJamChance, 1.0],
rolled per granted shot against the MUNGA uniform `Random`. Interim gate:
heat-degraded only (stands in for the deferred `LiveFireEnabled` novice
switch AND the trivially-true `heatLoad>0` early-out — the exact spurious-
cold-jam trap the BT411 port documented). Verified: SRM6s jam at
T≈1200-1320 (24%+ rolls past the 1000° threshold), stay jammed.
- **Emitter hard gate** (@4baab9): FailureHeat drops the beam state + charge
each frame; SELF-RECOVERING — conduction cools the sink below 2000° and the
weapon resumes from Loading. Verified: the PPC settles into an emergent
thermal duty cycle (fire at ~1930° → spike 2562° → shutdown → cool → refire);
the lasers oscillate around ~2200°.
## The experience gates + the coolant wave (Phase 5.3.12, 2026-07-22)
**The player-experience gates are LIVE** (the workflow dossier recovered every
body from the BT411 RE):
- `HeatSink::HeatModelActive()` (binary FUN_004ad7d4): owner mech →
`Entity::GetPlayerLink()` → `BTPlayer::heatModelOn` — ON only for veteran /
expert. Gates BOTH phases of HeatSinkSimulation (absorb/conduct AND
UpdateCoolant) and every weapon fire-heat dump. Standard mode has NO heat
consequences — authentic. NULL player link reads ON (dev-permissive).
- `ProjectileWeapon::LiveFireEnabled()`: playerLink → `simLive` — 0 only for
novice, whose ballistics never jam. `CheckForJam` is now the AUTHENTIC form:
LiveFireEnabled early-out + the `heatLoad <= 0` early-out (with the model off
heatLoad stays 0, so standard mode never jams either) + the minJamChance
floor (a veteran+ launcher carries its authored ~5% cold jam chance — the
interim heat-degraded gate is retired).
- BTPlayer's flag block renamed to its TRUE semantics (simLive @0x25c,
heatModelOn @0x260, advancedDamageOn/2, levelFlag26c/270, experienceLevel) —
the ctor rows were already binary-accurate; only the names/types were the
old "display toggles" misread.
**THE LOAD-BEARING FIX found on the way: `Mech` ctor `SetValidFlag()`.**
Every 1995 entity ctor tail marks itself valid; ours didn't — and
`Entity::Dispatch` routes messages to an INVALID entity into the deferred
event queue, so the PlayerLink bind (and every directly-dispatched mech
message) silently never landed. The experience gates read a NULL player
forever (permissive ON) until this one authentic line was restored.
**The coolant system** (all bodies authentic from the dossier, constants
byte-verified: HeatLoadScale 0.002 → heatLoad now lives in the [0,1] band,
HeatEqualizeEpsilon 1e-4, draw floor 0.0025 / ON 0.003, draw gate 1e-4):
- `UpdateCoolant` (@004adbf8): coolantDraw = own structural damage × heatLoad
(an UNDAMAGED mech leaks nothing — the bars stay full), drain, top-up via
the virtual `DrawCoolant`, and the active-flag hysteresis.
- `BalanceCoolant` (@004ada94): full clamp chain, called from ConductHeat.
- `DrawCoolant` base = 0; **Reservoir overrides it as THE SOURCE** (@4af3b0).
- **Reservoir reconstructed** (@4af408): capacity overlays thermalCapacity
(streamed 20), squirtMass 0.5, `CoolantSimulation` + `InjectCoolant`
(@4aefa4, the full flush distribution: condensers → weapons → heatables →
master with intentional duplicate weighting, squirts only leave the tank,
the chill only cools). `BT_FORCE_FLUSH=1` dev hook (the cockpit
InjectCoolant button joins the message wave). Deferred: the
AggregateHeatSink master-bank attach + the 0.05×count capacity rescale.
- **Condenser refrigeration** (@4ae4d8): valveState inits 1, massScale =
(1 own damage) × refrigerationFactor (streamed 3.0) clamped ≥ 1 each
frame — the condenser pumps heat as if 3× hotter, chilling itself below
ambient; that chill is what cools the weapons. INTERIM: coolantFlowScale
stays 1.0 (authentic streams 0 + the MoveValve/BTRecomputeCondenserValves
share assignment — the cockpit-button wave; zeroing it now would strand
condenser heat).
**VERIFIED headlessly, both directions of the experience lever** (TESTNOV.EGG
= TEST.EGG with `experience=novice`):
- NOVICE: 212 fire cycles ALL at T=77, zero jams, zero shutdowns, coolant
static — the heat model authentically absent.
- EXPERT: playerLink resolves, the full economy runs (PPC duty cycle now
sawing ~650-1200° — the refrigeration pump drains harder than before — 119
shutdowns + 2 authentic jams under forced sustained fire). Zero Fail in
both.
## Still deferred
The electrical charge model (TrackSeekVoltage / voltage sag / I²R), the
mech-disabled halves of the weapon gates (damage wave), the AggregateHeatSink
family (the central bank's count/radiator loop + reservoir attach + capacity
rescale + its forward-linked drain), the MoveValve / ToggleCooling /
InjectCoolant cockpit-button message handlers + BTRecomputeCondenserValves,
Myomers movement heat, jam recovery via ResetToInitialState (mission reset).
## The cockpit-button message layer (Phase 5.3.13, 2026-07-24)
The subsystem-family button messages are LIVE through the authentic Receiver
dispatch -> per-class handler-table path (the id space decodes cleanly:
Receiver::NextMessageID == 3, so ToggleCooling = 3 on the HeatSink chain and
the per-class id 4 is MoveValve on a Condenser / InjectCoolant on the
Reservoir -- the same number, different class, exactly the binary's
per-receiver-class convention):
- **HeatSink::ToggleCoolingMessageHandler** (@004ad6f8, id 3): novice-locked,
press-only; toggles coolantAvailable + coolantFlowScale together (OFF
strands this sink's heat until switched back).
- **Condenser::MoveValveMessageHandler** (@4ae464, id 4): novice-locked,
press-only; cycles the valve 1 -> 5 -> 50 -> 0 -> 1 and calls
**Condenser::RecomputeValves** (@0049f788): every condenser's
coolantFlowScale = valve / sum-of-valves. The Condenser ctor now streams
the authentic flowScale = 0; the Mech ctor seeds the shares once at spawn
(all valves 1 -> equal shares) -- retiring the flowScale=1 interim. (The
condenser-alarm change pulse joins the gauge wave.)
- **Reservoir::InjectCoolantMessageHandler** (@4aee70, id 4): novice-locked;
press raises the inject alarm when the tank holds charge (flush-cloud psfx
deferred), release drops it.
- **MechSubsystem::NoviceLockout()** (@4ac9c8): owner -> playerLink ->
experience == novice; unlinked mechs read unlocked.
- DEV harness: BT_PRESS_VALVE / BT_PRESS_FLUSH dispatch the REAL messages
~6 s in (Mech::Simulate).
VERIFIED: spawn shares 6 x 0.166667; one MoveValve press -> Condenser1
valve=5 flow=0.5, the others 0.1 (valve/sum exactly); the flush arms via the
real button message; NOVICE: both presses swallowed by the lockout (valve
lines stay spawn-only, zero flush). Zero Fail throughout.