Tests: ore dice keyed by ShotId and DropSeed on both fire paths; TrySell of an unheld commodity pays nothing
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5a3324d612
commit
5663361e94
4 changed files with 98 additions and 4 deletions
|
|
@ -24,6 +24,9 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
private readonly List<CultCache> _caches = new List<CultCache>();
|
||||
private int _fixtureCount;
|
||||
|
||||
// A run seed for the scenes built next: nonzero gives the zone a galaxy carrying it (so Zone.DropSeed differs by it).
|
||||
internal uint GalaxySeed;
|
||||
|
||||
public MiningCut3Tests() => Directory.CreateDirectory(_root);
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -163,7 +166,8 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
pack.Planets.Add(new CultRecordRef<BodyData>(key));
|
||||
scene.Belts.Add(key);
|
||||
}
|
||||
scene.Zone = new Zone(items, PlanetSettings(lightRadius), pack, new GalaxyZone { Name = "MiningCut3", Owner = null }, null);
|
||||
scene.Zone = new Zone(items, PlanetSettings(lightRadius), pack, new GalaxyZone { Name = "MiningCut3", Owner = null },
|
||||
GalaxySeed == 0 ? null : RunSeedFixture.Continue(cache, "MiningCut3", GalaxySeed));
|
||||
return scene;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,8 +58,9 @@ public sealed class MiningCut4bTests : IDisposable
|
|||
// `precision` 1 gives sigma 1 cell; accuracy 1 and a resolution that asks little of the info make PSensor 1.
|
||||
internal Rig Build(bool sensor = true, bool launcher = false, float damage = 10f, float velocity = 0f, float spread = 0f,
|
||||
float precision = 1f, float effective = float.PositiveInfinity, WeaponFuse? fuse = null, float blastRadius = 0f,
|
||||
float rockSize = 0f, float crossSection = 20f, bool target = true, float tracking = 1000000f, FieldKindData kind = null)
|
||||
float rockSize = 0f, float crossSection = 20f, bool target = true, float tracking = 1000000f, FieldKindData kind = null, uint galaxySeed = 0)
|
||||
{
|
||||
_fixtures.GalaxySeed = galaxySeed;
|
||||
kind ??= MiningCut3Tests.Kind("Asteroid", crossSection);
|
||||
var scene = _fixtures.BuildScene(new[] { kind }, MiningCut3Tests.Belt(kind, MiningCut3Tests.Rock(Distance, .25f, rockSize)));
|
||||
var gear = MiningCut3Tests.Gear("Targeting", new TargetingSystemData
|
||||
|
|
|
|||
|
|
@ -56,10 +56,10 @@ public sealed class MiningCut5aTests : IDisposable
|
|||
|
||||
// A rig whose rock (size .5: 4 units, whole) yields `ores`, shooter holding one bay.
|
||||
private (MiningCut4bTests.Rig Rig, EquippedCargoBay Bay) Mining(int bayWidth = 4, int bayHeight = 4, float damage = 1000f,
|
||||
WeaponFuse? fuse = null, float velocity = 0f, params (SimpleCommodityData, float)[] ores)
|
||||
WeaponFuse? fuse = null, float velocity = 0f, uint galaxySeed = 0, float rockSize = .5f, params (SimpleCommodityData, float)[] ores)
|
||||
{
|
||||
var kind = MiningCut3Tests.Kind("Asteroid", 20f);
|
||||
var rig = _rigs.Build(damage: damage, fuse: fuse, blastRadius: fuse == null ? 0f : 6f, precision: 1000f, rockSize: .5f, velocity: velocity, kind: kind);
|
||||
var rig = _rigs.Build(damage: damage, fuse: fuse, blastRadius: fuse == null ? 0f : 6f, precision: 1000f, rockSize: rockSize, velocity: velocity, kind: kind, galaxySeed: galaxySeed);
|
||||
Stock(rig.Scene, kind, ores.Length > 0 ? ores : new[] { (Commodity("Rock"), 1f) });
|
||||
return (rig, Bay(rig.Scene, rig.Shooter, bayWidth, bayHeight, "Hold"));
|
||||
}
|
||||
|
|
@ -242,4 +242,70 @@ public sealed class MiningCut5aTests : IDisposable
|
|||
Assert.True(mixes.Max() - mixes.Min() > 100, "rocks differ in mix when variance is above zero");
|
||||
Assert.Equal(mixes, Break(scene2, varied(), ship2, 12, 5).Select(m => m.Rock));
|
||||
}
|
||||
|
||||
// ==== the shot's own dice: ShotId and the zone's DropSeed key the ore ====
|
||||
|
||||
// A rig whose rock outlives many shots and pays hundreds of units a hit, three ores, so a changed key shows in the mix. The rock is
|
||||
// wider than the blast, so every blast wears it by the same share and only the dice can tell two shots apart.
|
||||
private (MiningCut4bTests.Rig Rig, EquippedCargoBay Bay) Rich(WeaponFuse? fuse, float damage, uint galaxySeed = 0)
|
||||
{
|
||||
var (rig, bay) = Mining(bayWidth: 8, bayHeight: 8, damage: damage, fuse: fuse, galaxySeed: galaxySeed, rockSize: 1f,
|
||||
ores: new[] { (Commodity("Rock"), 1f), (Commodity("Gold"), 1f), (Commodity("Ice"), 1f) });
|
||||
rig.Zone.Settings.AsteroidYield = new ExponentialLerp { Minimum = 6000f, Maximum = 6000f, Exponent = 1f };
|
||||
// Composition hashes the belt's record key, which differs per scene; variance 0 leaves the dice alone to vary the ore.
|
||||
rig.Zone.Settings.ChunkCompositionVariance = 0f;
|
||||
return (rig, bay);
|
||||
}
|
||||
|
||||
// Fires until `hits` shots have landed, returning each landed shot's payout (bay and floats together) as (Rock, Gold, Ice).
|
||||
private static (int, int, int)[] Payouts(MiningCut4bTests.Rig rig, EquippedCargoBay bay, int hits)
|
||||
{
|
||||
IEnumerable<ItemInstance> Held() => bay.Cargo.Keys.Concat(rig.Zone.FloatingItems.Select(f => f.Item));
|
||||
var paid = new List<(int, int, int)>();
|
||||
var before = (0, 0, 0);
|
||||
for (var shot = 0; shot < 100 && paid.Count < hits; shot++)
|
||||
{
|
||||
var landed = rig.Resolved.Count(o => o.Hit);
|
||||
FireControl.Fire(rig.Gun, rig.GunItem, rig.Shooter);
|
||||
MiningCut4bTests.Run(rig, .05f);
|
||||
if (rig.Resolved.Count(o => o.Hit) == landed) continue;
|
||||
Assert.True(rig.Zone.ChunkExists(rig.Rock), "the rock must outlive the shots under test");
|
||||
var held = Held().ToArray();
|
||||
var now = (Units(held, "Rock", rig.Scene), Units(held, "Gold", rig.Scene), Units(held, "Ice", rig.Scene));
|
||||
paid.Add((now.Item1 - before.Item1, now.Item2 - before.Item2, now.Item3 - before.Item3));
|
||||
before = now;
|
||||
}
|
||||
Assert.Equal(hits, paid.Count);
|
||||
return paid.ToArray();
|
||||
}
|
||||
|
||||
// Which ore lands in which slot follows each scene's catalog keys, so scenes compare by the multiset of counts.
|
||||
private static int[][] Mix(IEnumerable<(int, int, int)> paid) => paid.Select(p => new[] { p.Item1, p.Item2, p.Item3 }.OrderBy(n => n).ToArray()).ToArray();
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, 8f)]
|
||||
[InlineData(WeaponFuse.Contact, 40f)]
|
||||
public void TwoShotsInOneZonePayDifferentOre(WeaponFuse? fuse, float damage)
|
||||
{
|
||||
var (rig, bay) = Rich(fuse, damage);
|
||||
var paid = Payouts(rig, bay, 2);
|
||||
Assert.All(paid, p => Assert.True(p.Item1 + p.Item2 + p.Item3 > 100, "a shot pays hundreds of units"));
|
||||
Assert.NotEqual(paid[0], paid[1]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, 8f)]
|
||||
[InlineData(WeaponFuse.Contact, 40f)]
|
||||
public void ADifferentRunSeedPaysDifferentOreForTheSameShot(WeaponFuse? fuse, float damage)
|
||||
{
|
||||
var (a, bayA) = Rich(fuse, damage, galaxySeed: 7);
|
||||
var (b, bayB) = Rich(fuse, damage, galaxySeed: 8);
|
||||
Assert.NotEqual(a.Zone.DropSeed, b.Zone.DropSeed);
|
||||
Assert.Equal(a.Zone.CombatSeed, b.Zone.CombatSeed);
|
||||
Assert.NotEqual(Mix(Payouts(a, bayA, 1)), Mix(Payouts(b, bayB, 1)));
|
||||
// The same seed repeats it.
|
||||
var (c, bayC) = Rich(fuse, damage, galaxySeed: 7);
|
||||
var (d, bayD) = Rich(fuse, damage, galaxySeed: 7);
|
||||
Assert.Equal(Mix(Payouts(c, bayC, 1)), Mix(Payouts(d, bayD, 1)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,6 +192,29 @@ public sealed class StationServicesTests : IDisposable
|
|||
Assert.Empty(station.CargoBays[0].Cargo.Keys.OfType<SimpleCommodity>());
|
||||
}
|
||||
|
||||
// A commodity the seller does not hold sells for nothing: not one never stored, and not a stale copy of a stack the sale
|
||||
// already took. The held stack is untouched, and no credit is paid.
|
||||
[Fact]
|
||||
public void TrySell_commodity_not_in_the_hold_pays_nothing()
|
||||
{
|
||||
var from = Ship().CargoBays.Single();
|
||||
var held = Ore(10);
|
||||
Assert.True(from.TryStore(held));
|
||||
var station = Station();
|
||||
var credits = 500;
|
||||
|
||||
Assert.False(StationServices.TrySell(from, station, Ore(10), ref credits));
|
||||
Assert.Equal(500, credits);
|
||||
Assert.Empty(station.CargoBays.Single().Cargo.Keys.OfType<SimpleCommodity>());
|
||||
Assert.Equal(10, Assert.Single(from.Cargo.Keys.OfType<SimpleCommodity>()).Quantity);
|
||||
|
||||
Assert.True(StationServices.TrySell(from, station, held, ref credits));
|
||||
var paid = credits;
|
||||
Assert.False(StationServices.TrySell(from, station, held, ref credits));
|
||||
Assert.Equal(paid, credits);
|
||||
Assert.Equal(10, station.CargoBays.Single().Cargo.Keys.OfType<SimpleCommodity>().Sum(c => c.Quantity));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Repair_restores_and_charges()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue