Planner no longer has god-view. It assumes threats enter from detection boundary (unified info network earliest detection point), not route start. Core: DetectionCalculator (EO attenuated by Visibility, radar/IR unaffected; earliest detection via segment-circle intersection). EquipmentDeployment: drop DetectionRadius, add RadarRange/EORange/IRange/DetectionAccuracy. FireUnit detection fields activated in BuildFireUnits. IDefensePlanner.Plan adds 4th param detectionSources. Engine: BuildDetectionSources merges standalone detectors + fire-unit self-detection into unified list, passed to planner. Fix: Solve robustness when GenerateFireEventsAt returns empty (detection boundary too late to intercept). Tests 193 to 208 (+15). 0 warnings.
171 lines
6.9 KiB
C#
171 lines
6.9 KiB
C#
using System.Collections.Generic;
|
||
using CounterDrone.Core.Algorithms;
|
||
using CounterDrone.Core.Models;
|
||
using Xunit;
|
||
|
||
namespace CounterDrone.Core.Tests
|
||
{
|
||
public class DetectionCalculatorTests
|
||
{
|
||
private static List<Waypoint> Line(float x0, float z0, float x1, float z1)
|
||
=> new()
|
||
{
|
||
new Waypoint { PosX = x0, PosY = 500, PosZ = z0 },
|
||
new Waypoint { PosX = x1, PosY = 500, PosZ = z1 },
|
||
};
|
||
|
||
// ═══════════════════════════════════════
|
||
// EffectiveRange — 天气衰减
|
||
// ═══════════════════════════════════════
|
||
|
||
[Fact]
|
||
public void EffectiveRange_EO_VisibilityBelowBase_ScalesDown()
|
||
{
|
||
// 光电基准 10000m,能见度 5000m → 有效 = 10000 × (5000/10000) = 5000
|
||
float r = DetectionCalculator.EffectiveRange(0, 10000, 0, 5000);
|
||
Assert.Equal(5000f, r, 0);
|
||
}
|
||
|
||
[Fact]
|
||
public void EffectiveRange_EO_VisibilityAboveBase_NoEffect()
|
||
{
|
||
// 光电基准 10000m,能见度 15000m → 有效 = 10000(min(1, 1.5)=1)
|
||
float r = DetectionCalculator.EffectiveRange(0, 10000, 0, 15000);
|
||
Assert.Equal(10000f, r, 0);
|
||
}
|
||
|
||
[Fact]
|
||
public void EffectiveRange_Radar_UnaffectedByVisibility()
|
||
{
|
||
// 雷达 15000m,能见度只有 1000m,雷达不受影响
|
||
float r = DetectionCalculator.EffectiveRange(15000, 0, 0, 1000);
|
||
Assert.Equal(15000f, r, 0);
|
||
}
|
||
|
||
[Fact]
|
||
public void EffectiveRange_IR_UnaffectedByVisibility()
|
||
{
|
||
float r = DetectionCalculator.EffectiveRange(0, 0, 8000, 500);
|
||
Assert.Equal(8000f, r, 0);
|
||
}
|
||
|
||
[Fact]
|
||
public void EffectiveRange_TakesMaxOfMethods()
|
||
{
|
||
// 雷达 12000 + 光电 6000(能见度 3000 → 3000) + 红外 8000 → max=12000
|
||
float r = DetectionCalculator.EffectiveRange(12000, 6000, 8000, 3000);
|
||
Assert.Equal(12000f, r, 0);
|
||
}
|
||
|
||
[Fact]
|
||
public void EffectiveRange_EO_StrongestWhenGoodWeather()
|
||
{
|
||
// 雷达 8000 + 光电 15000(能见度 15000) + 红外 10000 → max=15000(光电胜出)
|
||
float r = DetectionCalculator.EffectiveRange(8000, 15000, 10000, 15000);
|
||
Assert.Equal(15000f, r, 0);
|
||
}
|
||
|
||
// ═══════════════════════════════════════
|
||
// EarliestDetection — 最早探测点
|
||
// ═══════════════════════════════════════
|
||
|
||
[Fact]
|
||
public void EarliestDetection_DroneEntersCircle_ReturnsEntryArc()
|
||
{
|
||
// 航路 X:0→10000 Z:0,探测源在 X=7000,半径 5000
|
||
// 探测圆边界在 X=2000 和 X=12000,无人机从 X=0 飞向 10000,在 X=2000 进入
|
||
// 弧长 = 2000
|
||
var route = Line(0, 0, 10000, 0);
|
||
var src = new DetectionSource
|
||
{
|
||
Position = new Vector3(7000, 0, 0),
|
||
RadarRange = 5000, Accuracy = 100,
|
||
};
|
||
var (arc, acc) = DetectionCalculator.EarliestDetection(route, new() { src }, 10000);
|
||
Assert.InRange(arc, 1950f, 2050f); // X=2000 进入圆
|
||
Assert.Equal(100f, acc, 0);
|
||
}
|
||
|
||
[Fact]
|
||
public void EarliestDetection_DroneStartsInside_ReturnsZero()
|
||
{
|
||
// 航路起点已在探测圆内
|
||
var route = Line(6000, 0, 10000, 0);
|
||
var src = new DetectionSource
|
||
{
|
||
Position = new Vector3(5000, 0, 0),
|
||
RadarRange = 5000, Accuracy = 50,
|
||
};
|
||
var (arc, _) = DetectionCalculator.EarliestDetection(route, new() { src }, 10000);
|
||
Assert.Equal(0f, arc, 0);
|
||
}
|
||
|
||
[Fact]
|
||
public void EarliestDetection_NeverEnters_ReturnsMaxValue()
|
||
{
|
||
// 航路离探测源很远
|
||
var route = Line(0, 50000, 10000, 50000);
|
||
var src = new DetectionSource
|
||
{
|
||
Position = new Vector3(5000, 0, 0),
|
||
RadarRange = 1000,
|
||
};
|
||
var (arc, _) = DetectionCalculator.EarliestDetection(route, new() { src }, 10000);
|
||
Assert.Equal(float.MaxValue, arc);
|
||
}
|
||
|
||
[Fact]
|
||
public void EarliestDetection_MultipleSources_TakesEarliest()
|
||
{
|
||
// 两个探测源:源A 探测圆边界 X=8000,源B 边界 X=3000
|
||
// 无人机从 X=0 飞,最早在 X=3000 被源B 发现
|
||
var route = Line(0, 0, 20000, 0);
|
||
var sources = new List<DetectionSource>
|
||
{
|
||
new() { Position = new Vector3(12000, 0, 0), RadarRange = 4000, Accuracy = 200 }, // 边界 X=8000
|
||
new() { Position = new Vector3(7000, 0, 0), RadarRange = 4000, Accuracy = 80 }, // 边界 X=3000
|
||
};
|
||
var (arc, acc) = DetectionCalculator.EarliestDetection(route, sources, 10000);
|
||
Assert.InRange(arc, 2950f, 3050f);
|
||
Assert.Equal(80f, acc, 0); // 用源B的精度
|
||
}
|
||
|
||
[Fact]
|
||
public void EarliestDetection_NoSources_ReturnsZeroArc()
|
||
{
|
||
// 无探测设备,弧长=0(上帝视角,从起点算)
|
||
var route = Line(0, 0, 10000, 0);
|
||
var (arc, acc) = DetectionCalculator.EarliestDetection(route, new List<DetectionSource>(), 10000);
|
||
Assert.Equal(0f, arc, 0);
|
||
Assert.Equal(float.MaxValue, acc); // 无精度信息
|
||
}
|
||
|
||
[Fact]
|
||
public void EarliestDetection_EO_WeatherShortensDetection()
|
||
{
|
||
// 光电 10000m,能见度 4000 → 有效 4000m
|
||
// 探测源在 X=9000,有效圆边界 X=5000 和 X=13000
|
||
// 无人机从 X=0 飞,在 X=5000 进入
|
||
var route = Line(0, 0, 15000, 0);
|
||
var src = new DetectionSource
|
||
{
|
||
Position = new Vector3(9000, 0, 0),
|
||
EORange = 10000, Accuracy = 100,
|
||
};
|
||
var (arc, _) = DetectionCalculator.EarliestDetection(route, new() { src }, 4000);
|
||
Assert.InRange(arc, 4950f, 5050f);
|
||
}
|
||
|
||
// ═══════════════════════════════════════
|
||
// SpreadRadius — 精度换算散布
|
||
// ═══════════════════════════════════════
|
||
|
||
[Fact]
|
||
public void SpreadRadius_EqualsAccuracy()
|
||
{
|
||
Assert.Equal(100f, DetectionCalculator.SpreadRadius(100f), 0);
|
||
Assert.Equal(0f, DetectionCalculator.SpreadRadius(0f), 0);
|
||
}
|
||
}
|
||
}
|