2020-12-22 03:13:43 +00:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
using MessagePack;
|
|
|
|
|
using Newtonsoft.Json;
|
2026-09-14 17:39:12 +02:00
|
|
|
using CultMath;
|
|
|
|
|
using static CultMath.math;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
|
|
|
|
|
public class RadiatorData : BehaviorData
|
|
|
|
|
{
|
|
|
|
|
[Inspectable, JsonProperty("emissivity"), Key(1), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat Emissivity = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("pumpedHeat"), Key(2), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat PumpedHeat = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[InspectableTemperature, JsonProperty("temperatureFloor"), Key(3), RuntimeInspectable]
|
|
|
|
|
public float TemperatureFloor;
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("wasteHeat"), Key(4), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat WasteHeat = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("energyUsage"), Key(5), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat EnergyUsage = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("thermalMass"), Key(6), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat ThermalMass = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
public override Behavior CreateInstance(EquippedItem item)
|
|
|
|
|
{
|
|
|
|
|
return new Radiator(this, item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Behavior CreateInstance(ConsumableItemEffect item)
|
|
|
|
|
{
|
|
|
|
|
return new Radiator(this, item);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-09 10:33:16 +01:00
|
|
|
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
public class Radiator : Behavior, IAlwaysUpdatedBehavior, IInitializableBehavior, IPowerConsumer
|
2020-04-09 10:33:16 +01:00
|
|
|
{
|
2021-04-18 05:13:20 +01:00
|
|
|
public float RadiatorTemperature { get; private set; }
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
|
2021-03-01 01:53:56 +00:00
|
|
|
public float Emissivity { get; private set; }
|
|
|
|
|
public float PumpedHeat { get; private set; }
|
|
|
|
|
public float WasteHeat { get; private set; }
|
|
|
|
|
public float EnergyUsage { get; private set; }
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private RadiatorData _data;
|
2026-06-13 04:56:50 +01:00
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
public Radiator(RadiatorData data, EquippedItem item) : base(data, item)
|
2021-04-18 05:13:20 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_data = data;
|
2021-04-18 05:13:20 +01:00
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
public Radiator(RadiatorData data, ConsumableItemEffect item) : base(data, item)
|
2020-04-09 10:33:16 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_data = data;
|
2020-04-09 10:33:16 +01:00
|
|
|
}
|
|
|
|
|
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
// Cut 3 (docs/stats-and-power-cut.md): mirrors Execute's own early-out below -- a radiator that would not
|
|
|
|
|
// even try to pump this tick (waste would outrun pump capacity) requests nothing, exactly like before.
|
2026-09-19 06:58:57 +02:00
|
|
|
//
|
|
|
|
|
// Nominal-request ruling (docs/stats-and-power-cut.md, operator ruling 2026-09-19): PumpedHeat, WasteHeat and
|
|
|
|
|
// EnergyUsage are all registered request fields (StatValidation.PowerRequestFields) -- read nominally (what
|
|
|
|
|
// full power would pump) so PumpedHeat carrying its own PowerSupply term (Cut 7's brownout curve) no longer
|
|
|
|
|
// makes this tick's request depend on this tick's own grant. Execute below is unchanged: it calls the real,
|
|
|
|
|
// curved Evaluate, so the actual pumping still degrades with whatever the bus actually grants.
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
public float PowerRequest(float dt)
|
|
|
|
|
{
|
2026-09-19 06:58:57 +02:00
|
|
|
var pumpedHeat = EvaluateNominalPower(_data.PumpedHeat);
|
|
|
|
|
var wasteHeat = EvaluateNominalPower(_data.WasteHeat);
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
var tempRatio = max(RadiatorTemperature / Temperature, 1);
|
|
|
|
|
if (tempRatio > pumpedHeat / wasteHeat) return 0f;
|
2026-09-19 06:58:57 +02:00
|
|
|
return EvaluateNominalPower(_data.EnergyUsage) * tempRatio * dt;
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
}
|
|
|
|
|
|
Cut 5: priority tiers replace PowerBus's single shared GrantRatio
PowerTiers.cs names five tiers (Critical/High/Medium/Low/Utility) and their
per-behaviour-kind defaults: Radiator is Critical (the closest thing this game
has to life support -- a starved radiator cascades into wear and shutdown
across the whole hull), Shield is High, Thruster/AetherDrive are Medium,
ConstantWeapon/InstantWeapon are Low, and Sensor/EnergyDraw are Utility.
IPowerConsumer.DefaultPowerTier lets each behaviour own its own default.
EquippableItem.PowerTier (Key 12) is the stored, player-choosable tier on the
unit rather than the design; EquippedItem's constructor seeds a kind default
the first time a unit is equipped, only when the field is still
PowerTiers.Unassigned, and never again -- a tier is a stored choice, not
something the bus re-derives.
PowerBus.AllocateTiers walks tiers lowest-number-first: a tier is granted in
full before the next tier sees anything left, an empty tier passes its whole
share down, and within a tier every consumer rations by the same
grant/demand fraction so unequal requests still divide proportionally. A new
TierGrantRatio[] exposes each tier's own ratio for a future UI (Cut 8) to
read starvation from directly, instead of reconstructing it from every item's
PowerSupply.
Deletes EquippedItem.SortPosition, IOrderedBehavior and Reactor.Order (its
only implementer): the bus already steps before any equipment's Execute, so
equip/behaviour order never decided who got fed even before this cut, and
nothing else needed the interface (subject to the map's O3 operator check).
docs/stats-and-power-cut.md Cut 5 / §1.3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 02:58:38 +02:00
|
|
|
// Cut 5 (docs/stats-and-power-cut.md §1.3, PowerTiers.cs): Critical -- the closest thing this game has to
|
|
|
|
|
// life support. A starved radiator cascades into wear and shutdown across every other item on the hull.
|
|
|
|
|
public int DefaultPowerTier => PowerTiers.Critical;
|
|
|
|
|
|
2021-04-18 05:13:20 +01:00
|
|
|
public override bool Execute(float dt)
|
2021-03-01 01:53:56 +00:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
PumpedHeat = Evaluate(_data.PumpedHeat);
|
|
|
|
|
WasteHeat = Evaluate(_data.WasteHeat);
|
|
|
|
|
EnergyUsage = Evaluate(_data.EnergyUsage);
|
2021-03-01 01:53:56 +00:00
|
|
|
|
2021-04-18 05:13:20 +01:00
|
|
|
var itemTemperature = Temperature;
|
|
|
|
|
var tempRatio = max(RadiatorTemperature / itemTemperature, 1);
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
|
2021-03-01 01:53:56 +00:00
|
|
|
// Temperature ratio would cause more waste heat than pump capacity, stop executing
|
|
|
|
|
if (tempRatio > PumpedHeat / WasteHeat) return true;
|
2021-03-02 23:02:19 +00:00
|
|
|
|
2026-09-19 04:10:19 +02:00
|
|
|
// Cut 7 (docs/stats-and-power-target.md): no separate power gate at all. PumpedHeat above is already a
|
|
|
|
|
// plain Evaluate() read, so a PumpedHeat stat carrying a PowerSupply term already pumps less under a
|
|
|
|
|
// partial grant -- waste heat below is unaffected by the curve, so a starved radiator falls behind and
|
|
|
|
|
// the ship heats up, which is the reduced-performance failure the ruling asks for instead of the pump
|
2026-09-19 05:03:31 +02:00
|
|
|
// simply refusing to run. F1 (docs/stats-and-power-cut.md, operator ruling 2026-09-19): PowerSupply is a
|
|
|
|
|
// multiplier PerformanceStat.Evaluate applies to the whole resolved value, not a term blended into the
|
|
|
|
|
// Min/Max interpolation, so at true zero supply PumpedHeat resolves to exactly 0 regardless of Min -- so
|
|
|
|
|
// "produces nothing" already falls out of Evaluate() without a special case here.
|
2026-08-10 14:57:50 +01:00
|
|
|
var pumpedHeat = PumpedHeat * max(itemTemperature - _data.TemperatureFloor, 0);
|
|
|
|
|
|
2021-03-01 01:53:56 +00:00
|
|
|
// Radiator temperature is below temperature floor, stop executing
|
|
|
|
|
if (pumpedHeat < 0.01f) return true;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
2021-03-01 01:53:56 +00:00
|
|
|
var wasteHeat = WasteHeat * tempRatio;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
2021-04-18 05:13:20 +01:00
|
|
|
AddHeat((wasteHeat - pumpedHeat) * dt);
|
2026-08-10 14:57:50 +01:00
|
|
|
RadiatorTemperature += pumpedHeat / Evaluate(_data.ThermalMass) * dt;
|
2021-03-01 01:53:56 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Update(float delta)
|
2020-04-09 10:33:16 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
Emissivity = Evaluate(_data.Emissivity);
|
2021-04-18 05:13:20 +01:00
|
|
|
var rad = pow(RadiatorTemperature, ItemManager.GameplaySettings.HeatRadiationExponent) * ItemManager.GameplaySettings.HeatRadiationMultiplier * Emissivity;
|
|
|
|
|
RadiatorTemperature -= rad * delta;
|
|
|
|
|
Entity.VisibilitySources[this] = rad;
|
2021-03-01 01:53:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Initialize()
|
|
|
|
|
{
|
2021-04-18 05:13:20 +01:00
|
|
|
RadiatorTemperature = Temperature;
|
2020-05-11 01:17:10 +01:00
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
}
|