- 3D球冠探测: IsInCoverage, DetectionEntity, Tick 第5步扫描 - 探测融合: EarliestDetection 采样法, break 修复 - planner 诊断: HasInterceptWindow 拦截窗口检查 - TryGenerateFireEvents 返回拒绝原因 - Summary 含失败原因+建议值(探测范围/弧长) - PlanningFailed 事件: 引擎在规划失败时发出事件 - 硬编码消除: Phase2Duration→AmmunitionSpec ExpansionFactor/TimingSafetyMargin→PlannerConfig 速度从 waypoint.Speed 读取(不用 TypicalSpeed) - TestData 改为从 seeded 数据库读取(不再内嵌 JSON) - 默认数据加 3D球冠参数,巡航导弹速度300→200 空基航路10km→20km, 位置调整 - 222 测试全通过
87 lines
4.5 KiB
C#
87 lines
4.5 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text.Json;
|
||
using System.Text.Json.Serialization;
|
||
using CounterDrone.Core.Models;
|
||
|
||
namespace CounterDrone.Core.Algorithms
|
||
{
|
||
/// <summary>防御规划器配置 — 全局策略参数,从 planner_config.json 加载。
|
||
/// 代码零默认值,所有字段必须从配置文件读取;文件缺失或字段缺失即抛异常。</summary>
|
||
public class PlannerConfig
|
||
{
|
||
/// <summary>云团重叠比例(0=相切,0.2=重叠 20%)。间距 = 2R × (1 − 重叠比例)。</summary>
|
||
public float CloudOverlapRatio { get; set; }
|
||
|
||
/// <summary>临界方案概率阈值(DeriveCritical 用)</summary>
|
||
public float CriticalProbabilityThreshold { get; set; }
|
||
|
||
/// <summary>拦截概率上限(封顶值)</summary>
|
||
public float MaxInterceptProbability { get; set; }
|
||
|
||
/// <summary>威胁类型系数表(TargetType → 系数)</summary>
|
||
public Dictionary<TargetType, float> TypeCoefficient { get; set; } = new();
|
||
|
||
/// <summary>弹药匹配表(PowerType → AerosolType)</summary>
|
||
public Dictionary<PowerType, AerosolType> AmmoMatch { get; set; } = new();
|
||
|
||
/// <summary>无探测设备时的默认探测精度 m(回退值,上帝视角但有标称误差)</summary>
|
||
public float DefaultDetectionAccuracy { get; set; }
|
||
|
||
/// <summary>云团有效膨胀系数(0~1)。planner 取 Phase 2 膨胀时间的此比例作为有效云团年龄。默认 0.9</summary>
|
||
public float ExpansionFactor { get; set; } = 0.9f;
|
||
|
||
/// <summary>拦截窗口安全余量(s)。计算所需探测弧长时在膨胀时间基础上额外预留。默认 1</summary>
|
||
public float TimingSafetyMargin { get; set; } = 1f;
|
||
|
||
private const string ConfigFileName = "planner_config.json";
|
||
|
||
/// <summary>从 dataRoot 加载配置。文件缺失或字段非法即抛异常。</summary>
|
||
public static PlannerConfig Load(string dataRoot)
|
||
{
|
||
if (string.IsNullOrEmpty(dataRoot))
|
||
throw new ArgumentException("dataRoot 不能为空", nameof(dataRoot));
|
||
string path = Path.Combine(dataRoot, ConfigFileName);
|
||
if (!File.Exists(path))
|
||
throw new FileNotFoundException($"planner 配置文件不存在: {path}");
|
||
|
||
string json = File.ReadAllText(path);
|
||
var config = JsonSerializer.Deserialize<PlannerConfig>(json, JsonOptions)
|
||
?? throw new InvalidDataException($"planner 配置解析失败: {path}");
|
||
config.Validate();
|
||
return config;
|
||
}
|
||
|
||
/// <summary>从 IPathProvider 加载(便捷重载)</summary>
|
||
public static PlannerConfig Load(IPathProvider paths)
|
||
=> Load(paths?.GetDataRoot() ?? throw new ArgumentNullException(nameof(paths)));
|
||
|
||
private void Validate()
|
||
{
|
||
if (CloudOverlapRatio < 0f || CloudOverlapRatio >= 1f)
|
||
throw new InvalidDataException($"CloudOverlapRatio 必须在 [0, 1),实际 {CloudOverlapRatio}");
|
||
if (CriticalProbabilityThreshold <= 0f || CriticalProbabilityThreshold >= 1f)
|
||
throw new InvalidDataException($"CriticalProbabilityThreshold 必须在 (0, 1),实际 {CriticalProbabilityThreshold}");
|
||
if (MaxInterceptProbability <= 0f || MaxInterceptProbability > 1f)
|
||
throw new InvalidDataException($"MaxInterceptProbability 必须在 (0, 1],实际 {MaxInterceptProbability}");
|
||
if (TypeCoefficient == null || TypeCoefficient.Count == 0)
|
||
throw new InvalidDataException("TypeCoefficient 不能为空");
|
||
if (AmmoMatch == null || AmmoMatch.Count == 0)
|
||
throw new InvalidDataException("AmmoMatch 不能为空");
|
||
if (DefaultDetectionAccuracy < 0f)
|
||
throw new InvalidDataException($"DefaultDetectionAccuracy 必须 >= 0,实际 {DefaultDetectionAccuracy}");
|
||
if (ExpansionFactor <= 0f || ExpansionFactor > 1f)
|
||
throw new InvalidDataException($"ExpansionFactor 必须在 (0, 1],实际 {ExpansionFactor}");
|
||
if (TimingSafetyMargin < 0f)
|
||
throw new InvalidDataException($"TimingSafetyMargin 必须 >= 0,实际 {TimingSafetyMargin}");
|
||
}
|
||
|
||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||
{
|
||
Converters = { new JsonStringEnumConverter() },
|
||
PropertyNameCaseInsensitive = true,
|
||
};
|
||
}
|
||
}
|