2026-10-13 20:13:02 +02:00
using System.Collections.Generic ;
2026-10-13 19:13:59 +02:00
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
{
2026-10-13 20:13:02 +02:00
// 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.
2026-10-13 19:13:59 +02:00
[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 ) ) ;
2026-10-13 20:13:02 +02:00
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 ) ;
2026-10-15 01:39:37 +02:00
Assert . False ( hostile . VisibleEnemies . Any ( x = > ReferenceEquals ( x , player ) ) , "the player is out of sight" ) ;
2026-10-13 19:13:59 +02:00
Assert . DoesNotContain ( player . IncomingPaints , paint = > paint . Mode = = PaintMode . Track ) ;
2026-10-13 20:13:02 +02:00
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" ) ;
2026-10-13 19:13:59 +02:00
var lockedAt = - 1 ;
var bearingDot = 0f ;
2026-10-17 02:47:53 +02:00
var stagedDrift = 0f ;
2026-10-13 20:13:02 +02:00
var strengths = new List < float > ( ) ;
for ( var tick = 0 ; tick < 600 & & ( lockedAt < 0 | | tick < lockedAt + 40 ) ; tick + + )
2026-10-13 19:13:59 +02:00
{
arena . Update ( . 1f ) ;
foreach ( var paint in player . IncomingPaints . Where ( paint = > paint . Mode = = PaintMode . Track ) )
{
2026-10-13 20:13:02 +02:00
if ( lockedAt < 0 )
{
lockedAt = tick ;
2026-10-17 02:47:53 +02:00
// 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 ) ) ) ;
2026-10-13 20:13:02 +02:00
}
strengths . Add ( paint . Strength ) ;
2026-10-13 19:13:59 +02:00
}
}
Assert . True ( lockedAt > = 0 , "the hostile never locked the player within 600 ticks of .1 s" ) ;
2026-10-17 02:47:53 +02:00
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)" ) ;
2026-10-13 20:13:02 +02:00
Assert . True ( strengths . First ( ) < strengths . Last ( ) , $"the lock tightens: Strength went {strengths.First()} to {strengths.Last()}" ) ;
2026-10-13 19:13:59 +02:00
}
}