From a249f86cef4cc61ea38a40bd95eedc8ee25df910 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 12 Oct 2026 04:30:09 +0200 Subject: [PATCH] Tests: the lock fixtures carry munition parts; the migration and item-read tests read the stat the way the engine does LockWarningTests builds launcher designs, which the new gate (ValidateMunitionParts) refuses without parts. The golden test selects the role's Quality term among the stat's heat and durability terms, and the item-read test settles the ship at the item's optimal temperature so only role quality moves the stat. Co-Authored-By: Claude Opus 5.5 --- .../Aetheria.Shared.Tests/LockWarningTests.cs | 11 +++++-- .../MunitionPartsTests.cs | 30 ++++++++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/tests/Aetheria.Shared.Tests/LockWarningTests.cs b/tests/Aetheria.Shared.Tests/LockWarningTests.cs index 28b6abbc2..3b2710660 100644 --- a/tests/Aetheria.Shared.Tests/LockWarningTests.cs +++ b/tests/Aetheria.Shared.Tests/LockWarningTests.cs @@ -68,7 +68,7 @@ public sealed class LockWarningTests : IDisposable private static PerformanceStat Const(float v) => new PerformanceStat { Min = v, Max = v }; - private static GearData Launcher(string name, float lockSpeed, float sensorImpact, float lockAngle, float directionImpact) => new GearData + private static GearData Launcher(string name, float lockSpeed, float sensorImpact, float lockAngle, float directionImpact) => WithMunitionParts(new GearData { Name = name, Hardpoint = HardpointType.Sensors, Shape = new Shape(), Durability = 1, MinimumTemperature = -1000, MaximumTemperature = 1000, OptimalTemperature = 0, PlateauWidth = 2000, @@ -84,7 +84,14 @@ public sealed class LockWarningTests : IDisposable DirectionImpact = Const(directionImpact), Decay = Const(.1f) } } - }; + }); + + // A launcher design carries the five munition parts (StatValidation.ValidateMunitionParts); these fixtures are about the lock. + private static GearData WithMunitionParts(GearData launcher) + { + launcher.Behaviors.AddRange(MunitionPartsTests.Parts(0)); + return launcher; + } private int _nextLot = 2; diff --git a/tests/Aetheria.Shared.Tests/MunitionPartsTests.cs b/tests/Aetheria.Shared.Tests/MunitionPartsTests.cs index 1e3de8fd2..a7da2c8c2 100644 --- a/tests/Aetheria.Shared.Tests/MunitionPartsTests.cs +++ b/tests/Aetheria.Shared.Tests/MunitionPartsTests.cs @@ -30,16 +30,18 @@ public sealed class MunitionPartsTests : IDisposable private static PerformanceStat Flat(float value) => new PerformanceStat { Min = value, Max = value }; - private static PerformanceStat Ranged(float min, float max, string role) => new PerformanceStat + private static PerformanceStat Ranged(float min, float max, string role) => role == null ? Flat(max) : new PerformanceStat { Min = min, Max = max, Terms = new List { new StatTerm { Source = StatSource.Quality, Exponent = 1.5f, Role = role } }, }; - // Every field set to a distinct value, so a field read from the wrong slot or dropped on the way is seen. - private static List Parts(int group) => new List + // The five parts a munition design carries, every field set to a distinct value so a field read from the wrong slot or + // dropped on the way is seen. The thruster's stats vary with the given role's quality when one is named; the design + // that carries them declares it. Fixtures that only need a valid munition pass none. + internal static List Parts(int group, string role = null) => new List { new MunitionHullData { Group = group, Durability = Flat(21), Radius = Flat(4.5f) }, - new MunitionThrusterData { Group = group, Acceleration = Ranged(300, 400, "motor"), Lateral = Ranged(150, 200, "motor"), MaxSpeed = Ranged(250, 350, "motor"), Visibility = Flat(11) }, + new MunitionThrusterData { Group = group, Acceleration = Ranged(300, 400, role), Lateral = Ranged(150, 200, role), MaxSpeed = Ranged(250, 350, role), Visibility = Flat(11) }, new MunitionSeekerData { Group = group, Sensitivity = Flat(.8f), BeamWidth = Flat(55), Range = Flat(900) }, new MunitionControlData { Group = group, Navigation = Flat(3.5f), Evasion = Flat(.4f), EvasionFrequency = Flat(.6f), Lifetime = Flat(12), Cognition = Flat(.35f) }, new MunitionFuelData { Group = group, DeltaV = Flat(700) }, @@ -57,7 +59,7 @@ public sealed class MunitionPartsTests : IDisposable launcher.Group = group; var design = new WeaponItemData { Name = name, Roles = new List { new ItemRole { Name = "motor" } } }; design.Behaviors.Add(launcher); - design.Behaviors.AddRange(Parts(group)); + design.Behaviors.AddRange(Parts(group, "motor")); return design; } @@ -102,12 +104,10 @@ public sealed class MunitionPartsTests : IDisposable Assert.Equal((thrustMin, thrustMax), (thruster.Acceleration.Min, thruster.Acceleration.Max)); Assert.Equal((thrustMin / 2f, thrustMax / 2f), (thruster.Lateral.Min, thruster.Lateral.Max)); Assert.Equal((speedMin, speedMax), (thruster.MaxSpeed.Min, thruster.MaxSpeed.Max)); + // The terms come across whole: the role's Quality term and whatever heat or durability terms the stat carried. + Assert.Equal(Describe(thruster.Acceleration).Split('[')[1], Describe(thruster.Lateral).Split('[')[1]); foreach (var stat in new[] { thruster.Acceleration, thruster.Lateral, thruster.MaxSpeed }) - { - var term = Assert.Single(stat.Terms); - Assert.Equal((StatSource.Quality, "thruster"), (term.Source, term.Role)); - } - Assert.Equal(thruster.Acceleration.Terms.Single().Exponent, thruster.Lateral.Terms.Single().Exponent); + Assert.Equal("thruster", Assert.Single(stat.Terms, t => t.Source == StatSource.Quality).Role); var evasion = Part(design).EvasionFrequency; Assert.Equal((dodge, dodge), (evasion.Min, evasion.Max)); @@ -183,7 +183,15 @@ public sealed class MunitionPartsTests : IDisposable var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null); ship = new Ship(items, zone, new EquippableItem { Data = cache.RefOf(hull), Durability = hull.Durability, Lot = 1 }, settings.DefaultEntitySettings); Assert.True(ship.TryEquip(new EquippableItem { Data = cache.RefOf(gt), Durability = gt.Durability, Lot = 2 }, hardpoint.Position)); - return ship.Equipment.Single(e => e.Data.Name == "GT 3K"); + zone.Entities.Add(ship); + ship.Activate(); + // Settle at the item's optimal temperature, as ConditionRatioTests does, so heat and wear read their identity and only + // the role's quality moves a stat. + var item = ship.Equipment.Single(e => e.Data.Name == "GT 3K"); + foreach (var cell in item.InsetShape.Coordinates) ship.Temperature[cell.x, cell.y] = gt.OptimalTemperature; + item.UpdatePerformance(); + item.UpdatePerformance(); + return item; } [Fact]