From 93f69ecb80cba88c4f4b71d0df42972540629a5b Mon Sep 17 00:00:00 2001 From: soul Date: Fri, 23 Oct 2026 00:09:21 +0200 Subject: [PATCH] Rounds per step: a held weapon asks the bus for its step's whole inflow; mine-laying test cooldown stays inside one activation's two steps Co-Authored-By: Claude Sonnet 5.5 --- .../Scripts/ServerShared/Behaviors/InputCapacitor.cs | 10 +++++----- Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs | 8 ++++++-- tests/Aetheria.Shared.Tests/MineTests.cs | 4 ++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Assets/Scripts/ServerShared/Behaviors/InputCapacitor.cs b/Assets/Scripts/ServerShared/Behaviors/InputCapacitor.cs index 4198801bd..f6b61f3d6 100644 --- a/Assets/Scripts/ServerShared/Behaviors/InputCapacitor.cs +++ b/Assets/Scripts/ServerShared/Behaviors/InputCapacitor.cs @@ -42,11 +42,11 @@ public class InputCapacitor public void AddCharge(float amount) => Charge = clamp(Charge + amount, 0f, Capacity); - // A weapon may fire several rounds in one step (ruling rounds-per-step), and what flows in during that step is what pays - // for them, so its buffer is Capacity or the step's inflow, whichever is more; Settle trims the surplus a step did not spend. - // The fill rate is unchanged, so the energy a gun draws is still exactly its rounds' cost. A sensor or a shield keeps the - // one-activation buffer above. - private float StepReach(float dt) => max(Capacity, Rate * dt); + // A weapon held through a step may fire several rounds in it (ruling rounds-per-step): the charge it holds and what flows in + // during the step pay for them, so for that step its buffer is a full activation plus the step's inflow; Settle trims what + // the step did not spend back to Capacity. The fill rate is unchanged, so the energy a gun draws is still exactly its + // rounds' cost. A sensor or a shield, and a weapon not held, keep the one-activation buffer above. + private float StepReach(float dt) => Capacity + Rate * dt; public float RequestedStepFill(float dt) => min(Rate * dt, max(0f, StepReach(dt) - Charge)); diff --git a/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs b/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs index 88f7942e3..49a0b7ea6 100644 --- a/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs +++ b/Assets/Scripts/ServerShared/Behaviors/InstantWeapon.cs @@ -177,11 +177,15 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerCo _capacitor.UpdateStats(perShotEnergy, cooldown, rateOverride: RateOverride); } + // What the capacitor asks the bus for this step: a weapon held through it may fire several rounds in it, so it asks for + // the step's whole inflow; any other tops up to one activation. + private float Fill(float dt) => Repeats ? _capacitor.RequestedStepFill(dt) : _capacitor.RequestedFill(dt); + // Cut 4: the request PowerBus needs before Execute runs, mirroring EnergyDraw's IPowerConsumer pattern. public float PowerRequest(float dt) { RefreshInputCapacitor(); - return _capacitor.RequestedStepFill(dt); + return Fill(dt); } // Cut 5 (docs/stats-and-power-cut.md ยง1.3, PowerTiers.cs): Low -- offense. The ruling's own example of what @@ -277,7 +281,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerCo // Cut 4: this tick's grant, read back via Item.PowerSupply -- Item is non-null here because a null-Item // instance never reaches PowerBus.Step (see TrySpendActivationEnergy) and so never accrues charge. if (Item != null) - _capacitor.AddStepCharge(_capacitor.RequestedStepFill(dt) * Item.PowerSupply, dt); + _capacitor.AddStepCharge(Fill(dt) * Item.PowerSupply, dt); var ended = false; var retriggered = false; if (_coolingDown) diff --git a/tests/Aetheria.Shared.Tests/MineTests.cs b/tests/Aetheria.Shared.Tests/MineTests.cs index 880fee858..ccc397c2c 100644 --- a/tests/Aetheria.Shared.Tests/MineTests.cs +++ b/tests/Aetheria.Shared.Tests/MineTests.cs @@ -515,8 +515,8 @@ public sealed class MineTests : IDisposable [Fact] public void LayingIsDeterministicAndFillsItsSpread() { - var a = Build(spread: 40f, velocity: 5f, cooldown: .15f, layerFaction: A); - var b = Build(spread: 40f, velocity: 5f, cooldown: .15f, layerFaction: A); + var a = Build(spread: 40f, velocity: 5f, cooldown: .199f, layerFaction: A); + var b = Build(spread: 40f, velocity: 5f, cooldown: .199f, layerFaction: A); var angles = new List(); using var sub = a.Zone.Mines.ObserveAdd().Subscribe(e => {