BT410 Phase 5.3.11: weapon heat consequences -- jams + thermal shutdowns LIVE

The heat economy now bites back, verified under sustained fire:

- Ballistic gate 1 (@4bbd36): destroyed-state or own-sink FailureHeat pins
  full recoil + the unavailable alarm (the NoAmmo roach-motel re-asserts).
- CheckForJam (@4bbfcc): p = 0.41*T/failT clamped [minJamChance, 1.0], rolled
  per granted shot against the MUNGA uniform Random. Interim heat-degraded
  gate stands in for the deferred LiveFireEnabled novice switch (the exact
  spurious-cold-jam trap the BT411 port documented). VERIFIED: SRM6s jam at
  T~1200-1320 after riding past the authored 1000-degree threshold, and stay
  jammed (mission-reset recovery only) -- authentic.
- Emitter hard gate (@4baab9): FailureHeat drops the beam state + charge each
  frame; SELF-RECOVERING once conduction cools the sink below failure.
  VERIFIED: the PPC settles into an emergent thermal duty cycle -- fire at
  ~1930K, spike to 2562K, shutdown, cool, refire -- firing exactly as fast as
  its sink sheds heat; the lasers oscillate around ~2200K.

Zero Fail. Deferred: LiveFireEnabled/HeatModelOff experience gates,
mech-disabled gate halves, coolant depletion, jam recovery via mission reset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-22 21:20:37 -05:00
co-authored by Claude Fable 5
parent 4391ddd322
commit b1cbbe1c9b
4 changed files with 124 additions and 4 deletions
+23
View File
@@ -190,6 +190,29 @@ void
// //
PoweredSubsystem::PoweredSubsystemSimulation(time_slice); PoweredSubsystem::PoweredSubsystemSimulation(time_slice);
//
// Hard failure (@4baab9): weapon DESTROYED or its own sink at FailureHeat
// -> drop the beam state and the charge, and hold there. Unlike the
// ballistic roach-motel this recovers by itself: once conduction cools the
// sink below the failure threshold the gate stops firing and the weapon
// resumes from Loading. (The owning-mech-disabled half joins with the
// damage wave.)
//
if (GetSimulationState() == 1 || GetHeatState() == FailureHeat)
{
if (getenv("BT_MECH_LOG") && GetWeaponState() != LoadingState)
{
DEBUG_STREAM << "[fire] '" << GetName()
<< "' THERMAL SHUTDOWN (T=" << CurrentTemperatureOf()
<< ")" << endl << flush;
}
ResetFiringState();
chargeLevel = 0.0f;
ComputeOutputVoltage();
Check_Fpu();
return;
}
{ {
static int forceFire = -1; static int forceFire = -1;
if (forceFire < 0) if (forceFire < 0)
+25 -4
View File
@@ -81,9 +81,30 @@ threshold: the authentic fire-discipline game. SRM salvos spike +641K and
cross degradation after three. Condensers absorb and pass to the central cross degradation after three. Condensers absorb and pass to the central
bank. Neutral run: sensor Ready/100%, mech holds, zero Fail. 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°.
## Still deferred ## Still deferred
UpdateCoolant / BalanceCoolant (coolant depletion + venting), the HeatModelOff UpdateCoolant / BalanceCoolant (coolant depletion + venting), the
experience gate (novice mode), the electrical charge model (TrackSeekVoltage / HeatModelOff / LiveFireEnabled experience gates (novice mode), the electrical
voltage sag / I²R), gate 1 + the heat-scaled jam roll in the weapon FSMs, the charge model (TrackSeekVoltage / voltage sag / I²R), the mech-disabled halves
central sink's forward-linked drain, Condenser MoveValve handling. of the weapon gates (damage wave), the central sink's forward-linked drain,
Condenser MoveValve handling, jam recovery via ResetToInitialState (mission
reset path).
+74
View File
@@ -23,6 +23,10 @@
# include <ammobin.hpp> # include <ammobin.hpp>
#endif #endif
#if !defined(RANDOM_HPP)
# include <random.hpp>
#endif
Derivation Derivation
ProjectileWeapon::ClassDerivations( ProjectileWeapon::ClassDerivations(
MechWeapon::ClassDerivations, MechWeapon::ClassDerivations,
@@ -159,6 +163,48 @@ void
} }
} }
//
//#############################################################################
// CheckForJam -- the heat-scaled jam roll (binary @4bbfcc):
// p = 0.41 * currentTemperature / failureTemperature,
// clamped to [minJamChance, 1.0], rolled against a uniform random.
// A jammed launcher clears only on ResetToInitialState (mission reset).
//
// TWO interim gates stand in for deferred systems (the BT411 port hit exactly
// this): the authentic leading gate is LiveFireEnabled() -- the player
// experience switch (novice mode = no jams), which needs the player-link
// accessor wave -- and the binary's own `heatLoad <= 0` early-out is trivially
// true once the heat model runs. Until LiveFireEnabled lands, gate the roll
// on the sink actually being heat-degraded, so cold weapons fire reliably and
// jams appear exactly when the heat economy says the weapon is cooking.
//#############################################################################
//
Logical
ProjectileWeapon::CheckForJam()
{
Check(this);
if (GetHeatState() < DegradationHeat) // interim LiveFireEnabled stand-in
{
return False;
}
Scalar p = (0.41f * CurrentTemperatureOf()) / failureTemperature;
if (minJamChance <= p)
{
if (p > 1.0f)
{
p = 1.0f;
}
}
else
{
p = minJamChance;
}
return (p > (Scalar)Random) ? True : False;
}
// //
//############################################################################# //#############################################################################
// ProjectileWeaponSimulation The ballistic per-frame fire state machine // ProjectileWeaponSimulation The ballistic per-frame fire state machine
@@ -209,6 +255,24 @@ void
// //
Logical trigger = CheckFireEdge(); Logical trigger = CheckFireEdge();
//
// Gate 1 (@4bbd36): weapon DESTROYED or its own sink at FailureHeat -> pin
// full recoil + the unavailable alarm. No return -- the frame continues
// into the state machine (whose NoAmmo case re-asserts). (The owning-mech-
// disabled half joins with the damage wave.)
//
if (GetSimulationState() == 1 || GetHeatState() == FailureHeat)
{
if (getenv("BT_MECH_LOG") && GetWeaponState() != NoAmmoState)
{
DEBUG_STREAM << "[fire] '" << GetName()
<< "' -> UNAVAILABLE (gate1: heatState=" << GetHeatState()
<< " T=" << CurrentTemperatureOf() << ")" << endl << flush;
}
recoil = rechargeRate;
weaponAlarm.SetLevel(NoAmmoState);
}
// //
// Gate 2: the linked AmmoBin. Empty (or missing) -> pin unavailable EVERY // Gate 2: the linked AmmoBin. Empty (or missing) -> pin unavailable EVERY
// frame while dry. // frame while dry.
@@ -244,6 +308,16 @@ void
{ {
weaponAlarm.SetLevel(NoAmmoState); weaponAlarm.SetLevel(NoAmmoState);
} }
else if (CheckForJam())
{
weaponAlarm.SetLevel(JammedState);
if (getenv("BT_MECH_LOG"))
{
DEBUG_STREAM << "[fire] '" << GetName()
<< "' JAMMED (T=" << CurrentTemperatureOf()
<< ")" << endl << flush;
}
}
else else
{ {
weaponAlarm.SetLevel(LoadingState); weaponAlarm.SetLevel(LoadingState);
+2
View File
@@ -77,6 +77,8 @@
void void
ProjectileWeaponSimulation(Scalar time_slice); ProjectileWeaponSimulation(Scalar time_slice);
Logical
CheckForJam();
public: public:
typedef ProjectileWeapon__SubsystemResource SubsystemResource; typedef ProjectileWeapon__SubsystemResource SubsystemResource;