BT410 5.3.123: the coolant shares itself -- BalanceCoolant lands on the heatable chain, and the valve system finds its true home

The mech's BalanceCoolant handler (0x16, @0049f728) and the share
renormalizer (@0049f788) land on a newly populated heatableSubsystems
chain (the binary ctor's GUID sweep, part_012.c:15774): every heatable
carries the coolant-allocation trio (priority +0x1d0 born 1, share
+0x15c = priority/total, balance alarm +0x1dc blipping 2/1-then-0 as the
gauge flash -- ctor evidence @004ae568).  The ctor tail and Reset both
renormalize, exactly as the binary's four call sites do.  The reservoir
draw (@004aefa4) weights each heatable's feed by the share -- that
consumer wires in with the heat-economy pass.

Soak: 20 heatables renormalize to 0.05 at ctor AND on a BT_PRESS_BALANCE
press through the normal dispatch, crash-sig 0 (pod_render_bal.conf).

Identified for 5.3.124: the binary's MoveValve is @004ae464 on the
HEATABLE base (name table @0x50e534), novice-locked via mapper+0x274
(FUN_004ac9c8 -- our NoviceLockout gate was right), stepping the SAME
+0x1d0 ladder then calling the mech redistribute: our Condenser-scoped
MoveValve/RecomputeValves/valveState triple is this mechanism writ small
and gets unified onto the new cells.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-06 21:46:28 -05:00
co-authored by Claude Fable 5
parent 33e065edc4
commit 0b7c64202a
6 changed files with 274 additions and 20 deletions
@@ -0,0 +1,85 @@
# pod_render_norio.conf -- pod_render_rec with the RIO serial port OFF.
#
# The emulator log shows a steady stream of serial1 RX OVERRUN errors on
# the RIO pipe, and controls post their events at HighEventPriority
# UNCONDITIONALLY (CONTROLS.HPP:250) -- so a chattering RIO port would
# flood a priority the background pump always serves first, starving the
# priority-0 renderer events the load gate waits on. This conf tests that
# by removing the port entirely. Everything else is identical to the rec
# conf, so a launch here and a hang there isolates the RIO.
#
[sdl]
output=opengl
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
priority=higher,higher
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
# emulation-thread stalls (RIO retry recovery) don't audibly chop
rate=44100
blocksize=1024
prebuffer=60
[serial]
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
# VWE fork namedpipe backend (com0com/realport retired -- COM1/COM2 gone):
# DOSBox = pipe client (retry), vRIO/vPLASMA apps = servers; an unconnected
# pipe behaves as an unplugged cable so the mission still runs. serialnamedpipe.h
serial1=disabled
serial2=namedpipe pipe:vplasma
# live UNBUFFERED game output: DOS char devices are not buffered, so
# redirecting stdout to COM3 lands every line immediately. A normal
# '> file' redirect stays 0 bytes until the process exits, which hides
# all progress on a run that does NOT crash.
serial3=file file:C:\VWE\TeslaRel410\emulator\render-bridge\podlog.txt
[autoexec]
mount c "C:\VWE\TeslaRel410\ALPHA_1"
c:
cd \REL410\BT
set VIDEOFORMAT=svga
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
rem driver relies on; skipping it left the cards uninitialized (silent).
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
set BLASTER=A220 I5 D1 H5 P330 T6
c:\sb16\diagnose /s
set BLASTER=A240 I7 D3 H6 P300 T6
c:\sb16\diagnose /s
set BLASTER=A220 I5 D1 H5 P330 T6
set TEMP=c:\
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
rem attached; stdout redirected so mission-load progress survives kills.
set BT_JOINTS=1
set L4VIEWEXT=1
set BT_STACK_LOG=1
set BT_MECH_LOG=1
set BT_PRESS_BALANCE=1
set BT_GAUGE_LOG=1
set BT_FORCE_THROTTLE=0
set BT_FORCE_TURN=0
set HEAPSIZE=15000000
set L4GAUGE=640x480x16
call setenv.bat r s n p
32rtm.exe -x
BTL4REC.EXE -egg testarn.egg > COM3
echo GAME-RC=%errorlevel% >> RC.TXT
32rtm.exe -u
echo ALPHA1-RUN-DONE
pause
+16
View File
@@ -101,6 +101,14 @@ HeatableSubsystem::HeatableSubsystem(
{
Check(owner);
Check_Pointer(subsystem_resource);
//
// The balance alarm arms once at birth (binary ctor @004ae568:
// FUN_0041b9ec(+0x1dc, 3)); the priority/share rest state rides
// ResetToInitialState below.
//
balanceAlarm.Initialize(3);
ResetToInitialState();
Check_Fpu();
}
@@ -123,6 +131,14 @@ void
Check(this);
currentTemperature = 300.0f;
heatLoad = 0.0f;
//
// Binary rest state (@004ae568 ctor / the @004ae4d8-family reset):
// priority 1 (the equal split), share 0 until the owning mech's
// redistribute computes the first real fractions.
//
coolantPriority = 1;
coolantShare = 0.0f;
}
//
+21
View File
@@ -122,6 +122,27 @@
CurrentTemperatureOf()
{ Check(this); return currentTemperature; }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The coolant-allocation trio (binary heatable +0x1d0/+0x15c/+0x1dc,
// ctor @004ae568). PUBLIC: the owning Mech's redistribute walks the
// heatable chain and writes all three (the duckCommand precedent).
//
// coolantPriority the draw-weight rung, born 1; the per-subsystem
// priority button (@004ae464, id TBD) steps it 0 -> 1 -> 5 ->
// 50 -> 0, and the mech's BalanceCoolant (0x16) resets every
// heatable to 1 (the equal split).
// coolantShare priority / total, written ONLY by the mech's
// RedistributeCoolantShares (@0049f788); the reservoir draw
// (@004aefa4) weights each heatable's coolant feed by it.
// balanceAlarm 3 levels (binary FUN_0041b9ec(+0x1dc, 3)); the
// redistribute blips it -- 2 = share shrank or held, 1 = grew,
// then back to 0 -- the cockpit gauge flash.
//
public:
int coolantPriority;
Scalar coolantShare;
AlarmIndicator balanceAlarm;
protected:
Scalar currentTemperature;
Scalar heatLoad;
+126
View File
@@ -90,6 +90,7 @@ const Receiver::HandlerEntry
MESSAGE_ENTRY(Mech, RealMaxSpeed),
MESSAGE_ENTRY(Mech, SetBurningState),
MESSAGE_ENTRY(Mech, ClearBurningState),
MESSAGE_ENTRY(Mech, BalanceCoolant),
MESSAGE_ENTRY(Mech, DuckRequest)
};
@@ -551,6 +552,31 @@ Mech::Mech(
}
}
//
//-----------------------------------------------------------------------
// THE HEATABLE CHAIN (binary ctor sweep, part_012.c:15774: every roster
// subsystem derived from HeatableSubsystem -- GUID 0x50e4fc -- joins the
// mech+0x7cc chain), then the first coolant shares (the binary calls the
// redistribute at the very ctor end, after the "Mechs" group add; the
// ordering between is inert). The weapon (+0x7bc) and Myomers (+0x7ac)
// sweeps land with their consumers.
//-----------------------------------------------------------------------
//
{
for (int hs = 2; hs < subsystemCount; ++hs)
{
if (
subsystemArray[hs] != NULL &&
subsystemArray[hs]->IsDerivedFrom(
HeatableSubsystem::ClassDerivations)
)
{
heatableSubsystems.Add(subsystemArray[hs]);
}
}
}
RedistributeCoolantShares();
//
//-----------------------------------------------------------------------
// Source the authentic per-mech locomotion params from the GameModel
@@ -925,6 +951,87 @@ void
AlwaysExecute();
}
//
//#############################################################################
// BalanceCoolantMessageHandler -- binary @0049f728 (authentic name from the
// handler table). A positive press resets every heatable's coolant
// priority to 1 -- the equal split -- and renormalizes the shares.
//#############################################################################
//
void
Mech::BalanceCoolantMessageHandler(Receiver::Message *message)
{
Check(this);
Check_Pointer(message);
if (((int *)(message + 1))[0] > 0)
{
ChainIteratorOf<Subsystem*> mark_walk(heatableSubsystems);
HeatableSubsystem *heatable;
while ((heatable = (HeatableSubsystem *)mark_walk.ReadAndNext()) != NULL)
{
heatable->coolantPriority = 1;
}
RedistributeCoolantShares();
}
}
//
//#############################################################################
// RedistributeCoolantShares -- binary @0049f788: total the priorities, give
// each heatable share = priority / total (0.0 when the total is empty --
// the data word @0049f850), blip its balance alarm with the direction
// (2 = shrank or held, 1 = grew, then back to 0), store the share. The
// reservoir draw weights each heatable's coolant feed by the share.
//#############################################################################
//
void
Mech::RedistributeCoolantShares()
{
Check(this);
int total = 0;
{
ChainIteratorOf<Subsystem*> sum_walk(heatableSubsystems);
HeatableSubsystem *heatable;
while ((heatable = (HeatableSubsystem *)sum_walk.ReadAndNext()) != NULL)
{
total += heatable->coolantPriority;
}
}
int share_count = 0;
Scalar first_share = 0.0f;
ChainIteratorOf<Subsystem*> share_walk(heatableSubsystems);
HeatableSubsystem *heatable;
while ((heatable = (HeatableSubsystem *)share_walk.ReadAndNext()) != NULL)
{
Scalar share = 0.0f;
if (total > 0)
{
share = (Scalar)heatable->coolantPriority / (Scalar)total;
}
heatable->balanceAlarm.SetLevel(
(share <= heatable->coolantShare) ? 2 : 1);
heatable->coolantShare = share;
heatable->balanceAlarm.SetLevel(0);
if (share_count == 0)
{
first_share = share;
}
++share_count;
}
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[coolant] shares renormalized: " << share_count
<< " heatables, total priority " << total
<< ", first share " << first_share << endl << flush;
}
}
//
//#############################################################################
// The collision-volume swap (binaries @004ac04c / @004ac064): write the
@@ -1371,6 +1478,12 @@ void
}
}
//
// The binary Reset (@0049fb74 tail) renormalizes the coolant shares
// after the roster's per-subsystem resets restored every priority.
//
RedistributeCoolantShares();
SetPreRunFlag();
if (getenv("BT_MECH_LOG"))
@@ -2169,6 +2282,19 @@ void
}
}
//
// BT_PRESS_BALANCE: one mech-level BalanceCoolant press (0x16)
// through the normal dispatch -- the equal-split button.
//
if (getenv("BT_PRESS_BALANCE"))
{
ReceiverDataMessageOf<int> balance_press(
Mech::BalanceCoolantMessageID,
sizeof(ReceiverDataMessageOf<int>),
1
);
Dispatch(&balance_press);
}
//
// BT_PRESS_GEN=1..4: SelectGenerator<N> at the first Emitter
// (the generator panel re-tap); BT_PRESS_SEEK: ToggleSeekVoltage
// at the first Emitter (the seek dial).
+12
View File
@@ -980,6 +980,18 @@
SetBurningStateMessageHandler(Receiver::Message *message);
void
ClearBurningStateMessageHandler(Receiver::Message *message);
void
BalanceCoolantMessageHandler(Receiver::Message *message);
//
// The coolant-share renormalizer (binary @0049f788): share =
// priority / total across the heatable chain, blipping each
// heatable's balance alarm with the change direction. Callers in
// the binary: the 0x16 handler, the ctor tail, Reset, and the
// per-subsystem priority stepper (@004ae464, not yet landed).
//
void
RedistributeCoolantShares();
void
SetStandingCollisionVolume(); // binary @004ac04c... see .CPP
void
+14 -20
View File
@@ -299,26 +299,20 @@ simulationFlags bit 1 == DontExecuteFlag (SIMULATE.HPP) -- the binary's
Still to land from the roster:
* **BalanceCoolant @0049f728** (decoded): if payload > 0, mark item+0x1d0=1
across the mech+0x7cc roster, then the redistribute pass @0049f788: total
the marks, per item fraction = mark/total (0.0 @0049f850 when empty),
blip the item's alarm at +0x1dc (level 2 if fraction <= stored else 1,
then 0 -- the gauge flash), store fraction at item+0x15c. UNBLOCKED (same sitting): the ctor's chain sweeps
(part_012.c:15760..) pin the triple by derivation GUID, names resolved
by static-layout adjacency to each TU's string pool: **+0x7ac =
MYOMERS** (GUID 0x51155c, string @0x51166d -- so the mobility scale IS
max leg-muscle effectiveness, item+0x31c), **+0x7bc = MECHWEAPON
roster** (0x511830; the ctor ORs weapon+0x334 mappable-button masks
into mech+0x410 as it fills it), **+0x7cc = HEATABLE SUBSYSTEMS**
(0x50e4fc == our heatableSubsystems chain; 0x50e604 = the HeatSink-side
derivation whose members get a post-ctor vcall+0x38 -- the conduction
link resolve). BalanceCoolant therefore marks EVERY heatable (+0x1d0),
and the stored fraction +0x15c is the item's coolant ALLOCATION SHARE:
the reservoir draw @004aefa4 weights each item's draw by it
(rate = -(reservoir+0x224) x share x dt, clamped, with mass/capacity
bookkeeping at item+0x12c/+0x128). Land as: three HeatableSubsystem
members (balanceMark/coolantShare/balanceAlarm, +0x1d0/+0x15c/+0x1dc)
+ the handler on heatableSubsystems.
* **BalanceCoolant @0049f728 -- LANDED 5.3.123** (equal-split press +
RedistributeCoolantShares @0049f788 + the heatable ctor sweep that
populates the heatableSubsystems chain + ctor/Reset redistribute calls;
soak: 20 heatables, first share 0.05, ctor + BT_PRESS_BALANCE both
renormalize, crash-sig 0). UNIFICATION OWED (5.3.124): the binary's
MoveValve handler is @004ae464 registered on the HEATABLE base (the
0x50e534 name table in the heatable statics region), novice-locked via
mapper+0x274 == 0 (FUN_004ac9c8 -- our NoviceLockout is correct),
stepping heatable+0x1d0 through 0 -> 1 -> 5 -> 50 -> 0 then calling the
MECH redistribute -- i.e. our Condenser::MoveValve / RecomputeValves /
valveState triple is the SAME mechanism scoped to condensers only;
retire it onto coolantPriority/RedistributeCoolantShares and re-home
the handler on HeatableSubsystem (ValveSetting attribute retargets to
the share/priority cells).
* **EjectPilot @0049f854** (head read): gated on payload > 0 AND
**mech+0x414** -- THE EFFECTIVENESS-CENSUS CONSUMER FOUND: you can only
eject a CRIPPLED mech (@0049fa1c's weapons/coolant/generator/limp