CounterDroneBackend/test/unit/CounterDrone.Core.Tests/DetectionCalculatorTests.cs
tian 11f8cb2c79 feat: detection-driven planning (v0.6.0)
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.
2026-06-15 12:52:57 +08:00

171 lines
6.9 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.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 → 有效 = 10000min(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);
}
}
}