CounterDroneBackend/test/unit/CounterDrone.Core.Tests/DroneEntityTests.cs
tian a9339c36e3 feat: 编队2D模型 — LateralSpacing/LongitudinalSpacing + DroneEntity 纵向偏移
- 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
2026-06-13 13:20:18 +08:00

159 lines
6.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, "蜂群应有位置差异");
}
}
}