Playtest night 5: "heat damage @ launch is lit up like a Christmas tree",
confirmed by two players at ANY spawn -- so not the respawn-reset bug it was
first filed as. Two stacked causes, both fixed here.
1. THE RATIO WAS NEVER RECONSTRUCTED. HeatConnection::Transfer @004c3720 sets
the colour index a ColorMapper pushes into the palette slot. The port wrote
*currentColorIndex = HeatRound(heat->currentTemperature);
straight into an index whose range is 0..99. A stone-cold mech sits at ~77
and the generators idle near 260, so every one of the 24 cmHeat mappers in
GAUGE/L4GAUGE.CFG (GeneratorA-D, Condenser1-6, HUD, Avionics, Gyroscope,
Torso, GAUSS, the lasers, SRM6, Myomers) saturated at the hot end from the
first frame and stayed there.
The real computation was INVISIBLE in the decomp. Ghidra renders the tail as
`uVar1 = FUN_004dcd94();` -- an arg-less __ftol, exactly the carve artifact
reconstruction-gotchas §19 documents: the x87 expression that left the value
in ST0 is dropped from the export. The previous author reconstructed the
only thing visible and flagged the scaling as unreconciled in a comment.
Raw disasm @0x4c379a-0x4c37c1 recovers it:
fld [num] ; fdiv [den] ; ratio
fcomp 1.0f ; jbe -> ratio=1 ; clamp
fmul 99.0f ; call __ftol ; -> 0..99
i.e. "how close to failure am I", not a raw temperature. At spawn that is
77/2000 -> 4, and the schematic reads cold.
The two operands come from one of two branches, chosen by a class flag at
+0x14 that the port did not model at all (ctor disasm @0x4c3682-0x4c36c2):
HeatableSubsystem (0x50e3ec) -> own temp@0x114 / own failureTemperature@0x11C
HeatWatcher (0x50e604) -> WATCHED subsystem's temp@0x114
/ the WATCHER's failureTemperature@0x124
neither -> source NULLed, Transfer writes 100
Note the watcher asymmetry: temperature from the watched subsystem, reference
from the watcher itself. Bridged as BTHeatWatcherSample so the gauge TU need
not include the heat family's headers.
2. THE WATCHER BRANCH COULD NEVER BE SELECTED. With (1) fixed, HUD, Gyroscope
and Torso still pinned at 99 reading temp=1.6369e-35 failAt=0 -- uninitialised
bytes. HeatWatcher's C++ base had been re-based to MechSubsystem, but its
DERIVATION chain still said HeatableSubsystem, so every watcher answered "yes,
I am heat-bearing", took branch one, and read its own watchedLink@0x114 as a
temperature. This is the last surviving instance of the #47 bug -- in the
file that fix's own comment cites as already correct. The two families are
disjoint in the binary and the branch test depends on it.
VERIFIED LIVE (solo, F6 to the Heat schematic, BT_HEATGAUGE_LOG=1): mode mask
reached 0x510421 (ModeSecondaryHeat) and the gauge tracks per subsystem rather
than saturating -- GeneratorA 259.5->13, Condenser3 202.0->10, SRM6 182.8->9,
lasers 90-97->4-5, and AmmoBinSRM6_1 (a watcher) resolves its link and reads
182.7->9 through the branch that previously read 1.6369e-35. Nothing pinned at
99. Operator confirms the panel is blue, not red.
BT_HEATGAUGE_LOG kept as a permanent env-gated diagnostic: bind-time
classification plus per-sample temp/failAt/ratio/index. A fresh spawn reading
99 is this regression returning.
Not covered: ColorMapperCritical (cmCrit / ModeSecondaryCritical) already
computes a proper damageLevel*100 ratio and was read, not measured -- if the
Critical page specifically still misbehaves that is a separate lead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>