CounterDroneBackend/src/CounterDrone.Core/Algorithms/PlannerConfig.cs
tian d8470bad30 Phase 10: 探测实时链路开发 + planner 诊断 + 硬编码消除
- 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 测试全通过
2026-06-16 14:10:23 +08:00

87 lines
4.5 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;
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,
};
}
}