diff --git a/Assets/Scripts/ServerShared/ScenarioStage.cs b/Assets/Scripts/ServerShared/ScenarioStage.cs index d89e4c7af..735d40775 100644 --- a/Assets/Scripts/ServerShared/ScenarioStage.cs +++ b/Assets/Scripts/ServerShared/ScenarioStage.cs @@ -136,16 +136,15 @@ public sealed class ScenarioStage return entity; } - // The patrol order a piloted entity starts on: the one orbit that sits nearest `near` when the stage opens. A check - // that rests on where a pilot goes states it here, so the pilot's circuit is not a draw. An entity that is not - // piloted or not placed is a failure. - public void Patrol(Entity entity, float2 near) + // The patrol order a piloted entity starts on: the one orbit at this index of the arena's orbits, which a galaxy seed + // fixes. A check that rests on where a pilot goes states it here, so the pilot's circuit is not a draw. An entity that + // is not placed and piloted, or an index past the orbits, is a failure. + public void Patrol(Entity entity, int orbit) { var placement = _placed.FirstOrDefault(p => ReferenceEquals(p.Entity, entity)); if (placement == null || !placement.Piloted) { _failures.Add($"patrol: {entity?.Name ?? "no entity"} is not a placed, piloted ship"); return; } - var orbit = Arena.Orbits.Keys.OrderBy(key => lengthsq(Arena.GetOrbitPosition(key) - near)).Cast().FirstOrDefault(); - if (orbit == null) { _failures.Add("patrol: the arena has no orbit to patrol"); return; } - placement.Circuit = new[] { orbit.Value }; + if (orbit < 0 || orbit >= Arena.Orbits.Count) { _failures.Add($"patrol: the arena has no orbit {orbit} (it has {Arena.Orbits.Count})"); return; } + placement.Circuit = new[] { Arena.Orbits.Keys.ElementAt(orbit) }; } // Items in the entity's first cargo bay, one per name. A name is an equippable or consumable design (open: the stage diff --git a/Assets/Scripts/ServerShared/Scenarios/LockOn.cs b/Assets/Scripts/ServerShared/Scenarios/LockOn.cs index 370c2cd80..56195a9c7 100644 --- a/Assets/Scripts/ServerShared/Scenarios/LockOn.cs +++ b/Assets/Scripts/ServerShared/Scenarios/LockOn.cs @@ -21,6 +21,9 @@ public sealed class LockOn : Scenario // The fit's lots are minted at this quality, and the hostile's patrol order is stated, so the lock follows from the // staged situation and not from item draws or the pilot's drawn circuit (ruling scenario-stages-hostile-task). const float StagedQuality = .6f; + // The orbit it patrols: the fourth of the arena's, which carries the hostile across the player's lock envelope (lock + // at about 35 s, closest 880 m; the other twelve, patrolled alone, do not lock it in 600 ticks). + const int PatrolOrbit = 3; public override Galaxy Generate(GalaxyStage stage) => stage.Prelude(DemoTerminus.Cast); @@ -46,6 +49,6 @@ public sealed class LockOn : Scenario stage.Player(fighter, float2(0, 0), facing: float2(0, 1)); var hostile = stage.Place(fighter, HostileAt, facing: normalize(-HostileAt), stance: ScenarioStance.Hostile, piloted: true); - stage.Patrol(hostile, float2(0, 0)); // it patrols the orbit by the player, which brings it into lock range + stage.Patrol(hostile, PatrolOrbit); } } diff --git a/tests/Aetheria.Shared.Tests/ScenarioTests.cs b/tests/Aetheria.Shared.Tests/ScenarioTests.cs index 60b205765..fe457bbd6 100644 --- a/tests/Aetheria.Shared.Tests/ScenarioTests.cs +++ b/tests/Aetheria.Shared.Tests/ScenarioTests.cs @@ -309,11 +309,14 @@ public sealed partial class RunStartTests } CultRecordKey[] Circuit(Zone arena) => ((PatrolOrbitsTask) Assert.Single(arena.Agents).Task).Circuit; + // The orbit keys are fresh per galaxy, so a circuit is compared by the place of each orbit among its zone's. + int[] Places(Zone arena) => Circuit(arena).Select(key => arena.Orbits.Keys.ToList().IndexOf(key)).ToArray(); + var plain = Arena(0); var disturbed = Arena(3); Assert.True(plain.Orbits.Count > 4, "the zone has orbits to choose a circuit among"); - Assert.Equal(4, Circuit(plain).Length); - Assert.Equal(Circuit(plain), Circuit(disturbed)); + Assert.Equal(4, Places(plain).Length); + Assert.Equal(Places(plain), Places(disturbed)); } // A fit that states its quality mints every lot it builds at it, assembly and role fills alike; the same fit without one diff --git a/tests/Aetheria.Shared.Tests/ZProbeTests.cs b/tests/Aetheria.Shared.Tests/ZProbeTests.cs deleted file mode 100644 index 68e366312..000000000 --- a/tests/Aetheria.Shared.Tests/ZProbeTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Xunit; -using static CultMath.math; - -public sealed partial class RunStartTests -{ - [Fact] - public void ZProbeLockOn() - { - var log = new List(); - for (var which = -1; which < 13; which++) - { - var (_, arena, staged, failures) = Launch(new LockOn(), Inputs(() => GalaxySeed)); - var player = staged.Player; - var hostile = staged.Entities.Single(); - var keys = arena.Orbits.Keys.ToList(); - var agent = arena.Agents.Single(); - var task = (PatrolOrbitsTask) agent.Task; - if (which < 0) - foreach (var b in arena.PlanetInstances) - log.Add($"PROBE body {arena.Planets[b.Key].Name} orbit idx {keys.IndexOf(b.Value.Orbit.Data == null ? default : keys.First(k => ReferenceEquals(arena.Orbits[k], b.Value.Orbit)))}"); - else task.Circuit = new[] { keys[which] }; - var minD = 1e9f; - var lockedAt = -1; - for (var tick = 0; tick < 600 && lockedAt < 0; tick++) - { - arena.Update(.1f); - minD = min(minD, length(hostile.Position.xz - player.Position.xz)); - if (player.IncomingPaints.Any(p => p.Mode == PaintMode.Track)) lockedAt = tick; - } - log.Add($"PROBE only-orbit {which} start={arena.Orbits[keys[Math.Max(which, 0)]].Position} locked={lockedAt} minD={minD}"); - } - Assert.Fail(string.Join(Environment.NewLine, log)); - } -}