Gate radiators through daemon behavior chains

This commit is contained in:
Meta Vens 2026-07-17 03:14:26 +01:00
commit 020f3aec92
7 changed files with 44 additions and 3 deletions

View file

@ -402,13 +402,23 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
new AetheriaRuntimeBehaviorField(2, BoolValue(false)),
new AetheriaRuntimeBehaviorField(3, BoolValue(true))
]),
new AetheriaRuntimeBehaviorPayload(1, "Heat", 0,
new AetheriaRuntimeBehaviorPayload(1, "Radiator", 0,
[
new AetheriaRuntimeBehaviorField(1, PerformanceStat(0)),
new AetheriaRuntimeBehaviorField(2, PerformanceStat(1)),
new AetheriaRuntimeBehaviorField(3, Number(200)),
new AetheriaRuntimeBehaviorField(4, PerformanceStat(0)),
new AetheriaRuntimeBehaviorField(5, PerformanceStat(0)),
new AetheriaRuntimeBehaviorField(6, PerformanceStat(10))
]),
new AetheriaRuntimeBehaviorPayload(2, "Heat", 0,
[
new AetheriaRuntimeBehaviorField(1, PerformanceStat(10)),
new AetheriaRuntimeBehaviorField(2, BoolValue(false))
]));
var catalog = new AetheriaRuntimeCatalogSnapshot([thermostat], [], []);
var entity = Entity(0, 0, "player");
entity.HeatsinksEnabled = true;
entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit
{
Item = new AetheriaRuntimeLoadoutItemCommit
@ -433,13 +443,23 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
SetTarget(200, 1);
var before = AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 0);
AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 0.1);
AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, 0.1);
RequireNear(before, AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 0), 0.000001,
"a low-pass thermotoggle above its target must stop downstream heat");
Require(!entity.BehaviorStates.Single(value => value.BehaviorKind == "Heat").ChainReached,
"a closed thermostat must keep downstream specialized and common behaviors unreachable");
RequireNear(0, entity.BehaviorStates.Single(value => value.BehaviorKind == "Radiator").PumpedHeat, 0.000001,
"a closed thermostat must keep the radiator subsystem from running");
SetTarget(300, 2);
AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 0.1);
AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, 0.1);
Require(AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 0) > before,
"a low-pass thermotoggle below its target must admit downstream heat");
Require(entity.BehaviorStates.Single(value => value.BehaviorKind == "Heat").ChainReached,
"an open thermostat must publish that the downstream behavior was reached this tick");
RequireNear(1, entity.BehaviorStates.Single(value => value.BehaviorKind == "Radiator").PumpedHeat, 0.000001,
"an open thermostat must admit the radiator subsystem through the same chain state");
void SetTarget(double target, long sequence)
{
@ -4929,6 +4949,7 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
var catalog = new AetheriaRuntimeCatalogSnapshot([capacitor, radiator], [], []);
AetheriaRuntimeEnergySimulation.BeginTick(entity, catalog);
AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 1);
AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, 1);
RequireNear(9, entity.BehaviorStates.Single(value => value.BehaviorKind == "Capacitor").CapacitorCharge,

View file

@ -113,6 +113,8 @@ namespace GameCult.Aetheria.State.Verse
.Where(value => value != null && string.Equals(value.OwnerKind,
AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal))
.ToDictionary(value => (value.OwnerIndex, value.BehaviorIndex));
foreach (var state in states.Values)
state.ChainReached = false;
var online = (entity.EquipmentStates ?? Array.Empty<AetheriaRuntimeEquipmentStateCommit>())
.Where(value => value != null).ToDictionary(value => value.EquipmentIndex);
var equipment = entity.Equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
@ -136,6 +138,7 @@ namespace GameCult.Aetheria.State.Verse
{
if (!states.TryGetValue((equipmentIndex, entry.Index), out var state))
break;
state.ChainReached = true;
if (string.Equals(entry.Payload.Kind, "Switch", StringComparison.Ordinal) && !state.SwitchActivated)
break;
if (string.Equals(entry.Payload.Kind, "Trigger", StringComparison.Ordinal))

View file

@ -63,7 +63,7 @@ namespace GameCult.Aetheria.State.Verse
AetheriaRuntimeCatalogSnapshot? catalog, double deltaSeconds)
{
if (entity == null || catalog == null || deltaSeconds <= 0 || !entity.HeatsinksEnabled) return;
foreach (var radiator in Online(entity, catalog, "Radiator"))
foreach (var radiator in AetheriaRuntimeEquippedBehaviorQueries.FindExecuting(entity, catalog, "Radiator"))
{
var itemTemperature = AetheriaRuntimeThermalSimulation.EquipmentTemperature(
entity, catalog, radiator.EquipmentIndex);

View file

@ -138,5 +138,13 @@ namespace GameCult.Aetheria.State.Verse
(!equipmentStates.TryGetValue(value.EquipmentIndex, out var state) || state.Online))
.ToArray();
}
public static IReadOnlyList<AetheriaRuntimeEquippedBehavior> FindExecuting(
AetheriaRuntimeEntitySnapshotCommit? entity,
AetheriaRuntimeCatalogSnapshot? catalog,
string behaviorKind) =>
FindOperational(entity, catalog, behaviorKind)
.Where(value => value.State.ChainReached)
.ToArray();
}
}

