- RoutePlan: FormationSpacing→LateralSpacing, 新增LateralCount/LongitudinalCount/LongitudinalSpacing - DroneEntity: 2D编队(latIdx/longIdx), 横向Y展开+纵向X落后 - DefaultFormations: 6种编队模板 - DroneEntityTests: 11个(含2D编队/2×2方队/蜂群) - Planner: yLanes=ceil(formationWidth/cloudDiam), totalNeeded=single×lanes
159 lines
6.6 KiB
C#
159 lines
6.6 KiB
C#
using System.Collections.Generic;
|
||
using CounterDrone.Core.Models;
|
||
using CounterDrone.Core.Simulation;
|
||
using Xunit;
|
||
|
||
namespace CounterDrone.Core.Tests
|
||
{
|
||
public class DroneEntityTests
|
||
{
|
||
private TargetConfig CreateConfig(float speed = 120, float altitude = 300)
|
||
{
|
||
return new TargetConfig
|
||
{
|
||
TargetType = (int)TargetType.Piston,
|
||
PowerType = (int)PowerType.Piston,
|
||
TypicalSpeed = speed,
|
||
TypicalAltitude = altitude,
|
||
Wingspan = 2.5,
|
||
Quantity = 1,
|
||
};
|
||
}
|
||
|
||
private List<Waypoint> CreateRoute(float fromX, float toX, float alt, float speed)
|
||
{
|
||
return new List<Waypoint>
|
||
{
|
||
new Waypoint { PosX = fromX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
||
new Waypoint { PosX = toX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed },
|
||
};
|
||
}
|
||
|
||
[Fact]
|
||
public void InitialPosition_IsFirstWaypoint()
|
||
{
|
||
var drone = new DroneEntity("d1", "g1", CreateConfig(),
|
||
CreateRoute(100, 500, 300, 120), 0, 50, 0, 0, FormationMode.Single);
|
||
Assert.Equal(100, drone.PosX, 1);
|
||
Assert.Equal(300, drone.PosY, 1);
|
||
}
|
||
|
||
[Fact]
|
||
public void Update_MovesTowardTarget()
|
||
{
|
||
var drone = new DroneEntity("d1", "g1", CreateConfig(),
|
||
CreateRoute(0, 1000, 300, 120), 0, 50, 0, 0, FormationMode.Single);
|
||
float prevX = drone.PosX;
|
||
drone.Update(1f, 0, WindDirection.N);
|
||
Assert.True(drone.PosX > prevX);
|
||
}
|
||
|
||
[Fact]
|
||
public void Update_ReachesTarget_StatusChanges()
|
||
{
|
||
var drone = new DroneEntity("d1", "g1", CreateConfig(360),
|
||
CreateRoute(0, 100, 300, 360), 0, 50, 0, 0, FormationMode.Single);
|
||
drone.Update(2f, 0, WindDirection.N);
|
||
Assert.Equal(DroneStatus.ReachedTarget, drone.Status);
|
||
}
|
||
|
||
[Fact]
|
||
public void Damage_ReducesHp()
|
||
{
|
||
var drone = new DroneEntity("d1", "g1", CreateConfig(),
|
||
CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||
drone.ApplyDamage(0.4f);
|
||
Assert.Equal(0.6f, drone.Hp, 0.01f);
|
||
Assert.Equal(DroneStatus.Flying, drone.Status);
|
||
}
|
||
|
||
[Fact]
|
||
public void Damage_ExceedingHp_Destroys()
|
||
{
|
||
var drone = new DroneEntity("d1", "g1", CreateConfig(),
|
||
CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||
drone.ApplyDamage(1.5f);
|
||
Assert.True(drone.Hp <= 0);
|
||
Assert.Equal(DroneStatus.Destroyed, drone.Status);
|
||
}
|
||
|
||
[Fact]
|
||
public void Wind_AffectsPosition()
|
||
{
|
||
var drone = new DroneEntity("d1", "g1", CreateConfig(),
|
||
CreateRoute(0, 1000, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||
float xBefore = drone.PosX;
|
||
drone.Update(1f, 20f, WindDirection.E);
|
||
Assert.True(drone.PosX > xBefore + 15f, "东风 20m/s 应增加 X");
|
||
}
|
||
|
||
// ═══════════════════════════════════════
|
||
// 编队位置
|
||
// ═══════════════════════════════════════
|
||
|
||
[Fact]
|
||
public void Formation_Lateral_YOffsetsDiffer()
|
||
{
|
||
var route = CreateRoute(0, 100, 300, 60);
|
||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 50, 0, 0, FormationMode.Formation);
|
||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 50, 0, 0, FormationMode.Formation);
|
||
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 2, 50, 0, 0, FormationMode.Formation);
|
||
// 横向展开:Y 递增
|
||
Assert.Equal(0, d0.FormationOffsetY);
|
||
Assert.Equal(50, d1.FormationOffsetY);
|
||
Assert.Equal(100, d2.FormationOffsetY);
|
||
}
|
||
|
||
[Fact]
|
||
public void Formation_Longitudinal_XOffsetsDiffer()
|
||
{
|
||
var route = CreateRoute(0, 100, 300, 60);
|
||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 0, 0, 100, FormationMode.Formation);
|
||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 0, 0, 1, 100, FormationMode.Formation);
|
||
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 0, 0, 2, 100, FormationMode.Formation);
|
||
// 纵向串列:X 落后递增
|
||
Assert.Equal(0, d0.FormationOffsetX);
|
||
Assert.Equal(-100, d1.FormationOffsetX);
|
||
Assert.Equal(-200, d2.FormationOffsetX);
|
||
}
|
||
|
||
[Fact]
|
||
public void Formation_Mixed_2x2()
|
||
{
|
||
var route = CreateRoute(0, 100, 300, 60);
|
||
// 2横 × 2纵: index 0=(0,0), 1=(1,0), 2=(0,1), 3=(1,1)
|
||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route,
|
||
0, 50, 0, 100, FormationMode.Formation); // lat=0, long=0
|
||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route,
|
||
1, 50, 0, 100, FormationMode.Formation); // lat=1, long=0
|
||
var d2 = new DroneEntity("d2", "g1", CreateConfig(), route,
|
||
0, 50, 1, 100, FormationMode.Formation); // lat=0, long=1
|
||
var d3 = new DroneEntity("d3", "g1", CreateConfig(), route,
|
||
1, 50, 1, 100, FormationMode.Formation); // lat=1, long=1
|
||
|
||
Assert.Equal(0, d0.FormationOffsetY); Assert.Equal(0, d0.FormationOffsetX);
|
||
Assert.Equal(50, d1.FormationOffsetY); Assert.Equal(0, d1.FormationOffsetX);
|
||
Assert.Equal(0, d2.FormationOffsetY); Assert.Equal(-100, d2.FormationOffsetX);
|
||
Assert.Equal(50, d3.FormationOffsetY); Assert.Equal(-100, d3.FormationOffsetX);
|
||
}
|
||
|
||
[Fact]
|
||
public void Formation_Single_HasNoOffset()
|
||
{
|
||
var drone = new DroneEntity("d1", "g1", CreateConfig(),
|
||
CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single);
|
||
Assert.Equal(0, drone.FormationOffsetX);
|
||
Assert.Equal(0, drone.FormationOffsetY);
|
||
}
|
||
|
||
[Fact]
|
||
public void Swarm_ProducesRandomOffsets()
|
||
{
|
||
var route = CreateRoute(0, 100, 300, 60);
|
||
var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 30, 0, 0, FormationMode.Swarm);
|
||
var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 30, 0, 0, FormationMode.Swarm);
|
||
Assert.True(d0.PosX != d1.PosX || d0.PosY != d1.PosY, "蜂群应有位置差异");
|
||
}
|
||
}
|
||
}
|