diff --git a/Assets/Scripts/ServerShared/Scenarios/Crossing.cs b/Assets/Scripts/ServerShared/Scenarios/Crossing.cs index 82e89f1c4..9d2d02c4e 100644 --- a/Assets/Scripts/ServerShared/Scenarios/Crossing.cs +++ b/Assets/Scripts/ServerShared/Scenarios/Crossing.cs @@ -5,15 +5,17 @@ public sealed class Crossing : Scenario { public override string Name => "Crossing"; public override string Brief => "plight at the starboard gun and a FastBlast+- at the port gun, a fast Djinni crossing close ahead and an " + - "unpiloted one waiting at range. Verify: as the Djinni crosses close, FastBlast lands some and plight " + + "unpiloted one waiting at range. The Djinni starts to port moving across the bow; the arena's gravity bends its run. Verify: as the Djinni crosses close, FastBlast lands some and plight " + "almost none; against the Djinni at range both land. Fly it again without the Fire Control Array: " + "with it the small gun lands more."; public override uint Seed => 112; public override bool Ambient => false; - // Where the Djinni sit: the player is at the origin facing +z. The crosser starts ahead and to starboard, facing - // across the bow, and flies in under 400 m. The waiting Djinni sits inside plight's arc at 80% of its Range. - public static readonly CultMath.float2 CrosserAt = float2(250, 280), CrosserFacing = float2(-1, 0); + // Where the Djinni sit: the player is at the origin facing +z. The crosser starts ahead and to port, facing across + // the bow and already moving at the Djinni's 50 m/s cap, so its run sweeps across the bow within seconds (the arena + // pulls the player to port, which brings the two together). The waiting Djinni sits inside plight's arc at 80% of + // its Range. + public static readonly CultMath.float2 CrosserAt = float2(-150, 200), CrosserFacing = float2(1, 0), CrosserVelocity = float2(50, 0); public const float PlightRange = 1216f; // plight's Range in metres, 1216.4 as the fit stands settled at rest public static readonly CultMath.float2 Waiting = float2(0, PlightRange * .8f); @@ -38,7 +40,7 @@ public sealed class Crossing : Scenario ("Fire Control Array", int2(1, 6), Clockwise)); stage.Player(fighter, float2(0, 0), facing: float2(0, 1)); - stage.Place(stage.Generated("Djinni"), CrosserAt, facing: CrosserFacing, stance: ScenarioStance.Hostile, piloted: true); + stage.Place(stage.Generated("Djinni"), CrosserAt, facing: CrosserFacing, stance: ScenarioStance.Hostile, piloted: true, velocity: CrosserVelocity); stage.Place(stage.Bare("Djinni"), Waiting); } } diff --git a/tests/Aetheria.Shared.Tests/CrossingScenarioTests.cs b/tests/Aetheria.Shared.Tests/CrossingScenarioTests.cs index 3bca80972..7da9e6f72 100644 --- a/tests/Aetheria.Shared.Tests/CrossingScenarioTests.cs +++ b/tests/Aetheria.Shared.Tests/CrossingScenarioTests.cs @@ -32,6 +32,8 @@ public sealed partial class RunStartTests var waiting = staged.Entities[1]; Assert.IsType(crosser); Assert.IsType(waiting); + Assert.Equal(("Djinni", "Djinni"), (_items.GetData(crosser.Hull).Name, _items.GetData(waiting.Hull).Name)); + Assert.Contains(crosser.Equipment, item => item.Behaviors.OfType().Any()); Assert.True(player.IsHostileTo(crosser) && crosser.IsHostileTo(player), "the crosser is hostile both ways"); Assert.Equal(1, AgentsOf(arena, crosser)); @@ -49,8 +51,30 @@ public sealed partial class RunStartTests Assert.True(FireControl.InArc(plight, toWaiting), "the waiting Djinni is inside plight's arc"); Assert.True(length(toWaiting) <= range, "the waiting Djinni is inside plight's Range"); Assert.True(length(toWaiting) > .75f * range, "the waiting Djinni is beyond three quarters of plight's Range"); + + // The crossing is what the sim does with that staging, read in the player's frame: the arena's gravity and the + // crosser's pilot act from the first tick, so the t=0 facing proves nothing. Within CrossingSeconds the crosser's + // bearing sweeps from port to starboard while it is ahead and inside CrossingMetres, and both guns bear on it. + var swept = false; + for (var tick = 0; tick < 60 * CrossingSeconds && !swept; tick++) + { + var before = crosser.Position - player.Position; + arena.Update(1f / 60f); + var after = crosser.Position - player.Position; + if (before.x < 0 && after.x >= 0) + { + swept = true; + Assert.True(after.z > 0, "the crosser passes the bow line ahead of the player, not astern"); + Assert.True(length(after) < CrossingMetres, "the crosser passes inside the stated distance"); + Assert.True(FireControl.InArc(small, after), "FastBlast+- bears on the crosser as it crosses"); + } + } + Assert.True(swept, $"the crosser's bearing never swept across the bow within {CrossingSeconds} s"); } + private const int CrossingSeconds = 6; + private const float CrossingMetres = 150f; + // Djinni Shakedown: the generated Djinni carries a gun on a Ballistic hardpoint (not a launcher); that mount bears on // Ahead and not on Abeam, and both targets are neutral. [Fact] @@ -66,7 +90,13 @@ public sealed partial class RunStartTests Assert.Equal(2, staged.Entities.Count); var ahead = staged.Entities[0]; var abeam = staged.Entities[1]; - Assert.Equal((DjinniShakedown.Ahead, DjinniShakedown.Abeam), (ahead.Position.xz, abeam.Position.xz)); + var facing = player.Direction; + var perpendicular = new float2(facing.y, -facing.x); + Assert.Equal(400f, length(ahead.Position.xz), .01f); + Assert.Equal(400f, length(abeam.Position.xz), .01f); + Assert.Equal(1f, dot(normalize(ahead.Position.xz), facing), .001f); + Assert.Equal(1f, abs(dot(normalize(abeam.Position.xz), perpendicular)), .001f); + Assert.Equal(("Longinus", "Longinus"), (_items.GetData(ahead.Hull).Name, _items.GetData(abeam.Hull).Name)); foreach (var barrel in barrels) { Assert.True(FireControl.InArc(barrel, ahead.Position - player.Position), $"{barrel.Data.Name} cannot bear on Ahead");