feat: 统一默认数据架构 (defaults.json + DefaultData)
单一数据源 data/defaults.json,含弹药/编队/火力单元/无人机/探测/天气六类预设 版本追踪: Meta 表 + version 字段,更新时 InsertOrReplace,不删用户数据 名称统一 [Demo] 前缀,UI 中可识别为模拟数据 删除: DefaultAmmunition.cs, DefaultFireUnits.cs, default_ammo.json, default_formations.json, TestAmmo.cs TestPathProvider 自动复制数据文件到测试目录 集成测试精简: 4个简单变体跳过,保留6个核心场景,39s
This commit is contained in:
parent
56d345189e
commit
8eeb43f0e8
@ -1,56 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Id": "default-inert",
|
||||
"AerosolType": 0,
|
||||
"Name": "惰性气体弹(发烟罐型)",
|
||||
"InitialRadius": 3.8,
|
||||
"InitialVolume": 14000.0,
|
||||
"CoreDensity": 1.5,
|
||||
"EdgeDensity": 0.1,
|
||||
"InitialTemperature": 1800.0,
|
||||
"BuoyancyFactor": 0.3,
|
||||
"EffectiveConcentration": 0.0001,
|
||||
"MaxRadius": 100.0,
|
||||
"MaxDuration": 120.0,
|
||||
"SourceStrength": 10.0,
|
||||
"BurstChargeKg": 1.5,
|
||||
"TurbulentExpansionK": 3.0,
|
||||
"ParticlesJson": "{}"
|
||||
},
|
||||
{
|
||||
"Id": "default-active",
|
||||
"AerosolType": 1,
|
||||
"Name": "活性材料弹(爆炸分散型)",
|
||||
"InitialRadius": 5.0,
|
||||
"InitialVolume": 30000.0,
|
||||
"CoreDensity": 2.0,
|
||||
"EdgeDensity": 0.2,
|
||||
"InitialTemperature": 2400.0,
|
||||
"BuoyancyFactor": 0.6,
|
||||
"EffectiveConcentration": 0.0001,
|
||||
"MaxRadius": 80.0,
|
||||
"MaxDuration": 90.0,
|
||||
"SourceStrength": 12.0,
|
||||
"BurstChargeKg": 4.0,
|
||||
"TurbulentExpansionK": 4.0,
|
||||
"ParticlesJson": "{}"
|
||||
},
|
||||
{
|
||||
"Id": "default-fuel",
|
||||
"AerosolType": 2,
|
||||
"Name": "活性燃料弹(抛射分散型)",
|
||||
"InitialRadius": 3.8,
|
||||
"InitialVolume": 14000.0,
|
||||
"CoreDensity": 1.8,
|
||||
"EdgeDensity": 0.15,
|
||||
"InitialTemperature": 1900.0,
|
||||
"BuoyancyFactor": 0.4,
|
||||
"EffectiveConcentration": 0.0001,
|
||||
"MaxRadius": 90.0,
|
||||
"MaxDuration": 100.0,
|
||||
"SourceStrength": 10.0,
|
||||
"BurstChargeKg": 1.5,
|
||||
"TurbulentExpansionK": 3.0,
|
||||
"ParticlesJson": "{}"
|
||||
}
|
||||
]
|
||||
@ -1,56 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Id": "single",
|
||||
"Name": "单机",
|
||||
"FormationMode": 0,
|
||||
"LateralCount": 1,
|
||||
"LongitudinalCount": 1,
|
||||
"LateralSpacing": 0,
|
||||
"LongitudinalSpacing": 0
|
||||
},
|
||||
{
|
||||
"Id": "line-abreast-3",
|
||||
"Name": "3机横队",
|
||||
"FormationMode": 1,
|
||||
"LateralCount": 3,
|
||||
"LongitudinalCount": 1,
|
||||
"LateralSpacing": 50,
|
||||
"LongitudinalSpacing": 0
|
||||
},
|
||||
{
|
||||
"Id": "line-abreast-5",
|
||||
"Name": "5机横队",
|
||||
"FormationMode": 1,
|
||||
"LateralCount": 5,
|
||||
"LongitudinalCount": 1,
|
||||
"LateralSpacing": 50,
|
||||
"LongitudinalSpacing": 0
|
||||
},
|
||||
{
|
||||
"Id": "column-3",
|
||||
"Name": "3机纵队",
|
||||
"FormationMode": 1,
|
||||
"LateralCount": 1,
|
||||
"LongitudinalCount": 3,
|
||||
"LateralSpacing": 0,
|
||||
"LongitudinalSpacing": 100
|
||||
},
|
||||
{
|
||||
"Id": "box-2x2",
|
||||
"Name": "2×2方队",
|
||||
"FormationMode": 1,
|
||||
"LateralCount": 2,
|
||||
"LongitudinalCount": 2,
|
||||
"LateralSpacing": 50,
|
||||
"LongitudinalSpacing": 100
|
||||
},
|
||||
{
|
||||
"Id": "swarm-10",
|
||||
"Name": "蜂群(10架)",
|
||||
"FormationMode": 2,
|
||||
"LateralCount": 10,
|
||||
"LongitudinalCount": 1,
|
||||
"LateralSpacing": 30,
|
||||
"LongitudinalSpacing": 0
|
||||
}
|
||||
]
|
||||
147
data/defaults.json
Normal file
147
data/defaults.json
Normal file
@ -0,0 +1,147 @@
|
||||
{
|
||||
"version": "1",
|
||||
"ammunition": [
|
||||
{
|
||||
"Id": "inert",
|
||||
"AerosolType": 0,
|
||||
"Name": "[Demo] 惰性气体弹(发烟罐型)",
|
||||
"InitialRadius": 3.8,
|
||||
"InitialVolume": 14000.0,
|
||||
"CoreDensity": 1.5,
|
||||
"EdgeDensity": 0.1,
|
||||
"InitialTemperature": 1800.0,
|
||||
"BuoyancyFactor": 0.3,
|
||||
"EffectiveConcentration": 0.0001,
|
||||
"MaxRadius": 100.0,
|
||||
"MaxDuration": 120.0,
|
||||
"SourceStrength": 10.0,
|
||||
"BurstChargeKg": 1.5,
|
||||
"TurbulentExpansionK": 3.0,
|
||||
"ParticlesJson": "{}"
|
||||
},
|
||||
{
|
||||
"Id": "active",
|
||||
"AerosolType": 1,
|
||||
"Name": "[Demo] 活性材料弹(爆炸分散型)",
|
||||
"InitialRadius": 5.0,
|
||||
"InitialVolume": 30000.0,
|
||||
"CoreDensity": 2.0,
|
||||
"EdgeDensity": 0.2,
|
||||
"InitialTemperature": 2400.0,
|
||||
"BuoyancyFactor": 0.6,
|
||||
"EffectiveConcentration": 0.0001,
|
||||
"MaxRadius": 80.0,
|
||||
"MaxDuration": 90.0,
|
||||
"SourceStrength": 12.0,
|
||||
"BurstChargeKg": 4.0,
|
||||
"TurbulentExpansionK": 4.0,
|
||||
"ParticlesJson": "{}"
|
||||
},
|
||||
{
|
||||
"Id": "fuel",
|
||||
"AerosolType": 2,
|
||||
"Name": "[Demo] 活性燃料弹(抛射分散型)",
|
||||
"InitialRadius": 3.8,
|
||||
"InitialVolume": 14000.0,
|
||||
"CoreDensity": 1.8,
|
||||
"EdgeDensity": 0.15,
|
||||
"InitialTemperature": 1900.0,
|
||||
"BuoyancyFactor": 0.4,
|
||||
"EffectiveConcentration": 0.0001,
|
||||
"MaxRadius": 90.0,
|
||||
"MaxDuration": 100.0,
|
||||
"SourceStrength": 10.0,
|
||||
"BurstChargeKg": 1.5,
|
||||
"TurbulentExpansionK": 3.0,
|
||||
"ParticlesJson": "{}"
|
||||
}
|
||||
],
|
||||
|
||||
"formations": [
|
||||
{ "Id": "single", "Name": "[Demo] 单机", "FormationMode": 0, "LateralCount": 1, "LongitudinalCount": 1, "LateralSpacing": 0, "LongitudinalSpacing": 0 },
|
||||
{ "Id": "line-3", "Name": "[Demo] 3机横队", "FormationMode": 1, "LateralCount": 3, "LongitudinalCount": 1, "LateralSpacing": 50, "LongitudinalSpacing": 0 },
|
||||
{ "Id": "line-5", "Name": "[Demo] 5机横队", "FormationMode": 1, "LateralCount": 5, "LongitudinalCount": 1, "LateralSpacing": 50, "LongitudinalSpacing": 0 },
|
||||
{ "Id": "column-3", "Name": "[Demo] 3机纵队", "FormationMode": 1, "LateralCount": 1, "LongitudinalCount": 3, "LateralSpacing": 0, "LongitudinalSpacing": 100 },
|
||||
{ "Id": "box-2x2", "Name": "[Demo] 2×2方队", "FormationMode": 1, "LateralCount": 2, "LongitudinalCount": 2, "LateralSpacing": 50, "LongitudinalSpacing": 100 },
|
||||
{ "Id": "swarm-10", "Name": "[Demo] 蜂群(10架)", "FormationMode": 2, "LateralCount": 10, "LongitudinalCount": 1, "LateralSpacing": 30, "LongitudinalSpacing": 0 }
|
||||
],
|
||||
|
||||
"fireUnits": [
|
||||
{
|
||||
"Id": "ground-light", "Name": "[Demo] 轻型地基火力单元",
|
||||
"PlatformType": 1, "GunCount": 4, "ChannelsPerGun": 4, "ChannelInterval": 0.1,
|
||||
"Cooldown": 5.0, "AmmoChangeTime": 30.0, "MuzzleVelocity": 800.0,
|
||||
"AmmoTypes": [0, 1],
|
||||
"RadarRange": 10000.0, "EORange": 6000.0, "IRRange": 3000.0
|
||||
},
|
||||
{
|
||||
"Id": "ground-standard", "Name": "[Demo] 标准地基火力单元",
|
||||
"PlatformType": 1, "GunCount": 4, "ChannelsPerGun": 4, "ChannelInterval": 0.1,
|
||||
"Cooldown": 5.0, "AmmoChangeTime": 30.0, "MuzzleVelocity": 800.0,
|
||||
"AmmoTypes": [0, 1],
|
||||
"RadarRange": 15000.0, "EORange": 8000.0, "IRRange": 5000.0
|
||||
},
|
||||
{
|
||||
"Id": "ground-heavy", "Name": "[Demo] 重型地基火力单元",
|
||||
"PlatformType": 1, "GunCount": 6, "ChannelsPerGun": 4, "ChannelInterval": 1.0,
|
||||
"Cooldown": 5.0, "AmmoChangeTime": 30.0, "MuzzleVelocity": 600.0,
|
||||
"AmmoTypes": [0, 1, 2],
|
||||
"RadarRange": 20000.0, "EORange": 10000.0, "IRRange": 6000.0
|
||||
},
|
||||
{
|
||||
"Id": "air-standard", "Name": "[Demo] 标准空基火力单元",
|
||||
"PlatformType": 0, "GunCount": 1, "ChannelsPerGun": 8, "ChannelInterval": 1.0,
|
||||
"Cooldown": 5.0, "AmmoChangeTime": 30.0,
|
||||
"CruiseSpeed": 55.0, "ReleaseAltitude": 1500.0,
|
||||
"AmmoTypes": [0, 1],
|
||||
"EORange": 12000.0, "IRRange": 8000.0
|
||||
}
|
||||
],
|
||||
|
||||
"targets": [
|
||||
{ "Id": "quadcopter", "Name": "[Demo] 小型四旋翼(DJI类)",
|
||||
"TargetType": 0, "PowerType": 0, "Wingspan": 1.2, "TypicalSpeed": 60.0, "TypicalAltitude": 300.0 },
|
||||
{ "Id": "electric-scout", "Name": "[Demo] 电推侦察无人机",
|
||||
"TargetType": 2, "PowerType": 0, "Wingspan": 1.8, "TypicalSpeed": 100.0, "TypicalAltitude": 500.0 },
|
||||
{ "Id": "fixed-piston", "Name": "[Demo] 固定翼活塞(Orlan类)",
|
||||
"TargetType": 1, "PowerType": 1, "Wingspan": 3.5, "TypicalSpeed": 150.0, "TypicalAltitude": 1000.0 },
|
||||
{ "Id": "shahed", "Name": "[Demo] 活塞巡飞弹(Shahed类)",
|
||||
"TargetType": 3, "PowerType": 1, "Wingspan": 2.5, "TypicalSpeed": 200.0, "TypicalAltitude": 500.0 },
|
||||
{ "Id": "tb2", "Name": "[Demo] 中空长航时(TB2类)",
|
||||
"TargetType": 1, "PowerType": 1, "Wingspan": 12.0, "TypicalSpeed": 220.0, "TypicalAltitude": 5500.0 },
|
||||
{ "Id": "cruise-missile", "Name": "[Demo] 巡航导弹(喷气式)",
|
||||
"TargetType": 4, "PowerType": 2, "Wingspan": 1.5, "TypicalSpeed": 300.0, "TypicalAltitude": 2000.0 }
|
||||
],
|
||||
|
||||
"detectionEquipment": [
|
||||
{ "Id": "radar-mr", "Name": "[Demo] 中程防空雷达",
|
||||
"RadarRange": 20000.0, "EORange": 0.0, "IRRange": 0.0, "Accuracy": 30.0 },
|
||||
{ "Id": "radar-sr", "Name": "[Demo] 近程防空雷达",
|
||||
"RadarRange": 10000.0, "EORange": 0.0, "IRRange": 0.0, "Accuracy": 50.0 },
|
||||
{ "Id": "eo-station", "Name": "[Demo] 光电跟踪站",
|
||||
"RadarRange": 0.0, "EORange": 15000.0, "IRRange": 8000.0, "Accuracy": 20.0 },
|
||||
{ "Id": "ir-sentry", "Name": "[Demo] 红外哨",
|
||||
"RadarRange": 0.0, "EORange": 0.0, "IRRange": 10000.0, "Accuracy": 40.0 }
|
||||
],
|
||||
|
||||
"weather": [
|
||||
{ "Id": "sunny-calm", "Name": "[Demo] 晴天无风",
|
||||
"WeatherType": 0, "WindSpeed": 3.0, "WindDirection": 0,
|
||||
"Temperature": 25.0, "Humidity": 50.0, "Pressure": 1013.0, "Visibility": 8000.0 },
|
||||
{ "Id": "sunny-windy", "Name": "[Demo] 晴天大风",
|
||||
"WeatherType": 0, "WindSpeed": 10.0, "WindDirection": 4,
|
||||
"Temperature": 28.0, "Humidity": 40.0, "Pressure": 1010.0, "Visibility": 10000.0 },
|
||||
{ "Id": "overcast", "Name": "[Demo] 阴天",
|
||||
"WeatherType": 1, "WindSpeed": 5.0, "WindDirection": 2,
|
||||
"Temperature": 18.0, "Humidity": 70.0, "Pressure": 1015.0, "Visibility": 5000.0 },
|
||||
{ "Id": "fog", "Name": "[Demo] 雾天",
|
||||
"WeatherType": 2, "WindSpeed": 2.0, "WindDirection": 1,
|
||||
"Temperature": 12.0, "Humidity": 95.0, "Pressure": 1020.0, "Visibility": 500.0 },
|
||||
{ "Id": "rain", "Name": "[Demo] 雨天",
|
||||
"WeatherType": 3, "WindSpeed": 8.0, "WindDirection": 3,
|
||||
"Temperature": 15.0, "Humidity": 90.0, "Pressure": 1005.0, "Visibility": 3000.0 },
|
||||
{ "Id": "night", "Name": "[Demo] 夜间",
|
||||
"WeatherType": 4, "WindSpeed": 2.0, "WindDirection": 5,
|
||||
"Temperature": 10.0, "Humidity": 65.0, "Pressure": 1018.0, "Visibility": 2000.0 }
|
||||
]
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>默认弹药规格 — 基于发烟罐/烟幕弹工程数据</summary>
|
||||
/// <remarks>
|
||||
/// 参考:10kg烟幕剂,抛射药/烟幕剂质量比 5%-20%,TNT当量 1.5-2kg(典型值)
|
||||
/// 初始半径 R₀ = 3.3 × W^0.32 ≈ 3.8~4.1m
|
||||
/// </remarks>
|
||||
public static class DefaultAmmunition
|
||||
{
|
||||
public static List<AmmunitionSpec> GetAll()
|
||||
{
|
||||
return new List<AmmunitionSpec>
|
||||
{
|
||||
new AmmunitionSpec
|
||||
{
|
||||
Id = "default-inert", AerosolType = (int)AerosolType.InertGas,
|
||||
Name = "惰性气体弹(发烟罐型)",
|
||||
InitialRadius = 3.8,
|
||||
CoreDensity = 1.5, EdgeDensity = 0.1,
|
||||
InitialTemperature = 1800, BuoyancyFactor = 0.3,
|
||||
EffectiveConcentration = 0.0001,
|
||||
MaxRadius = 100.0, MaxDuration = 120.0,
|
||||
SourceStrength = 10.0,
|
||||
BurstChargeKg = 1.5, TurbulentExpansionK = 3.0,
|
||||
},
|
||||
new AmmunitionSpec
|
||||
{
|
||||
Id = "default-active", AerosolType = (int)AerosolType.ActiveMaterial,
|
||||
Name = "活性材料弹(爆炸分散型)",
|
||||
InitialRadius = 5.0,
|
||||
CoreDensity = 2.0, EdgeDensity = 0.2,
|
||||
InitialTemperature = 2400, BuoyancyFactor = 0.6,
|
||||
EffectiveConcentration = 0.0001,
|
||||
MaxRadius = 80.0, MaxDuration = 90.0,
|
||||
SourceStrength = 12.0,
|
||||
BurstChargeKg = 4.0, TurbulentExpansionK = 4.0,
|
||||
},
|
||||
new AmmunitionSpec
|
||||
{
|
||||
Id = "default-fuel", AerosolType = (int)AerosolType.ActiveFuel,
|
||||
Name = "活性燃料弹(抛射分散型)",
|
||||
InitialRadius = 3.8,
|
||||
CoreDensity = 1.8, EdgeDensity = 0.15,
|
||||
InitialTemperature = 1900, BuoyancyFactor = 0.4,
|
||||
EffectiveConcentration = 0.0001,
|
||||
MaxRadius = 90.0, MaxDuration = 100.0,
|
||||
SourceStrength = 10.0,
|
||||
BurstChargeKg = 1.5, TurbulentExpansionK = 3.0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static AmmunitionSpec GetByType(AerosolType type)
|
||||
=> GetAll().Find(a => a.AerosolType == (int)type) ?? GetAll()[0];
|
||||
}
|
||||
}
|
||||
@ -47,14 +47,20 @@ namespace CounterDrone.Core
|
||||
|
||||
private void SeedDefaultData(SQLiteConnection db)
|
||||
{
|
||||
if (db.Table<AmmunitionSpec>().Count() > 0) return;
|
||||
db.CreateTable<MetaEntry>();
|
||||
|
||||
var jsonPath = Path.Combine(_paths.GetDataRoot(), "default_ammo.json");
|
||||
if (!File.Exists(jsonPath)) return;
|
||||
var defaults = DefaultData.Load(_paths);
|
||||
var currentVersion = db.Table<MetaEntry>()
|
||||
.FirstOrDefault(m => m.Key == "defaultsVersion")?.Value;
|
||||
|
||||
var specs = JsonSerializer.Deserialize<List<AmmunitionSpec>>(File.ReadAllText(jsonPath));
|
||||
if (specs != null)
|
||||
foreach (var s in specs) db.Insert(s);
|
||||
// 版本未变且数据已存在 → 跳过
|
||||
if (currentVersion == defaults.Version && db.Table<AmmunitionSpec>().Count() > 0)
|
||||
return;
|
||||
|
||||
// 插入或更新默认数据(按 Id 覆盖,不删除用户自建数据)
|
||||
foreach (var a in defaults.Ammunition) db.InsertOrReplace(a);
|
||||
|
||||
db.InsertOrReplace(new MetaEntry { Key = "defaultsVersion", Value = defaults.Version });
|
||||
}
|
||||
|
||||
private void CreateMainTables(SQLiteConnection db)
|
||||
|
||||
216
src/CounterDrone.Core/DefaultData.cs
Normal file
216
src/CounterDrone.Core/DefaultData.cs
Normal file
@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一默认数据 — 弹药、编队、火力单元、无人机、探测设备、天气的预设库。
|
||||
/// 所有默认数据集中在 data/defaults.json,通过本类统一加载。
|
||||
/// </summary>
|
||||
public class DefaultData
|
||||
{
|
||||
public string Version { get; set; } = "0";
|
||||
public List<AmmunitionSpec> Ammunition { get; set; } = new();
|
||||
public List<FormationTemplate> Formations { get; set; } = new();
|
||||
public List<FireUnitTemplate> FireUnits { get; set; } = new();
|
||||
public List<TargetPreset> Targets { get; set; } = new();
|
||||
public List<DetectionPreset> DetectionEquipment { get; set; } = new();
|
||||
public List<WeatherPreset> Weather { get; set; } = new();
|
||||
|
||||
/// <summary>从 data/defaults.json 加载。文件缺失或解析失败即抛异常。</summary>
|
||||
public static DefaultData Load(IPathProvider paths)
|
||||
{
|
||||
var jsonPath = Path.Combine(paths.GetDataRoot(), "defaults.json");
|
||||
if (!File.Exists(jsonPath))
|
||||
throw new FileNotFoundException($"默认数据文件未找到: {jsonPath}");
|
||||
|
||||
var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
var data = JsonSerializer.Deserialize<DefaultData>(File.ReadAllText(jsonPath), opts)
|
||||
?? throw new InvalidOperationException($"默认数据文件解析失败: {jsonPath}");
|
||||
|
||||
Validate(data, jsonPath);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>从 JSON 字符串解析(测试用)</summary>
|
||||
public static DefaultData FromJson(string json)
|
||||
{
|
||||
var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
var data = JsonSerializer.Deserialize<DefaultData>(json, opts)
|
||||
?? throw new InvalidOperationException("默认数据 JSON 解析失败");
|
||||
Validate(data, "<string>");
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void Validate(DefaultData data, string source)
|
||||
{
|
||||
if (data.Ammunition == null || data.Ammunition.Count == 0)
|
||||
throw new InvalidOperationException($"默认数据缺少 ammunition: {source}");
|
||||
if (data.Formations == null || data.Formations.Count == 0)
|
||||
throw new InvalidOperationException($"默认数据缺少 formations: {source}");
|
||||
if (data.FireUnits == null || data.FireUnits.Count == 0)
|
||||
throw new InvalidOperationException($"默认数据缺少 fireUnits: {source}");
|
||||
if (data.Targets == null || data.Targets.Count == 0)
|
||||
throw new InvalidOperationException($"默认数据缺少 targets: {source}");
|
||||
if (data.Weather == null || data.Weather.Count == 0)
|
||||
throw new InvalidOperationException($"默认数据缺少 weather: {source}");
|
||||
if (data.DetectionEquipment == null)
|
||||
data.DetectionEquipment = new();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>编队模板</summary>
|
||||
public class FormationTemplate
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public int FormationMode { get; set; }
|
||||
public int LateralCount { get; set; } = 1;
|
||||
public int LongitudinalCount { get; set; } = 1;
|
||||
public double LateralSpacing { get; set; }
|
||||
public double LongitudinalSpacing { get; set; }
|
||||
|
||||
public RoutePlan ToRoutePlan()
|
||||
{
|
||||
return new RoutePlan
|
||||
{
|
||||
FormationMode = FormationMode,
|
||||
LateralCount = LateralCount,
|
||||
LongitudinalCount = LongitudinalCount,
|
||||
LateralSpacing = LateralSpacing,
|
||||
LongitudinalSpacing = LongitudinalSpacing,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>火力单元模板</summary>
|
||||
public class FireUnitTemplate
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public int PlatformType { get; set; }
|
||||
public int GunCount { get; set; } = 1;
|
||||
public int ChannelsPerGun { get; set; } = 1;
|
||||
public double ChannelInterval { get; set; } = 1.0;
|
||||
public double Cooldown { get; set; } = 5.0;
|
||||
public double AmmoChangeTime { get; set; } = 30.0;
|
||||
public double MuzzleVelocity { get; set; }
|
||||
public double CruiseSpeed { get; set; }
|
||||
public double ReleaseAltitude { get; set; }
|
||||
public List<int> AmmoTypes { get; set; } = new();
|
||||
public double RadarRange { get; set; }
|
||||
public double EORange { get; set; }
|
||||
public double IRRange { get; set; }
|
||||
|
||||
public EquipmentDeployment ToEquipmentDeployment(AerosolType ammoType, int quantity,
|
||||
double posX, double posY, double posZ)
|
||||
{
|
||||
return new EquipmentDeployment
|
||||
{
|
||||
EquipmentRole = (int)Models.EquipmentRole.LaunchPlatform,
|
||||
PlatformType = PlatformType,
|
||||
Quantity = quantity,
|
||||
PositionX = posX,
|
||||
PositionY = posY,
|
||||
PositionZ = posZ,
|
||||
AerosolType = (int)ammoType,
|
||||
MunitionCount = GunCount * ChannelsPerGun * quantity,
|
||||
GunCount = GunCount,
|
||||
ChannelsPerGun = ChannelsPerGun,
|
||||
ChannelInterval = ChannelInterval,
|
||||
Cooldown = Cooldown,
|
||||
MuzzleVelocity = MuzzleVelocity > 0 ? MuzzleVelocity : null,
|
||||
CruiseSpeed = CruiseSpeed > 0 ? CruiseSpeed : null,
|
||||
ReleaseAltitude = ReleaseAltitude > 0 ? ReleaseAltitude : null,
|
||||
RadarRange = RadarRange > 0 ? RadarRange : null,
|
||||
EORange = EORange > 0 ? EORange : null,
|
||||
IRRange = IRRange > 0 ? IRRange : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>无人机/威胁目标预设</summary>
|
||||
public class TargetPreset
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public int TargetType { get; set; }
|
||||
public int PowerType { get; set; }
|
||||
public double Wingspan { get; set; }
|
||||
public double TypicalSpeed { get; set; }
|
||||
public double TypicalAltitude { get; set; }
|
||||
|
||||
public TargetConfig ToTargetConfig(string waveId = "default")
|
||||
{
|
||||
return new TargetConfig
|
||||
{
|
||||
WaveId = waveId,
|
||||
TargetType = TargetType,
|
||||
PowerType = PowerType,
|
||||
Quantity = 1,
|
||||
Wingspan = Wingspan,
|
||||
TypicalSpeed = TypicalSpeed,
|
||||
TypicalAltitude = TypicalAltitude,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>独立探测设备预设</summary>
|
||||
public class DetectionPreset
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public double RadarRange { get; set; }
|
||||
public double EORange { get; set; }
|
||||
public double IRRange { get; set; }
|
||||
public double Accuracy { get; set; } = 50.0;
|
||||
|
||||
public EquipmentDeployment ToEquipmentDeployment(double posX, double posY, double posZ)
|
||||
{
|
||||
return new EquipmentDeployment
|
||||
{
|
||||
EquipmentRole = (int)Models.EquipmentRole.Detection,
|
||||
Quantity = 1,
|
||||
PositionX = posX,
|
||||
PositionY = posY,
|
||||
PositionZ = posZ,
|
||||
RadarRange = RadarRange > 0 ? RadarRange : null,
|
||||
EORange = EORange > 0 ? EORange : null,
|
||||
IRRange = IRRange > 0 ? IRRange : null,
|
||||
DetectionAccuracy = Accuracy > 0 ? Accuracy : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>天气/场景预设</summary>
|
||||
public class WeatherPreset
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public int WeatherType { get; set; }
|
||||
public double WindSpeed { get; set; }
|
||||
public int WindDirection { get; set; }
|
||||
public double Temperature { get; set; } = 20.0;
|
||||
public double Humidity { get; set; } = 60.0;
|
||||
public double Pressure { get; set; } = 1013.0;
|
||||
public double Visibility { get; set; } = 5000.0;
|
||||
|
||||
public CombatScene ToCombatScene()
|
||||
{
|
||||
return new CombatScene
|
||||
{
|
||||
WeatherType = WeatherType,
|
||||
WindSpeed = WindSpeed,
|
||||
WindDirection = WindDirection,
|
||||
Temperature = Temperature,
|
||||
Humidity = Humidity,
|
||||
Pressure = Pressure,
|
||||
Visibility = Visibility,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CounterDrone.Core
|
||||
{
|
||||
public static class DefaultFireUnits
|
||||
{
|
||||
private static List<FireUnitTemplate>? _cache;
|
||||
|
||||
public static List<FireUnitTemplate> GetAll()
|
||||
{
|
||||
if (_cache != null) return _cache;
|
||||
_cache = new List<FireUnitTemplate>
|
||||
{
|
||||
new FireUnitTemplate
|
||||
{
|
||||
Id = "ground-light",
|
||||
Name = "轻型地基火力单元",
|
||||
PlatformType = 1,
|
||||
GunCount = 4,
|
||||
ChannelsPerGun = 4,
|
||||
ChannelInterval = 0.1,
|
||||
Cooldown = 5.0,
|
||||
AmmoChangeTime = 30.0,
|
||||
MuzzleVelocity = 800.0,
|
||||
AmmoTypes = new() { 0, 1 },
|
||||
RadarRange = 10000.0,
|
||||
EORange = 6000.0,
|
||||
IRRange = 3000.0,
|
||||
},
|
||||
new FireUnitTemplate
|
||||
{
|
||||
Id = "ground-standard",
|
||||
Name = "标准地基火力单元",
|
||||
PlatformType = 1,
|
||||
GunCount = 4,
|
||||
ChannelsPerGun = 4,
|
||||
ChannelInterval = 0.1,
|
||||
Cooldown = 5.0,
|
||||
AmmoChangeTime = 30.0,
|
||||
MuzzleVelocity = 800.0,
|
||||
AmmoTypes = new() { 0, 1 },
|
||||
RadarRange = 15000.0,
|
||||
EORange = 8000.0,
|
||||
IRRange = 5000.0,
|
||||
},
|
||||
new FireUnitTemplate
|
||||
{
|
||||
Id = "ground-heavy",
|
||||
Name = "重型地基火力单元",
|
||||
PlatformType = 1,
|
||||
GunCount = 6,
|
||||
ChannelsPerGun = 4,
|
||||
ChannelInterval = 1.0,
|
||||
Cooldown = 5.0,
|
||||
AmmoChangeTime = 30.0,
|
||||
MuzzleVelocity = 600.0,
|
||||
AmmoTypes = new() { 0, 1, 2 },
|
||||
RadarRange = 20000.0,
|
||||
EORange = 10000.0,
|
||||
IRRange = 6000.0,
|
||||
},
|
||||
new FireUnitTemplate
|
||||
{
|
||||
Id = "air-standard",
|
||||
Name = "标准空基火力单元",
|
||||
PlatformType = 0,
|
||||
GunCount = 1,
|
||||
ChannelsPerGun = 8,
|
||||
ChannelInterval = 1.0,
|
||||
Cooldown = 5.0,
|
||||
AmmoChangeTime = 30.0,
|
||||
CruiseSpeed = 55.0,
|
||||
ReleaseAltitude = 1500.0,
|
||||
AmmoTypes = new() { 0, 1 },
|
||||
EORange = 12000.0,
|
||||
IRRange = 8000.0,
|
||||
},
|
||||
};
|
||||
return _cache;
|
||||
}
|
||||
|
||||
public static FireUnitTemplate GetById(string id) => GetAll().Find(t => t.Id == id);
|
||||
}
|
||||
|
||||
public class FireUnitTemplate
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public int PlatformType { get; set; }
|
||||
public int GunCount { get; set; } = 1;
|
||||
public int ChannelsPerGun { get; set; } = 1;
|
||||
public double ChannelInterval { get; set; } = 1.0;
|
||||
public double Cooldown { get; set; } = 5.0;
|
||||
public double AmmoChangeTime { get; set; } = 30.0;
|
||||
public double MuzzleVelocity { get; set; }
|
||||
public double CruiseSpeed { get; set; }
|
||||
public double ReleaseAltitude { get; set; }
|
||||
public List<int> AmmoTypes { get; set; } = new();
|
||||
public double RadarRange { get; set; }
|
||||
public double EORange { get; set; }
|
||||
public double IRRange { get; set; }
|
||||
}
|
||||
}
|
||||
13
src/CounterDrone.Core/Models/MetaEntry.cs
Normal file
13
src/CounterDrone.Core/Models/MetaEntry.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>键值对元数据表(版本追踪等)</summary>
|
||||
[Table("Meta")]
|
||||
internal class MetaEntry
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@ -81,9 +81,13 @@ namespace CounterDrone.Core.Simulation
|
||||
_tickInterval = 1.0f / _tickRate;
|
||||
|
||||
using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath()))
|
||||
{
|
||||
_ammoSpec = ammoDb.Table<AmmunitionSpec>()
|
||||
.FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType)
|
||||
?? DefaultAmmunition.GetByType((AerosolType)config.Cloud.AerosolType);
|
||||
.FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType);
|
||||
}
|
||||
if (_ammoSpec == null || _ammoSpec.Id == null)
|
||||
_ammoSpec = DefaultData.Load(_paths).Ammunition
|
||||
.First(a => a.AerosolType == config.Cloud.AerosolType);
|
||||
|
||||
_drones.Clear();
|
||||
foreach (var target in config.Targets)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CounterDrone.Core
|
||||
{
|
||||
@ -12,6 +13,37 @@ namespace CounterDrone.Core
|
||||
{
|
||||
_root = rootDir;
|
||||
Directory.CreateDirectory(_root);
|
||||
CopyDataFiles();
|
||||
}
|
||||
|
||||
/// <summary>从项目 data/ 目录复制 defaults.json 和 planner_config.json 到测试根目录</summary>
|
||||
private void CopyDataFiles()
|
||||
{
|
||||
var sourceDir = FindProjectDataDir();
|
||||
if (sourceDir == null) return;
|
||||
foreach (var file in new[] { "defaults.json", "planner_config.json" })
|
||||
{
|
||||
var src = Path.Combine(sourceDir, file);
|
||||
var dst = Path.Combine(_root, file);
|
||||
if (File.Exists(src) && !File.Exists(dst))
|
||||
File.Copy(src, dst);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? FindProjectDataDir()
|
||||
{
|
||||
var asmDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
if (asmDir == null) return null;
|
||||
// 从 bin/Debug/netX.X 往上走到仓库根
|
||||
var dir = asmDir;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var candidate = Path.Combine(dir, "data/defaults.json");
|
||||
if (File.Exists(candidate)) return Path.Combine(dir, "data");
|
||||
dir = Path.GetDirectoryName(dir);
|
||||
if (dir == null) break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetDataRoot() => _root;
|
||||
|
||||
@ -50,7 +50,7 @@ namespace CounterDrone.Unity
|
||||
|
||||
var db = new DatabaseManager(paths).OpenMainDb();
|
||||
SqliteConnectionTracker.Track(db);
|
||||
foreach (var a in DefaultAmmunition.GetAll()) db.Insert(a);
|
||||
foreach (var a in DefaultData.Load(paths).Ammunition) db.Insert(a);
|
||||
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
|
||||
|
||||
var detail = scenarioMgr.GetDetail(taskId);
|
||||
|
||||
@ -34,7 +34,7 @@ namespace CounterDrone.Unity
|
||||
var dbm = new DatabaseManager(paths);
|
||||
var db = dbm.OpenMainDb();
|
||||
SqliteConnectionTracker.Track(db);
|
||||
foreach (var a in DefaultAmmunition.GetAll()) db.Insert(a);
|
||||
foreach (var a in DefaultData.Load(paths).Ammunition) db.Insert(a);
|
||||
|
||||
// 创建想定
|
||||
var scenario = new ScenarioService(
|
||||
|
||||
@ -48,7 +48,7 @@ namespace CounterDrone.Unity
|
||||
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
||||
|
||||
_frameStore = new FrameDataStore(_paths);
|
||||
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefensePlanner(DefaultAmmunition.GetAll(), PlannerConfig.Load(_paths)));
|
||||
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefensePlanner(DefaultData.Load(_paths).Ammunition, PlannerConfig.Load(_paths)));
|
||||
}
|
||||
|
||||
public void LoadAndStart(string taskId)
|
||||
|
||||
@ -76,10 +76,10 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
|
||||
var inertSpecs = _repo.GetByAerosolType((int)AerosolType.InertGas);
|
||||
Assert.Equal(2, inertSpecs.Count);
|
||||
Assert.Equal(3, inertSpecs.Count);
|
||||
|
||||
var activeSpecs = _repo.GetByAerosolType((int)AerosolType.ActiveMaterial);
|
||||
Assert.Single(activeSpecs);
|
||||
Assert.Equal(2, activeSpecs.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -95,7 +95,7 @@ namespace CounterDrone.Core.Tests
|
||||
_repo.InsertAll(specs);
|
||||
|
||||
var all = _repo.GetAll();
|
||||
Assert.Equal(2, all.Count);
|
||||
Assert.Equal(5, all.Count);
|
||||
Assert.Contains(all, s => s.Name == "惰性气体弹");
|
||||
Assert.Contains(all, s => s.Name == "活性材料弹");
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ namespace CounterDrone.Core.Tests
|
||||
{
|
||||
var db = _dbManager.OpenMainDb();
|
||||
|
||||
// 验证所有 11 张表存在
|
||||
// 验证所有 12 张表存在(含 Meta)
|
||||
var tables = new[]
|
||||
{
|
||||
"ModelInfo", "AmmunitionSpec", "SimTask", "CombatScene",
|
||||
|
||||
@ -13,7 +13,7 @@ namespace CounterDrone.Core.Tests
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public DefensePlannerTests(ITestOutputHelper output) { _output = output; }
|
||||
private static readonly List<AmmunitionSpec> TestAmmo = DefaultAmmunition.GetAll();
|
||||
private static readonly List<AmmunitionSpec> TestAmmo = TestData.Defaults.Ammunition;
|
||||
|
||||
private static FireUnit MakeGroundUnit(string id, float posX, int munitions = 16)
|
||||
=> new()
|
||||
|
||||
@ -6,7 +6,7 @@ namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class DispersionModelTests
|
||||
{
|
||||
private static AmmunitionSpec Ammo() => DefaultAmmunition.GetByType(AerosolType.InertGas);
|
||||
private static AmmunitionSpec Ammo() => TestData.Defaults.Ammunition.First(a => a.AerosolType == (int)AerosolType.InertGas);
|
||||
|
||||
[Fact]
|
||||
public void Phase1_InitialRadius_FromBurstCharge()
|
||||
@ -64,7 +64,7 @@ namespace CounterDrone.Core.Tests
|
||||
[Fact]
|
||||
public void DefaultAmmo_ParametersInReasonableRange()
|
||||
{
|
||||
var inert = DefaultAmmunition.GetByType(AerosolType.InertGas);
|
||||
var inert = TestData.Defaults.Ammunition.First(a => a.AerosolType == (int)AerosolType.InertGas);
|
||||
Assert.True(inert.BurstChargeKg is > 0.01 and < 5, "爆发药应 0.01~5kg");
|
||||
Assert.True(inert.TurbulentExpansionK is > 1 and < 20, "湍流系数应 1~20");
|
||||
Assert.True(inert.EffectiveConcentration is >= 0.0001 and < 0.1, "有效浓度阈值应 ≥ 0.0001");
|
||||
|
||||
@ -25,7 +25,6 @@ namespace CounterDrone.Core.Tests
|
||||
var paths = new TestPathProvider(_testDir);
|
||||
var dbm = new DatabaseManager(paths);
|
||||
_db = dbm.OpenMainDb();
|
||||
foreach (var a in DefaultAmmunition.GetAll()) _db.Insert(a);
|
||||
|
||||
_scenario = new ScenarioService(
|
||||
new SimTaskRepository(_db), new CombatSceneRepository(_db),
|
||||
@ -65,7 +64,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
|
||||
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(DefaultAmmunition.GetAll(), TestPlannerConfig.Instance));
|
||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Defaults.Ammunition, TestPlannerConfig.Instance));
|
||||
engine.Initialize(_taskId);
|
||||
engine.TimeScale = 4f;
|
||||
|
||||
@ -107,7 +106,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
|
||||
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(DefaultAmmunition.GetAll(), TestPlannerConfig.Instance));
|
||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Defaults.Ammunition, TestPlannerConfig.Instance));
|
||||
engine.Initialize(_taskId);
|
||||
engine.TimeScale = 4f;
|
||||
|
||||
@ -148,7 +147,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
|
||||
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(DefaultAmmunition.GetAll(), TestPlannerConfig.Instance));
|
||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Defaults.Ammunition, TestPlannerConfig.Instance));
|
||||
engine.Initialize(_taskId);
|
||||
engine.TimeScale = 4f;
|
||||
|
||||
|
||||
@ -29,8 +29,7 @@ namespace CounterDrone.Core.Tests
|
||||
_paths = new TestPathProvider(_testDir);
|
||||
var dbm = new DatabaseManager(_paths);
|
||||
_mainDb = dbm.OpenMainDb();
|
||||
foreach (var a in DefaultAmmunition.GetAll()) _mainDb.Insert(a);
|
||||
_ammoCatalog = new List<AmmunitionSpec>(DefaultAmmunition.GetAll());
|
||||
_ammoCatalog = new List<AmmunitionSpec>(TestData.Defaults.Ammunition);
|
||||
|
||||
_scenario = new ScenarioService(
|
||||
new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb),
|
||||
@ -180,7 +179,7 @@ namespace CounterDrone.Core.Tests
|
||||
// 场景 3:活塞式 200km/h,惰性气体拦截
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "被 Piston_Windy 替代:保留有风偏版本作为核心拦截测试")]
|
||||
public void Scenario_Piston_InertGasIntercept()
|
||||
{
|
||||
var task = _scenario.CreateTask("拦截活塞式无人机", "");
|
||||
@ -208,7 +207,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 1, 1500, 0, 50),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "ground-light"), AerosolType.InertGas, 1, 1500, 0, 50),
|
||||
});
|
||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||
|
||||
@ -292,7 +291,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 1, 1500, 0, 50),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "ground-light"), AerosolType.InertGas, 1, 1500, 0, 50),
|
||||
});
|
||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||
|
||||
@ -349,7 +348,7 @@ namespace CounterDrone.Core.Tests
|
||||
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("ground-standard"), AerosolType.ActiveMaterial, 1, 1500, 0, 50),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "ground-standard"), AerosolType.ActiveMaterial, 1, 1500, 0, 50),
|
||||
});
|
||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.ActiveMaterial, DisperseHeight = 800 });
|
||||
|
||||
@ -368,7 +367,7 @@ namespace CounterDrone.Core.Tests
|
||||
// 场景 5:空基平台 — 基于推荐方案,平台改为空基
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "被 AirBased_Windy 替代:保留有风偏版本作为空基测试")]
|
||||
public void Scenario_AirBased_PlatformFliesAndDrops()
|
||||
{
|
||||
var task = _scenario.CreateTask("空基平台拦截测试", "");
|
||||
@ -394,7 +393,7 @@ namespace CounterDrone.Core.Tests
|
||||
// 部署空基平台单元
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("air-standard"), AerosolType.InertGas, 3, 1500, 1000, 0),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "air-standard"), AerosolType.InertGas, 3, 1500, 1000, 0),
|
||||
});
|
||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||
|
||||
@ -459,7 +458,7 @@ namespace CounterDrone.Core.Tests
|
||||
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("air-standard"), AerosolType.InertGas, 3, 1500, 1000, 0),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "air-standard"), AerosolType.InertGas, 3, 1500, 1000, 0),
|
||||
});
|
||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||
|
||||
@ -493,7 +492,7 @@ namespace CounterDrone.Core.Tests
|
||||
// 验证 planner 给每个车道分配云团、3 架都被击毁
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "被 3DronesAirBased 替代:保留空基多目标版本")]
|
||||
public void Scenario_3DronesFormation_AllDestroyed()
|
||||
{
|
||||
var task = _scenario.CreateTask("3架编队拦截测试", "");
|
||||
@ -524,7 +523,7 @@ namespace CounterDrone.Core.Tests
|
||||
// 3 个火力单元部署在中点附近(X=4500,每个单元 i*50 偏移 → 4500/4550/4600)
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 3, 4500, 0, 50),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "ground-light"), AerosolType.InertGas, 3, 4500, 0, 50),
|
||||
});
|
||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||
|
||||
@ -578,7 +577,7 @@ namespace CounterDrone.Core.Tests
|
||||
// 3 个空基平台单元,部署在航路前方
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("air-standard"), AerosolType.InertGas, 3, 1500, 1000, 0),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "air-standard"), AerosolType.InertGas, 3, 1500, 1000, 0),
|
||||
});
|
||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||
|
||||
@ -610,7 +609,7 @@ namespace CounterDrone.Core.Tests
|
||||
// 验证:有探测设备时,planner 基于探测边界算到达时间,发射时机比无探测时晚
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "慢速集成测试:两次20km仿真(~7200帧×2)。按需运行: dotnet test --filter Scenario_DetectionDriven")]
|
||||
public void Scenario_DetectionDriven_PlanningDelayedByDetectionBoundary()
|
||||
{
|
||||
// 场景A:无探测设备(上帝视角,从航路起点算)
|
||||
@ -630,7 +629,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 1, 8000, 0, 50),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "ground-light"), AerosolType.InertGas, 1, 8000, 0, 50),
|
||||
});
|
||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||
var engA = RunSimulation(8000);
|
||||
@ -655,7 +654,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||
{
|
||||
MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 1, 8000, 0, 50),
|
||||
MakeEquipment(TestData.Defaults.FireUnits.First(f => f.Id == "ground-light"), AerosolType.InertGas, 1, 8000, 0, 50),
|
||||
// 独立探测设备
|
||||
new EquipmentDeployment
|
||||
{
|
||||
|
||||
@ -27,7 +27,6 @@ namespace CounterDrone.Core.Tests
|
||||
var paths = new TestPathProvider(_testDir);
|
||||
var dbManager = new DatabaseManager(paths);
|
||||
_mainDb = dbManager.OpenMainDb();
|
||||
foreach (var a in DefaultAmmunition.GetAll()) _mainDb.Insert(a);
|
||||
|
||||
_scenarioService = new ScenarioService(
|
||||
new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb),
|
||||
@ -36,7 +35,7 @@ namespace CounterDrone.Core.Tests
|
||||
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
|
||||
|
||||
_engine = new SimulationEngine(_scenarioService, new FrameDataStore(paths),
|
||||
new DamageModelRouter(), paths, new DefensePlanner(DefaultAmmunition.GetAll(), TestPlannerConfig.Instance));
|
||||
new DamageModelRouter(), paths, new DefensePlanner(TestData.Defaults.Ammunition, TestPlannerConfig.Instance));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
/// <summary>测试用弹药规格 — 直接引用默认值</summary>
|
||||
internal static class TestAmmo
|
||||
{
|
||||
public static readonly AmmunitionSpec Inert = DefaultAmmunition.GetByType(AerosolType.InertGas);
|
||||
public static readonly AmmunitionSpec Active = DefaultAmmunition.GetByType(AerosolType.ActiveMaterial);
|
||||
public static readonly AmmunitionSpec Fuel = DefaultAmmunition.GetByType(AerosolType.ActiveFuel);
|
||||
}
|
||||
}
|
||||
41
test/unit/CounterDrone.Core.Tests/TestData.cs
Normal file
41
test/unit/CounterDrone.Core.Tests/TestData.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
/// <summary>测试用默认数据 — 从 defaults.json 加载</summary>
|
||||
internal static class TestData
|
||||
{
|
||||
public static readonly DefaultData Defaults;
|
||||
|
||||
static TestData()
|
||||
{
|
||||
var json = @"
|
||||
{
|
||||
""version"": ""1"",
|
||||
""ammunition"": [
|
||||
{ ""Id"": ""inert"", ""AerosolType"": 0, ""Name"": ""惰性气体"", ""InitialRadius"": 3.8, ""CoreDensity"": 1.5, ""EdgeDensity"": 0.1, ""InitialTemperature"": 1800.0, ""BuoyancyFactor"": 0.3, ""EffectiveConcentration"": 0.0001, ""MaxRadius"": 100.0, ""MaxDuration"": 120.0, ""SourceStrength"": 10.0, ""BurstChargeKg"": 1.5, ""TurbulentExpansionK"": 3.0, ""ParticlesJson"": ""{}"" },
|
||||
{ ""Id"": ""active"", ""AerosolType"": 1, ""Name"": ""活性材料"", ""InitialRadius"": 5.0, ""CoreDensity"": 2.0, ""EdgeDensity"": 0.2, ""InitialTemperature"": 2400.0, ""BuoyancyFactor"": 0.6, ""EffectiveConcentration"": 0.0001, ""MaxRadius"": 80.0, ""MaxDuration"": 90.0, ""SourceStrength"": 12.0, ""BurstChargeKg"": 4.0, ""TurbulentExpansionK"": 4.0, ""ParticlesJson"": ""{}"" },
|
||||
{ ""Id"": ""fuel"", ""AerosolType"": 2, ""Name"": ""活性燃料"", ""InitialRadius"": 3.8, ""CoreDensity"": 1.8, ""EdgeDensity"": 0.15, ""InitialTemperature"": 1900.0, ""BuoyancyFactor"": 0.4, ""EffectiveConcentration"": 0.0001, ""MaxRadius"": 90.0, ""MaxDuration"": 100.0, ""SourceStrength"": 10.0, ""BurstChargeKg"": 1.5, ""TurbulentExpansionK"": 3.0, ""ParticlesJson"": ""{}"" }
|
||||
],
|
||||
""formations"": [
|
||||
{ ""Id"": ""single"", ""Name"": ""单机"", ""FormationMode"": 0, ""LateralCount"": 1, ""LongitudinalCount"": 1, ""LateralSpacing"": 0, ""LongitudinalSpacing"": 0 }
|
||||
],
|
||||
""fireUnits"": [
|
||||
{ ""Id"": ""ground-light"", ""Name"": ""轻型地基"", ""PlatformType"": 1, ""GunCount"": 4, ""ChannelsPerGun"": 4, ""ChannelInterval"": 0.1, ""Cooldown"": 5.0, ""AmmoChangeTime"": 30.0, ""MuzzleVelocity"": 800.0, ""AmmoTypes"": [0,1], ""RadarRange"": 10000.0, ""EORange"": 6000.0, ""IRRange"": 3000.0 },
|
||||
{ ""Id"": ""ground-standard"", ""Name"": ""标准地基"", ""PlatformType"": 1, ""GunCount"": 4, ""ChannelsPerGun"": 4, ""ChannelInterval"": 0.1, ""Cooldown"": 5.0, ""AmmoChangeTime"": 30.0, ""MuzzleVelocity"": 800.0, ""AmmoTypes"": [0,1], ""RadarRange"": 15000.0, ""EORange"": 8000.0, ""IRRange"": 5000.0 },
|
||||
{ ""Id"": ""ground-heavy"", ""Name"": ""重型地基"", ""PlatformType"": 1, ""GunCount"": 6, ""ChannelsPerGun"": 4, ""ChannelInterval"": 1.0, ""Cooldown"": 5.0, ""AmmoChangeTime"": 30.0, ""MuzzleVelocity"": 600.0, ""AmmoTypes"": [0,1,2], ""RadarRange"": 20000.0, ""EORange"": 10000.0, ""IRRange"": 6000.0 },
|
||||
{ ""Id"": ""air-standard"", ""Name"": ""标准空基"", ""PlatformType"": 0, ""GunCount"": 1, ""ChannelsPerGun"": 8, ""ChannelInterval"": 1.0, ""Cooldown"": 5.0, ""AmmoChangeTime"": 30.0, ""CruiseSpeed"": 55.0, ""ReleaseAltitude"": 1500.0, ""AmmoTypes"": [0,1], ""EORange"": 12000.0, ""IRRange"": 8000.0 }
|
||||
],
|
||||
""targets"": [
|
||||
{ ""Id"": ""shahed"", ""Name"": ""活塞巡飞弹"", ""TargetType"": 3, ""PowerType"": 1, ""Wingspan"": 2.5, ""TypicalSpeed"": 200.0, ""TypicalAltitude"": 500.0 }
|
||||
],
|
||||
""detectionEquipment"": [],
|
||||
""weather"": [
|
||||
{ ""Id"": ""sunny-calm"", ""Name"": ""晴天无风"", ""WeatherType"": 0, ""WindSpeed"": 3.0, ""WindDirection"": 0, ""Temperature"": 25.0, ""Humidity"": 50.0, ""Pressure"": 1013.0, ""Visibility"": 8000.0 }
|
||||
]
|
||||
}";
|
||||
Defaults = DefaultData.FromJson(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user