View file

@ -1518,6 +1518,9 @@ namespace GameCult.Aetheria.State.Verse
[Key(58)]
public double CooldownProgress { get; set; }
[Key(59)]
public bool ChainReached { get; set; }
[Key(55)]
public string ResourceScannerTargetBodyKey { get; set; } = "";

View file

@ -323,6 +323,12 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
adjustment; non-finite values, wrong behavior indices, missing catalogs, and
fixed thermostats fail closed. Eve receives the live target as ordinary
behavior state and remains presentation-only.
Each behavior row also records whether ordered execution reached it during
the current fixed step. Specialized subsystem handlers consume that daemon
fact instead of equating "installed and online" with "admitted by the
chain". Radiators are the first migrated consumer: a closed thermostat makes
its downstream radiator unreachable, so the energy/thermal handler cannot
pump heat behind the gate.
- Sensor contact information is daemon-owned. Each operational installed
`Sensor` behavior executes the fossil information equation against target
visibility, distance, observer-relative angle, its authored sensitivity

View file

@ -43,7 +43,7 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity.
| Save/resume | original persistence paths | daemon/CultCache | published current state | clients rehydrate | owner-exists; not parity-proven | mid-loop restart |
| Terminus loop | original game composition | daemon/Ymir/Eve | pilot surface | Aetheria.Unity | live minimum-loop proof: generic Unity discovers the provider, lowers the pilot world/assets, single-steps dock and undock while paused, buys daemon-priced station stock, single-steps Ymir salvage collection, resumes, moves, targets, fires, and presents authoritative receipts/shot outcome; the released full-session profile starts with no pickup, destroys a deterministic target, observes its canonical cargo drop, tractors it into a Ymir Begin contact, and proves exactly one `ymir-fact:` collection receipt plus cargo `0` to `1` | richer trade content, docking transitions, and sustained combat encounters |
| Starbridge | original/shared architecture intent | one daemon session | pilot + commander surfaces | Unity + Electron | commander task board and autonomous explore execution wired; Electron Eve lowering and split-target proof missing | commander orders plus four pilots and autonomous workers |
| Behavior engine | `ServerShared/Behaviors` composition | daemon | executable action/status projection | action bar and feedback | tick reconciles equipped catalog payloads into persistent behavior state; `Switch`, one-shot `Trigger`, normalized `Cooldown`, adjustable high/low-pass `Thermotoggle`, `EnergyDraw`, equipment `Heat`, atomic `ItemUsage`, and thermal `Wear` execute in authored group order and stop downstream effects on refusal; cooldown projects generic progress and thermostat target is daemon-commanded state seeded from authored data. Equipped and timed-consumable `StatModifier` sources persist daemon-owned lifecycle state and every runtime stat reader applies the fossil `base × multiplier product + constant sum` rule through one query; legacy `Data` kind references and required-behavior filters are honored | remaining specialized behavior return-value composition and canonical consumable-content proof |
| Behavior engine | `ServerShared/Behaviors` composition | daemon | executable action/status projection | action bar and feedback | tick reconciles equipped catalog payloads into persistent behavior state; `Switch`, one-shot `Trigger`, normalized `Cooldown`, adjustable high/low-pass `Thermotoggle`, `EnergyDraw`, equipment `Heat`, atomic `ItemUsage`, and thermal `Wear` execute in authored group order and stop downstream effects on refusal; each row records whether the chain reached it, and the radiator subsystem now requires that fact rather than mere installation. Cooldown projects generic progress and thermostat target is daemon-commanded state seeded from authored data. Equipped and timed-consumable `StatModifier` sources persist daemon-owned lifecycle state and every runtime stat reader applies the fossil `base × multiplier product + constant sum` rule through one query; legacy `Data` kind references and required-behavior filters are honored | thruster/AetherDrive/radiator success gating their trailing wear, remaining specialized return-value composition, and canonical consumable-content proof |
| Sensors/visibility | installed sensor sensitivity and angular curve, `Visibility`, `Reflector`, decaying signature sources, stellar light, ping state, and observer information thresholds | daemon | accumulated contacts, named visibility sources, and ping events | indicators/maps and provider environment lowering | every operational installed Sensor executes the fossil visibility/sensitivity/angle/distance information equation and decay; visibility is thresholded by the provider setting. Actor-scoped ping owns energy, cooldown, expansion radius, reconnect-stable crossed-target state, one-time information boost, and transient signature. Operational `Visibility` equipment publishes evaluated constant signature; committed weapons publish authored signature including charged-shot multiplication; transient weapon and ping sources decay by the fossil exponential rule. Reflectors multiply evaluated cross-section by the exponent-8 stellar `PowerPulse` after thermal settlement. Gameplay light and environment projection share resolved authored orbit/body centers and provider-owned sun radius inputs. Contact refresh no longer invents perfect information or overwrites emitted visibility; docked craft consume one daemon-derived union of craft and dock-parent contacts through both Eve rows and SoA bodies, with sensor-source provenance | active consumable visibility/reflector effects, complete source cardinality, classification timelines, and canonical catalog station-array data |
| AI/tasks | corporation task scheduler plus agent state machines and installed orbital `TurretController` | daemon | commander task board, corporation survey ledger, controller aim/state, and shared semantic controls | Eve commander/pilot clients | point exploration, loadout-driven attack, haul, mining/offload, resource surveying, physical station towing, persistent orbit patrol, route-aware assignment, and autonomous return home are wired through shared commands. Ship combat samples the fossil DPS/range policy, selects live weapon groups, leads projectiles, strafes relative to target attitude, and gates fire on catalog placement rotation. Operational orbital controllers independently acquire the first visible hostile ship, publish predictive look direction, and trigger only exact aligned in-range weapon behaviors through the shared combat lifecycle; non-player identity no longer grants implicit group-zero fire. Cross-zone task and home routes survive the full daemon-owned wormhole timeline; attack admission rejects missing/friendly targets and unavailable weapon groups before durable queueing | cancellation, reconnect, multi-client command transfer, and model-local barrel-direction metadata |
| Consumables | cargo-backed timed behavior containers | daemon | dynamic actions, active effects, and lifecycle facts | action bar/schematic | daemon atomically resolves typed metadata and concrete cargo, preserves consumed-instance quality, enforces authored stacking, evaluates effectiveness/quality, and executes ordered `EnergyDraw`, fossil-compatible no-op `Heat`, `ItemUsage`, and stateful `Cooldown` payloads after energy tick initialization; every effect and payload state has stable authored identity; cooldowns update before the ordered chain, require state strictly below zero, reset to one on success, and continue aging behind earlier failures; false and unsupported payloads stop later execution with a typed fact; timers retain the fossil's exact-zero final tick; Unity reconciles presentation by effect identity and cannot restore active behavior state by list index | catalog revision pinning, stat modifiers, remaining behavior families, and reconnect presentation proof |