Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oLWY1G6S8U35bi2FxVWni
202 lines
8.8 KiB
Markdown
202 lines
8.8 KiB
Markdown
# Auto Radiator
|
||
|
||
A Cosmoteer mod. Radiators stop dumping heat the ship still wants.
|
||
|
||
## The problem
|
||
|
||
Thermal Canister Missiles are built from the ship's own heat. The game says so
|
||
itself, in `Data/strings/en.rules`:
|
||
|
||
> Manufactures guided Thermal Canister Missiles that take thermal energy from
|
||
> their own ship's heat pipe network and inflict it upon the target.
|
||
|
||
The launcher charges each missile from the heat network — `HEAT_PER_MISSILE =
|
||
8500`, two missiles buffered per launcher. A radiator pulls from that same
|
||
network, through a component that is behaviourally identical to the launcher's:
|
||
|
||
| | Radiator | Thermal launcher |
|
||
|---|---|---|
|
||
| Type | `NetworkResourceOutput` | `NetworkResourceOutput` |
|
||
| Mode | `DistributeProportionallyByAvailable` | `DistributeProportionallyByAvailable` |
|
||
| Categories | `[ thermal_storage ]` | `[ thermal_storage ]` |
|
||
|
||
`Data/ships/priorities.rules` defines priorities for crew and supply only —
|
||
nothing for heat. So the two compete flat, proportionally, and a radiator
|
||
running constantly is spending your ammunition to make steam.
|
||
|
||
## Why holding heat is safe
|
||
|
||
Heat storage being full does not hurt you. Damage comes only from *overflow*.
|
||
Every overclocked part routes its heat through this
|
||
(`Data/ships/base_part_overclock.rules`):
|
||
|
||
```
|
||
HeatDistributionStorage
|
||
{
|
||
Type = MultiResourceStorage
|
||
ResourceStorages = [NetworkHeatInput, OverflowHeatStorage]
|
||
AddMode = InOrder
|
||
}
|
||
```
|
||
|
||
`InOrder` means the network is tried first, and `OverflowHeatStorage` only
|
||
receives what the network refuses. Overflow then applies the `cosmoteer.heat`
|
||
status to the producing part's own tiles.
|
||
|
||
From `Data/statuses/heat/heat.rules`, that status does nothing whatsoever below
|
||
**350** per tile (`RequireValueRangeForTick = [350, Infinity]`). Fire chance
|
||
ramps from 350 to 2500; thermal damage begins at 700.
|
||
|
||
An overclocked large reactor produces 2750 heat/sec over its 4×4 footprint —
|
||
about **172 status/sec per tile** while fully overflowing, so roughly **2
|
||
seconds of continuous, total overflow** before fire risk even arms. Radiators
|
||
react within **0.5 s** (`HeatDissipationConverter { Interval = 0.5 }`).
|
||
|
||
At a 98% ceiling on a single 15000-capacity thermal battery, the overshoot
|
||
during that reaction window is about 625 status heat spread over 16 tiles —
|
||
roughly **39 per tile**, against a threshold of 350. Holding the network near
|
||
full is safe with a wide margin, and every percent of ceiling given up is
|
||
ammunition thrown away for nothing.
|
||
|
||
The genuine constraint is not the threshold. It is total radiator throughput
|
||
(1100/sec each) versus sustained generation. If you are short on radiators you
|
||
overflow continuously and no threshold value saves you — which is what the
|
||
safety net below is for.
|
||
|
||
## What the mod does
|
||
|
||
Radiators vent when **any** of these is true:
|
||
|
||
1. The heat network is not asking them to hold — the thermal battery is at or
|
||
above 98% full (dropping the hold again at 94%, a deadband so radiators do
|
||
not chatter at the threshold).
|
||
2. No launcher on the network is set to Thermal Canister Missiles. If you are
|
||
not spending heat as ammunition there is nothing to hoard, so radiators
|
||
behave exactly as in vanilla.
|
||
3. **Safety net:** some part is actually overflowing heat right now. This reads
|
||
the engine's own `OverflowHeatStorage`, so it fires the moment the network
|
||
genuinely refuses heat — regardless of thresholds, pipe topology, or whether
|
||
your radiator capacity was ever sufficient. Latched for 3 seconds so a
|
||
stream of per-tick overflow events becomes one steady signal.
|
||
|
||
Your manual radiator on/off switch still overrides all of it. The automation can
|
||
only idle a radiator you have left switched on.
|
||
|
||
Ships with no thermal battery have no broadcaster, so nothing changes for them.
|
||
The signal polarity is deliberately "hold", not "vent", so that the absence of
|
||
any part of this mod's machinery always falls back to vanilla behaviour.
|
||
|
||
No stat or number is changed. Only *when* radiators run.
|
||
|
||
## Layout
|
||
|
||
```
|
||
mod.rules manifest + patch actions
|
||
components/thermal_battery_vent.rules decides when there is surplus
|
||
components/launcher_ammo_signal.rules reports thermal ammo in use
|
||
components/overclock_overflow_signal.rules safety net, reports real overflow
|
||
components/radiator_auto.rules combines the signals into one gate
|
||
tests/ offline validation suite
|
||
install.sh validate + copy into the Mods folder
|
||
```
|
||
|
||
## Install
|
||
|
||
```sh
|
||
./install.sh
|
||
```
|
||
|
||
Then enable **Auto Radiator** in the game's Mods menu. `./install.sh --uninstall`
|
||
removes it. If the script cannot find your Mods folder, set `COSMOTEER_MODS_DIR`.
|
||
|
||
## Tests
|
||
|
||
```sh
|
||
python3 tests/test_mod.py
|
||
```
|
||
|
||
Cosmoteer applies mod actions to vanilla data at load time, and a path that does
|
||
not resolve, a component reference pointing at nothing, or a misspelled
|
||
component `Type` all fail identically: the game crashes on startup and leaves a
|
||
log file. The suite parses the installed game's `.rules` tree and checks all
|
||
three offline. It also self-checks the parser against known vanilla facts first,
|
||
so a broken parser fails loudly instead of passing everything.
|
||
|
||
Set `COSMOTEER_DATA` to point at a different install.
|
||
|
||
## How to tell it is working
|
||
|
||
There is no separate "auto" mode and nothing to reset. The radiator's on/off
|
||
switch is an AND gate:
|
||
|
||
- switch **off** → radiator is off, and stays off. The mod never turns it back on.
|
||
- switch **on** → the automation decides, moment to moment.
|
||
|
||
An auto-idled radiator shows the same "powered off" indicator icon as a manually
|
||
switched-off one, and stops emitting its steam plume. That icon appearing on a
|
||
radiator you did *not* switch off is the mod working.
|
||
|
||
## Reading the diagnostic icons
|
||
|
||
The radiator reports its own internal state through indicator icons. An icon is
|
||
drawn while its toggle is **off**, so:
|
||
|
||
| Icon on a radiator | Meaning |
|
||
|---|---|
|
||
| 🔧 wrench (`MissingComponent`) | **Always expected.** Its absence means the mod's components never merged onto the part, and nothing else in the mod is running. |
|
||
| thermal missile | No thermal-ammo signal is reaching this radiator — no launcher on its heat network is set to Thermal Canister Missiles. |
|
||
| resource | No hold signal is reaching this radiator — no thermal battery on its network is asking it to hold. |
|
||
| power-off | The radiator is idle: either you switched it off, or the automation idled it. |
|
||
|
||
A correctly working setup, on a ship with a thermal launcher and a battery below
|
||
the vent ceiling, shows **the wrench alone**.
|
||
|
||
These make the failure legible rather than guessable: wrench missing points at
|
||
the patch mechanism, either other icon points at the specific signal that is
|
||
dead.
|
||
|
||
## Status
|
||
|
||
Validated against game data version 0.30.4c, and confirmed to load cleanly
|
||
(`Enabled mods: [User Folder] - ganter.auto_radiator`).
|
||
|
||
**v1.0.0 loaded without error and did nothing in game.** It broadcast its
|
||
signals with `NetworkToggle` / `NetworkToggleReceiver`. All three vanilla uses of
|
||
those types set `IncludeInPhysical = false` — they are blueprint stat-display
|
||
plumbing and carry no signal on a real ship, so every receiver read off, the
|
||
vent gate was permanently open, and radiators behaved exactly as in vanilla.
|
||
|
||
**v1.1.0** carried the signals as `NetworkValue` / `NetworkValueReceiver`
|
||
instead, which is what vanilla's thermal amplification pump uses to drive actual
|
||
physical behaviour. That was necessary but not sufficient: it also loaded
|
||
cleanly and did nothing.
|
||
|
||
**v1.2.0** added the diagnostic indicator icons, which is what finally located
|
||
the fault. The wrench icon was present — so `AddBase` and the `Overrides` had
|
||
been applying correctly the whole time — while both signal icons showed,
|
||
meaning both `NetworkValue`s read zero.
|
||
|
||
**v1.3.0** fixes the actual cause. A network component is only attached to the
|
||
network if a **route** reaches it: the part's `NetworkPort` anchors on one
|
||
component, and a `NetworkRouter` bridges that anchor to every other network
|
||
component on the part. That is how vanilla connects the radiator's own
|
||
`NetworkedThermalDissipationLoadValue`, and how the amplification pump reaches
|
||
the resonance beam turret. Unrouted components are not an error — they simply
|
||
never connect and read as absent forever.
|
||
|
||
Routes were added from each part's anchor: `NetworkedHeatBuffer` on the radiator
|
||
through its existing `Router`, plus a new `NetworkRouter` on the thermal
|
||
battery, the missile launcher and `base_part_overclock`, none of which ships
|
||
one.
|
||
|
||
Three separate mechanisms have now each produced a mod that loads without a
|
||
single error and silently does nothing. The test suite has a mutation-verified
|
||
guard for each: blueprint-only signal types, fields that do not exist in the
|
||
real schema, and unrouted network components.
|
||
|
||
## Possible additions
|
||
|
||
- A per-radiator UI toggle for auto/manual, which needs a new `ChoiceID` wired
|
||
into `Data/gui/game/parts/part_toggles.rules`.
|
||
- Making the 98% / 94% band configurable.
|