F2: gate the bus's reactors and consumers on Active, not Online

PowerBus.Step billed every equipped IPowerConsumer's request unconditionally
and admitted a reactor on Item.Online alone, but Entity.Update only ever runs
a behaviour's Execute when the item is Active (Enabled && Online). A consumer
the player switched off never executes and never spends anything, yet was
still billed, starving live consumers and draining capacitors for nobody. A
reactor switched off stayed Online and kept generating even though
Reactor.Execute (which produces the heat receipt) never ran for it: free,
heat-free power.

Both directions now gate on the same predicate the rest of Entity uses:
item.Active.Value.

Pinned in tests/Aetheria.Shared.Tests/PowerBusTests.cs:
DisabledConsumerContributesNoDemand and DisabledReactorGeneratesNoPower --
both fail under the reverted (Online-only / unconditional) mutation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-19 05:06:31 +02:00
commit 881415d5e3
2 changed files with 54 additions and 2 deletions

View file

@ -93,14 +93,24 @@ public class PowerBus
{
switch (behavior)
{
case Reactor reactor when reactor.Item.Online.Value:
// F2 (docs/stats-and-power-cut.md, Soul pass 2026-09-19): this must agree with the one predicate
// Entity.Update actually gates Execute on (Active.Value, Entity.cs -- Enabled && Online), in both
// directions. Gating on Online alone let a reactor the player switched off (Enabled = false, so
// Active is false but Online stays true) keep generating here even though Reactor.Execute -- the
// behaviour that produces the heat receipt -- never runs for it: free, heat-free power. Capacitor
// has no IPowerConsumer/generation role and is unconditionally a store regardless of its own
// item's state (mirrors pre-existing behaviour; nothing in the ruling asks for that to change).
case Reactor reactor when item.Active.Value:
reactors.Add(reactor);
break;
case Capacitor capacitor:
capacitors.Add(capacitor);
break;
}
if (behavior is IPowerConsumer consumer)
// The other direction of the same rule: a consumer switched off or shut down (Active.Value false)
// never runs its Execute, so it must never be billed either -- otherwise a disabled item's demand
// starves every live consumer and drains the capacitors for energy nobody spends.
if (behavior is IPowerConsumer consumer && item.Active.Value)
{
// Cut 5 (docs/stats-and-power-cut.md §1.3): the item's own stored choice wins once it has one;
// PowerTiers.Unassigned only survives past EquippedItem's constructor for a consumer added to