Heat: THE AUTHENTIC ELECTRICAL MODEL -- weapons recharge from generators (task #10)

Task #10 set out to fix "scrambled linked-sink routing"; a [heat-link] attach
log proved the routing was NEVER scrambled (every subsystem links its authored
condenser exactly; the "pools in Condenser1" read was the diagnostic-sampler
aliasing trap).  The real defect: emitter.cpp's local FUN_00417ab4 stub
returned NULL, so the whole electrical model was inert and the E7 force-charge
recharged every emitter in ONE frame (~0.3s cycle, 1501 fires/90s, ~1.7e9
heat/s -- the "runaway").

Landed authentically [T1: disasm + byte-verified constants]:
- Emitter ctor @004bb120: seekVoltage = authored fraction x generator
  ratedVoltage (10000); EC = energyTotal/(seekV^2 x 0.5); voltageScale@0x310 =
  (RechargeRate / -ln(1 - 1e-4 x seekV)) / EC -- charge reaches seekV[rec] in
  EXACTLY the authored RechargeRate (PPC 5s, ERL 4s, SRM 3s, ERM 2s) cold.
  Owner-flags ctor gate (the usual gotcha; the this-flags read never armed).
- PoweredSubsystem::ChargeTimeScale (@004b0d50, was a =1.0 stub): voltageScale
  x (1 + thermalResistivity x srcTempRise) -- hot generators charge slower.
  ("voltageScale is never read back" was wrong; corrected + swept.)
- TrackSeekVoltage @004ba838: charging I^2R -> the GENERATOR's pendingHeat
  (~3.5e8/full PPC charge) -- generators self-heat, conduct to their authored
  condensers, and throttle further charging.  The feedback economy closes.
- FailureHeat consumers found: this+0x184 == 2 gates BOTH weapon families
  (@004baa88 emitter: reset firing + hold charge 0; @004bbd36 ballistic:
  recoil=rechargeRate + alarm 7).  Emitter::GetFaultState un-stubbed.
- ProjectileWeaponSimulation @004bbd04 opens with call 0x4b0bd0 (disasm) --
  launchers now run the powered/heat step (their firing heat previously
  accumulated in pendingHeat forever).
- heat.cpp: per-instance BT_HEAT_LOG census (the old shared-static 1-Hz
  sampler aliased); [heat-link] attach log; the stolen-else Verify restored.

Verified live (120s max-rate autofire): PPC ~470-500 (was 55,000), bank
plateaus ~600 and sheds to ambient, generators 1100-1400, ERMLaser
self-regulates at the authored 2000 failure threshold (shutdown-cool-resume).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 16:59:29 -05:00
co-authored by Claude Fable 5
parent 4ed2bbc293
commit 4e63a7b6c3
10 changed files with 273 additions and 90 deletions
+28 -10
View File
@@ -38,6 +38,7 @@
//
#include <bt.hpp>
#include <map> // BT_HEAT_LOG census (diag only)
#pragma hdrstop
#if !defined(HEAT_HPP)
@@ -505,6 +506,15 @@ HeatSink::HeatSink(
// @004adda0: "Bad subsystem resource ->heatSink" HEAT.CPP:0x25F
Verify(False, "Bad subsystem resource ->heatSink", __FILE__, 0x25F);
}
if (getenv("BT_HEAT_LOG"))
{
DEBUG_STREAM << "[heat-link] " << GetName()
<< " sinkIdx=" << subsystem_resource->heatSinkIndex
<< " linked=" << (linked ? linked->GetName() : "<NULL/not-built-yet>")
<< " mass=" << subsystem_resource->thermalMass
<< " k=" << subsystem_resource->thermalConductance
<< std::endl;
}
if (
(owner->simulationFlags & SegmentCopyMask) == 0 // param_2+0x28 & 0xc == 0
@@ -602,17 +612,25 @@ void
currentTemperature = heatEnergy / thermalMass;
UpdateHeatLoad();
// BRING-UP verify (1 Hz, viewpoint mech only): show the mech heat climbing as
// weapons fire and relaxing when they stop. pendingHeat just absorbed above.
static Scalar s_heatLog = 0.0f;
s_heatLog += time_slice;
if (s_heatLog >= 1.0f && application != 0
&& (Entity *)owner == application->GetViewpointEntity() && pendingHeat > 0.0f)
// DIAG census (BT_HEAT_LOG, viewpoint mech): PER-INSTANCE 5-s timers --
// the old shared static timer aliased to whichever instance crossed the
// tick (the diagnostic-sampler trap), which manufactured the task-#10
// "heat pools in Condenser1" misread.
if (getenv("BT_HEAT_LOG") && application != 0
&& (Entity *)owner == application->GetViewpointEntity())
{
s_heatLog = 0.0f;
DEBUG_STREAM << "[heat] " << GetName() << " temp=" << currentTemperature
<< " heatEnergy=" << heatEnergy << " absorbed=" << pendingHeat
<< " heatLoad=" << heatLoad << "\n" << std::flush;
static std::map<const void *, Scalar> s_census;
Scalar &acc = s_census[this];
acc += time_slice;
if (acc >= 5.0f)
{
acc = 0.0f;
DEBUG_STREAM << "[heat-t] " << GetName()
<< " T=" << currentTemperature
<< " absorbed=" << pendingHeat
<< " cool=" << coolantLevel << "/" << thermalCapacity
<< " load=" << heatLoad << "\n" << std::flush;
}
}
pendingHeat = 0.0f;