From e64b50c2beab6da87d4f21c6440fe700d82b2ccc Mon Sep 17 00:00:00 2001 From: Tobias Ganter Date: Fri, 28 Aug 2026 23:46:22 +0200 Subject: [PATCH] feat: make the radiator report its own internal state v1.1.0 loaded cleanly and still did nothing, so the NetworkValue rewrite was not the whole story. Rather than keep guessing at which link in the chain is dead, add indicator icons bound to the internal signals. An indicator draws while its toggle is off, so one glance at a radiator now separates the cases that were previously indistinguishable: wrench absent -> the AddBase never merged; nothing in the mod runs thermal missile -> no thermal-ammo signal arriving resource -> no hold signal arriving from a battery Tests check that every added indicator binds to a component that exists and to a sprite that exists in the indicators atlas. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011oLWY1G6S8U35bi2FxVWni --- README.md | 19 ++++++++++++++ components/radiator_auto.rules | 11 ++++++++ mod.rules | 47 +++++++++++++++++++++++++++++++++- tests/test_mod.py | 45 +++++++++++++++++++++++++++++++- 4 files changed, 120 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 003b698..ed98ead 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,25 @@ 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 diff --git a/components/radiator_auto.rules b/components/radiator_auto.rules index 73d51d5..a327d9f 100644 --- a/components/radiator_auto.rules +++ b/components/radiator_auto.rules @@ -83,3 +83,14 @@ AutoPowerToggle Toggles = [ PowerToggle, AutoRad_VentAllowed ] Mode = All } + +// Diagnostic anchor. An indicator icon shows while its toggle is OFF, so an +// icon bound to this one is visible whenever these components were merged onto +// the part at all. If that icon is absent, the AddBase in mod.rules did not +// take effect and nothing else in this file is running either -- which is the +// single most useful thing to be able to tell apart from a dead signal. +AutoRad_AlwaysOff +{ + Type = StaticToggle + ToggleOn = false +} diff --git a/mod.rules b/mod.rules index c19bb87..e7807b4 100644 --- a/mod.rules +++ b/mod.rules @@ -16,7 +16,7 @@ ID = ganter.auto_radiator Name = "Auto Radiator" -Version = 1.1.0 +Version = 1.2.0 CompatibleGameVersions = ["0.30.4c", "0.30.4"] ModifiesGameplay = true Author = "Tobias Ganter" @@ -118,4 +118,49 @@ Actions Toggle = AutoPowerToggle } } + + // --- Diagnostic indicators ------------------------------------------- + // + // An indicator icon is drawn while its toggle is OFF. These three make the + // radiator report its own internal state, so a radiator that is not + // behaving can be read directly instead of guessed at: + // + // wrench icon always visible -> these components merged onto the part + // absent -> the AddBase did not take effect + // thermal missile visible -> no thermal-ammo signal is arriving + // resource icon visible -> no hold signal is arriving from a battery + // + // A working setup on a ship with a thermal launcher and a battery below the + // vent ceiling shows the wrench alone. + { + Action = AddMany + AddTo = "/Part/Components/Indicators/Indicators" + ManyToAdd + [ + { + Toggle = AutoRad_AlwaysOff + AtlasSprite + { + File = &/INDICATORS/MissingComponent + Size = [1, 1] + } + } + { + Toggle = AutoRad_ThermalAmmoInUse + AtlasSprite + { + File = &/INDICATORS/NoThermalMissiles + Size = [1, 1] + } + } + { + Toggle = AutoRad_IsHolding + AtlasSprite + { + File = &/INDICATORS/NoResource + Size = [1, 1] + } + } + ] + } ] diff --git a/tests/test_mod.py b/tests/test_mod.py index a1dd9d2..2d3ba7f 100644 --- a/tests/test_mod.py +++ b/tests/test_mod.py @@ -365,7 +365,50 @@ def test_no_name_collisions_with_vanilla(): # --------------------------------------------------------------------------- -# 7. The mod manifest must declare the fields Cosmoteer requires. +# 7. Added indicators must bind to a real toggle and a real atlas sprite. +# --------------------------------------------------------------------------- + +def test_added_indicators_are_valid(): + mod = rules_parser.parse_file(os.path.join(MOD_ROOT, "mod.rules")) + radiator_components = resolve("/Part/Components") + added = rules_parser.parse_file(os.path.join(MOD_ROOT, "components", "radiator_auto.rules")) + known = set(added.order) | set(radiator_components.order if radiator_components else []) + + atlas = vanilla("gui/game/indicators/indicators.rules") + check(atlas is not None, "cannot read the indicators atlas") + + for action in iter_actions(mod): + kind = action.get("Action") + add_to = action.get("AddTo") + if not kind or kind.value != "AddMany" or add_to is None: + continue + if "Indicators" not in add_to.value: + continue + entries = action.get("ManyToAdd") + check(entries is not None, "indicator AddMany has no ManyToAdd list") + if entries is None: + continue + for key in entries.order: + entry = entries.children[key] + toggle = entry.get("Toggle") + check(toggle is not None, "an added indicator has no Toggle") + if toggle is not None: + check( + toggle.value.strip() in known, + f"added indicator binds to unknown component '{toggle.value.strip()}'", + ) + sprite = entry.get("AtlasSprite/File") + check(sprite is not None, "an added indicator has no AtlasSprite/File") + if sprite is not None and atlas is not None: + name = sprite.value.strip().rsplit("/", 1)[-1] + check( + name in atlas.children, + f"added indicator uses unknown atlas sprite '{name}'", + ) + + +# --------------------------------------------------------------------------- +# 8. The mod manifest must declare the fields Cosmoteer requires. # --------------------------------------------------------------------------- def test_manifest_is_well_formed():