From 8eeb43f0e8181843fcd024b10b2c494d38704a73 Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Mon, 15 Jun 2026 15:46:20 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=9F=E4=B8=80=E9=BB=98=E8=AE=A4?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=9E=B6=E6=9E=84=20(defaults.json=20+=20Def?=
=?UTF-8?q?aultData)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
单一数据源 data/defaults.json,含弹药/编队/火力单元/无人机/探测/天气六类预设
版本追踪: Meta 表 + version 字段,更新时 InsertOrReplace,不删用户数据
名称统一 [Demo] 前缀,UI 中可识别为模拟数据
删除: DefaultAmmunition.cs, DefaultFireUnits.cs, default_ammo.json, default_formations.json, TestAmmo.cs
TestPathProvider 自动复制数据文件到测试目录
集成测试精简: 4个简单变体跳过,保留6个核心场景,39s
---
data/default_ammo.json | 56 -----
data/default_formations.json | 56 -----
data/defaults.json | 147 ++++++++++++
.../Algorithms/DefaultAmmunition.cs | 59 -----
src/CounterDrone.Core/DatabaseManager.cs | 18 +-
src/CounterDrone.Core/DefaultData.cs | 216 ++++++++++++++++++
src/CounterDrone.Core/DefaultFireUnits.cs | 103 ---------
src/CounterDrone.Core/Models/MetaEntry.cs | 13 ++
.../Simulation/SimulationEngine.cs | 8 +-
src/CounterDrone.Core/TestPathProvider.cs | 32 +++
.../Scripts/Managers/ManagerVerification.cs | 2 +-
.../Scripts/Managers/SimulationBootstrap.cs | 2 +-
.../Scripts/Managers/SimulationRunner.cs | 2 +-
.../AmmunitionSpecRepositoryTests.cs | 6 +-
.../DatabaseManagerTests.cs | 2 +-
.../DefensePlannerTests.cs | 2 +-
.../DispersionModelTests.cs | 4 +-
.../CounterDrone.Core.Tests/EdgeCaseTests.cs | 7 +-
.../FullPipelineTests.cs | 29 ++-
.../SimulationEngineTests.cs | 3 +-
test/unit/CounterDrone.Core.Tests/TestAmmo.cs | 14 --
test/unit/CounterDrone.Core.Tests/TestData.cs | 41 ++++
22 files changed, 495 insertions(+), 327 deletions(-)
delete mode 100644 data/default_ammo.json
delete mode 100644 data/default_formations.json
create mode 100644 data/defaults.json
delete mode 100644 src/CounterDrone.Core/Algorithms/DefaultAmmunition.cs
create mode 100644 src/CounterDrone.Core/DefaultData.cs
delete mode 100644 src/CounterDrone.Core/DefaultFireUnits.cs
create mode 100644 src/CounterDrone.Core/Models/MetaEntry.cs
delete mode 100644 test/unit/CounterDrone.Core.Tests/TestAmmo.cs
create mode 100644 test/unit/CounterDrone.Core.Tests/TestData.cs
diff --git a/data/default_ammo.json b/data/default_ammo.json
deleted file mode 100644
index 5b6fe29..0000000
--- a/data/default_ammo.json
+++ /dev/null
@@ -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": "{}"
- }
-]
diff --git a/data/default_formations.json b/data/default_formations.json
deleted file mode 100644
index bb01361..0000000
--- a/data/default_formations.json
+++ /dev/null
@@ -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
- }
-]
diff --git a/data/defaults.json b/data/defaults.json
new file mode 100644
index 0000000..994a2f0
--- /dev/null
+++ b/data/defaults.json
@@ -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 }
+ ]
+}
diff --git a/src/CounterDrone.Core/Algorithms/DefaultAmmunition.cs b/src/CounterDrone.Core/Algorithms/DefaultAmmunition.cs
deleted file mode 100644
index 4d1dfa1..0000000
--- a/src/CounterDrone.Core/Algorithms/DefaultAmmunition.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System.Collections.Generic;
-using CounterDrone.Core.Models;
-
-namespace CounterDrone.Core.Algorithms
-{
- /// 默认弹药规格 — 基于发烟罐/烟幕弹工程数据
- ///
- /// 参考:10kg烟幕剂,抛射药/烟幕剂质量比 5%-20%,TNT当量 1.5-2kg(典型值)
- /// 初始半径 R₀ = 3.3 × W^0.32 ≈ 3.8~4.1m
- ///
- public static class DefaultAmmunition
- {
- public static List GetAll()
- {
- return new List
- {
- 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];
- }
-}
diff --git a/src/CounterDrone.Core/DatabaseManager.cs b/src/CounterDrone.Core/DatabaseManager.cs
index c24496d..f408f2f 100644
--- a/src/CounterDrone.Core/DatabaseManager.cs
+++ b/src/CounterDrone.Core/DatabaseManager.cs
@@ -47,14 +47,20 @@ namespace CounterDrone.Core
private void SeedDefaultData(SQLiteConnection db)
{
- if (db.Table().Count() > 0) return;
+ db.CreateTable();
- var jsonPath = Path.Combine(_paths.GetDataRoot(), "default_ammo.json");
- if (!File.Exists(jsonPath)) return;
+ var defaults = DefaultData.Load(_paths);
+ var currentVersion = db.Table()
+ .FirstOrDefault(m => m.Key == "defaultsVersion")?.Value;
- var specs = JsonSerializer.Deserialize>(File.ReadAllText(jsonPath));
- if (specs != null)
- foreach (var s in specs) db.Insert(s);
+ // 版本未变且数据已存在 → 跳过
+ if (currentVersion == defaults.Version && db.Table().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)
diff --git a/src/CounterDrone.Core/DefaultData.cs b/src/CounterDrone.Core/DefaultData.cs
new file mode 100644
index 0000000..8b15293
--- /dev/null
+++ b/src/CounterDrone.Core/DefaultData.cs
@@ -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
+{
+ ///
+ /// 统一默认数据 — 弹药、编队、火力单元、无人机、探测设备、天气的预设库。
+ /// 所有默认数据集中在 data/defaults.json,通过本类统一加载。
+ ///
+ public class DefaultData
+ {
+ public string Version { get; set; } = "0";
+ public List Ammunition { get; set; } = new();
+ public List Formations { get; set; } = new();
+ public List FireUnits { get; set; } = new();
+ public List Targets { get; set; } = new();
+ public List DetectionEquipment { get; set; } = new();
+ public List Weather { get; set; } = new();
+
+ /// 从 data/defaults.json 加载。文件缺失或解析失败即抛异常。
+ 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(File.ReadAllText(jsonPath), opts)
+ ?? throw new InvalidOperationException($"默认数据文件解析失败: {jsonPath}");
+
+ Validate(data, jsonPath);
+ return data;
+ }
+
+ /// 从 JSON 字符串解析(测试用)
+ public static DefaultData FromJson(string json)
+ {
+ var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
+ var data = JsonSerializer.Deserialize(json, opts)
+ ?? throw new InvalidOperationException("默认数据 JSON 解析失败");
+ Validate(data, "");
+ 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();
+ }
+ }
+
+ /// 编队模板
+ 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,
+ };
+ }
+ }
+
+ /// 火力单元模板
+ 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 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,
+ };
+ }
+ }
+
+ /// 无人机/威胁目标预设
+ 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,
+ };
+ }
+ }
+
+ /// 独立探测设备预设
+ 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,
+ };
+ }
+ }
+
+ /// 天气/场景预设
+ 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,
+ };
+ }
+ }
+}
diff --git a/src/CounterDrone.Core/DefaultFireUnits.cs b/src/CounterDrone.Core/DefaultFireUnits.cs
deleted file mode 100644
index 7afd5c6..0000000
--- a/src/CounterDrone.Core/DefaultFireUnits.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-using System.Collections.Generic;
-
-namespace CounterDrone.Core
-{
- public static class DefaultFireUnits
- {
- private static List? _cache;
-
- public static List GetAll()
- {
- if (_cache != null) return _cache;
- _cache = new List
- {
- 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 AmmoTypes { get; set; } = new();
- public double RadarRange { get; set; }
- public double EORange { get; set; }
- public double IRRange { get; set; }
- }
-}
diff --git a/src/CounterDrone.Core/Models/MetaEntry.cs b/src/CounterDrone.Core/Models/MetaEntry.cs
new file mode 100644
index 0000000..eb694f1
--- /dev/null
+++ b/src/CounterDrone.Core/Models/MetaEntry.cs
@@ -0,0 +1,13 @@
+using SQLite;
+
+namespace CounterDrone.Core.Models
+{
+ /// 键值对元数据表(版本追踪等)
+ [Table("Meta")]
+ internal class MetaEntry
+ {
+ [PrimaryKey]
+ public string Key { get; set; } = string.Empty;
+ public string Value { get; set; } = string.Empty;
+ }
+}
diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs
index a83440b..d856830 100644
--- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs
+++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs
@@ -81,9 +81,13 @@ namespace CounterDrone.Core.Simulation
_tickInterval = 1.0f / _tickRate;
using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath()))
+ {
_ammoSpec = ammoDb.Table()
- .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)
diff --git a/src/CounterDrone.Core/TestPathProvider.cs b/src/CounterDrone.Core/TestPathProvider.cs
index 9f8e4c8..dda5542 100644
--- a/src/CounterDrone.Core/TestPathProvider.cs
+++ b/src/CounterDrone.Core/TestPathProvider.cs
@@ -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();
+ }
+
+ /// 从项目 data/ 目录复制 defaults.json 和 planner_config.json 到测试根目录
+ 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;
diff --git a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs
index 7f7d93d..f1102bb 100644
--- a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs
+++ b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs
@@ -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().ToList();
var detail = scenarioMgr.GetDetail(taskId);
diff --git a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs
index 26fe2e9..5bd66ef 100644
--- a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs
+++ b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs
@@ -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(
diff --git a/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs b/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs
index 78ee901..1e1bddb 100644
--- a/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs
+++ b/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs
@@ -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)
diff --git a/test/unit/CounterDrone.Core.Tests/AmmunitionSpecRepositoryTests.cs b/test/unit/CounterDrone.Core.Tests/AmmunitionSpecRepositoryTests.cs
index e4e370f..61554fc 100644
--- a/test/unit/CounterDrone.Core.Tests/AmmunitionSpecRepositoryTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/AmmunitionSpecRepositoryTests.cs
@@ -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 == "活性材料弹");
}
diff --git a/test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs b/test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs
index 8180035..b4a49c5 100644
--- a/test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs
@@ -31,7 +31,7 @@ namespace CounterDrone.Core.Tests
{
var db = _dbManager.OpenMainDb();
- // 验证所有 11 张表存在
+ // 验证所有 12 张表存在(含 Meta)
var tables = new[]
{
"ModelInfo", "AmmunitionSpec", "SimTask", "CombatScene",
diff --git a/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs b/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs
index c5daec1..7eb0ae8 100644
--- a/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs
@@ -13,7 +13,7 @@ namespace CounterDrone.Core.Tests
private readonly ITestOutputHelper _output;
public DefensePlannerTests(ITestOutputHelper output) { _output = output; }
- private static readonly List TestAmmo = DefaultAmmunition.GetAll();
+ private static readonly List TestAmmo = TestData.Defaults.Ammunition;
private static FireUnit MakeGroundUnit(string id, float posX, int munitions = 16)
=> new()
diff --git a/test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs b/test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs
index fac37c3..629bbe1 100644
--- a/test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs
@@ -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");
diff --git a/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs b/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs
index cd8292f..5d69799 100644
--- a/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs
@@ -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;
diff --git a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs
index 4dc2ee0..faf22c3 100644
--- a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs
@@ -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(DefaultAmmunition.GetAll());
+ _ammoCatalog = new List(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
{
- 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
{
- 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
{
- 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
{
- 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
{
- 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
{
- 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
{
- 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
{
- 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
{
- 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
{
diff --git a/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs b/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs
index 7a93603..93a7b2c 100644
--- a/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs
@@ -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()
diff --git a/test/unit/CounterDrone.Core.Tests/TestAmmo.cs b/test/unit/CounterDrone.Core.Tests/TestAmmo.cs
deleted file mode 100644
index cc2aae8..0000000
--- a/test/unit/CounterDrone.Core.Tests/TestAmmo.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-using CounterDrone.Core.Algorithms;
-using CounterDrone.Core.Models;
-
-namespace CounterDrone.Core.Tests
-{
- /// 测试用弹药规格 — 直接引用默认值
- 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);
- }
-}
diff --git a/test/unit/CounterDrone.Core.Tests/TestData.cs b/test/unit/CounterDrone.Core.Tests/TestData.cs
new file mode 100644
index 0000000..4aca8cc
--- /dev/null
+++ b/test/unit/CounterDrone.Core.Tests/TestData.cs
@@ -0,0 +1,41 @@
+using CounterDrone.Core;
+using CounterDrone.Core.Models;
+
+namespace CounterDrone.Core.Tests
+{
+ /// 测试用默认数据 — 从 defaults.json 加载
+ 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);
+ }
+ }
+}