Rounds per step: a gun's capacitor holds a step's inflow so every round of the step is paid; the mine-laying test's cooldown no longer pins the one-lay-per-step cap
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
04128e2f81
commit
1870e23bfd
4 changed files with 27 additions and 9 deletions
|
|
@ -42,6 +42,18 @@ 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);
|
||||
|
||||
public float RequestedStepFill(float dt) => min(Rate * dt, max(0f, StepReach(dt) - Charge));
|
||||
|
||||
public void AddStepCharge(float amount, float dt) => Charge = clamp(Charge + amount, 0f, StepReach(dt));
|
||||
|
||||
public void Settle() => Charge = min(Charge, Capacity);
|
||||
|
||||
public bool CanSpend(float cost) => Charge >= cost;
|
||||
|
||||
// Atomic and whole-or-nothing, mirroring Entity.TrySpendCapacitorCharge's old contract exactly (down to the
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerCo
|
|||
public float PowerRequest(float dt)
|
||||
{
|
||||
RefreshInputCapacitor();
|
||||
return _capacitor.RequestedFill(dt);
|
||||
return _capacitor.RequestedStepFill(dt);
|
||||
}
|
||||
|
||||
// Cut 5 (docs/stats-and-power-cut.md §1.3, PowerTiers.cs): Low -- offense. The ruling's own example of what
|
||||
|
|
@ -265,7 +265,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerCo
|
|||
public override bool Execute(float dt)
|
||||
{
|
||||
try { return Step(dt); }
|
||||
finally { _pulled = false; }
|
||||
finally { _pulled = false; _capacitor.Settle(); }
|
||||
}
|
||||
|
||||
// One step of rounds. A round is due at a moment inside the step (_burstTimer is how long ago, at the step's end), and a
|
||||
|
|
@ -277,7 +277,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.AddCharge(_capacitor.RequestedFill(dt) * Item.PowerSupply);
|
||||
_capacitor.AddStepCharge(_capacitor.RequestedStepFill(dt) * Item.PowerSupply, dt);
|
||||
var ended = false;
|
||||
var retriggered = false;
|
||||
if (_coolingDown)
|
||||
|
|
|
|||
|
|
@ -515,8 +515,8 @@ public sealed class MineTests : IDisposable
|
|||
[Fact]
|
||||
public void LayingIsDeterministicAndFillsItsSpread()
|
||||
{
|
||||
var a = Build(spread: 40f, velocity: 5f, cooldown: .01f, layerFaction: A);
|
||||
var b = Build(spread: 40f, velocity: 5f, cooldown: .01f, layerFaction: A);
|
||||
var a = Build(spread: 40f, velocity: 5f, cooldown: .15f, layerFaction: A);
|
||||
var b = Build(spread: 40f, velocity: 5f, cooldown: .15f, layerFaction: A);
|
||||
var angles = new List<float>();
|
||||
using var sub = a.Zone.Mines.ObserveAdd().Subscribe(e =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public sealed partial class ContactRevealTests
|
|||
ship.SetTarget(TargetRef.None);
|
||||
for (var step = 0; step < (int) MathF.Round(seconds / dt); step++)
|
||||
{
|
||||
capacitor.AddCharge(1000f);
|
||||
capacitor.AddStepCharge(1000f, dt);
|
||||
gun.Activate();
|
||||
Tick(dt);
|
||||
}
|
||||
|
|
@ -313,18 +313,24 @@ public sealed partial class ContactRevealTests
|
|||
foreach (var s in ships) s.SetTarget(TargetRef.None);
|
||||
void Step()
|
||||
{
|
||||
for (var i = 0; i < guns; i++) { capacitors[i].AddCharge(1000f); weapons[i].Activate(); }
|
||||
for (var i = 0; i < guns; i++) { capacitors[i].AddStepCharge(1000f, dt); weapons[i].Activate(); }
|
||||
Tick(dt);
|
||||
}
|
||||
for (var i = 0; i < 30; i++) Step(); // warm the JIT and the shot queue
|
||||
rounds = 0;
|
||||
for (var i = 0; i < 60; i++) Tick(dt); // let the in-flight rounds resolve
|
||||
var steps = 300;
|
||||
var idleBytes = GC.GetAllocatedBytesForCurrentThread();
|
||||
var idle = System.Diagnostics.Stopwatch.StartNew();
|
||||
for (var i = 0; i < steps; i++) Tick(dt);
|
||||
idle.Stop();
|
||||
idleBytes = GC.GetAllocatedBytesForCurrentThread() - idleBytes;
|
||||
rounds = 0;
|
||||
var bytes = GC.GetAllocatedBytesForCurrentThread();
|
||||
var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
for (var i = 0; i < steps; i++) Step();
|
||||
watch.Stop();
|
||||
bytes = GC.GetAllocatedBytesForCurrentThread() - bytes;
|
||||
var line = $"{guns} guns at .005 s, {SimClock.TickRate} Hz: {rounds / (double) steps:F1} rounds per step, {watch.Elapsed.TotalMilliseconds / steps:F3} ms per step, {bytes / (double) rounds:F0} heap bytes per round";
|
||||
var line = $"{guns} guns at .005 s, {SimClock.TickRate} Hz: {rounds / (double) steps:F1} rounds per step, {watch.Elapsed.TotalMilliseconds / steps:F3} ms per step, {(bytes - idleBytes) / (double) rounds:F0} heap bytes per round beyond the idle step (idle step {idle.Elapsed.TotalMilliseconds / steps:F3} ms, {idleBytes / (double) steps:F0} bytes)";
|
||||
System.IO.File.WriteAllText(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "simrate-stepcost.txt"), line);
|
||||
Assert.True(Math.Abs(rounds / (double) steps - guns * dt / .005) < 1.0 * guns, line);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue