Aetheria/tests/Aetheria.Shared.Tests/LockOnScenarioTests.cs
Meta Vens 82362da9d5 LockOnSituation: the warning is measured against the hostile's live bearing at the first lock, limit 0.5 deg
The staged line stopped being the hostile's line once consumable-ai r3 gave it a flanking approach. The message records the drift from the staged line. LockOn.cs's 5-degree staged-match claim is removed. Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:47:53 +02:00

60 lines
3.5 KiB
C#

using System.Collections.Generic;
using System.Linq;
using Xunit;
using static CultMath.math;
// The arena the operator flies to check the incoming-lock warning (map: Scenarios for operator checks, SC5 and SC14). The
// test launches the scenario the way New Game does and steps what it staged.
public sealed partial class RunStartTests
{
// Lock On: the hostile starts piloted, facing the player, beyond its own sight of the player, and the player has no
// incoming paint. Stepped at .1 s, it closes, finds the player and locks: a Track paint reaches the player bearing
// toward the hostile, and its Strength rises from nothing as the lock tightens.
[Fact]
public void LockOnSituation()
{
var (_, arena, staged, failures) = Launch(new LockOn(), Inputs(() => GalaxySeed));
Assert.True(failures.Count == 0, string.Join("; ", failures));
var player = staged.Player;
var hostile = Assert.Single(staged.Entities);
Assert.True(player.IsHostileTo(hostile) && hostile.IsHostileTo(player), "the hostile is hostile both ways");
Assert.Equal(1, AgentsOf(arena, hostile));
var toPlayer = normalize(player.Position.xz - hostile.Position.xz);
Assert.True(dot(normalize(hostile.Direction), toPlayer) >= cos(radians(5f)), "the hostile faces the player");
// The first real sensing tick: the hostile holds no track of the player, and the sim's own passive sensing, asked for
// the player's signature at the staged place, settles below the detection threshold: the staging is out of its sight.
arena.Update(.1f);
Assert.False(hostile.VisibleEnemies.Any(x => ReferenceEquals(x, player)), "the player is out of sight");
Assert.DoesNotContain(player.IncomingPaints, paint => paint.Mode == PaintMode.Track);
Assert.True(player.Visibility > 0f, "the player shows a signature to be seen by");
var threshold = player.ItemManager.GameplaySettings.TargetDetectionInfoThreshold;
var settled = hostile.PassiveInfo(player.Position.xz, player.Visibility);
Assert.True(settled <= threshold, $"the hostile would settle at {settled} on the player, above the detection threshold {threshold}: staged inside its sight");
var lockedAt = -1;
var bearingDot = 0f;
var stagedDrift = 0f;
var strengths = new List<float>();
for (var tick = 0; tick < 600 && (lockedAt < 0 || tick < lockedAt + 40); tick++)
{
arena.Update(.1f);
foreach (var paint in player.IncomingPaints.Where(paint => paint.Mode == PaintMode.Track))
{
if (lockedAt < 0)
{
lockedAt = tick;
// The warning points at the hostile where it is at the lock; the hostile has left the staged line by then.
var live = normalize(hostile.Position.xz - player.Position.xz);
bearingDot = dot(normalize(paint.Bearing), live);
stagedDrift = degrees(acos(clamp(dot(live, -toPlayer), -1f, 1f)));
}
strengths.Add(paint.Strength);
}
}
Assert.True(lockedAt >= 0, "the hostile never locked the player within 600 ticks of .1 s");
Assert.True(bearingDot >= cos(radians(.5f)), $"the warning points at the hostile where it is (the hostile is {stagedDrift} deg off its staged line at the lock)");
Assert.True(strengths.First() < strengths.Last(), $"the lock tightens: Strength went {strengths.First()} to {strengths.Last()}");
}
}