refactor: 推荐算法统一处理空基/地基平台类型

- ThreatProfile 新增 PreferredPlatformType 字段
- DefaultDefenseAdvisor 根据平台类型正确计算 FireTime/CruiseSpeed/ReleaseAltitude
- 空基:FireTime 提前扣除飞行+下落时间;地基:保持原逻辑
- 删除 SimulationEngine.AdjustFireTimesForAirBased(引擎不再做适配)
- 集成测试直接用 PreferredPlatformType 驱动空基方案
- 实施文档同步
This commit is contained in:
tian 2026-06-12 17:52:44 +08:00
parent c4d9ccc4fa
commit 0a3c444a4b
5 changed files with 60 additions and 76 deletions

View File

@ -160,7 +160,7 @@ Phase 8 ⬜ 待开发
|---|------|------|
| 8.1.1 | 探测设备搜索逻辑 | `DetectionEntity` 类已存在,需接入 `SimulationEngine` 实现探测→火控链路闭环 |
| 8.1.2 | 蜂群运动模型 | `FormationMode.Swarm` 枚举已定义,需差异化行为(随机扰动、个体差异) |
| 8.1.3 | 空基平台飞行动态 | `PlatformEntity` 状态机Idle→FlyingToTarget→ReadyToRelease巡航飞行弹药继承载机速度下落`Kinematics` 工具类支撑,集成测试通过 | ✅ |
| 8.1.3 | 空基平台飞行动态 | `PlatformEntity` 状态机,巡航飞行,弹药继承载机速度。**推荐算法统一处理平台类型**`ThreatProfile.PreferredPlatformType` 驱动,`DefaultDefenseAdvisor` 根据类型正确计算 `FireTime`/`CruiseSpeed`/`ReleaseAltitude`。引擎零适配逻辑 | ✅ |
| 8.1.4 | 预置典型目标库 | 具体无人机型号 JSON 配置(如 DJI Mavic 3、Shahed-136 等),导入 `TargetConfig` 默认值 |
| 8.1.5 | 毁伤曲线参数校准 | 三种毁伤模型的 damageRate/threshold/burstDamage 需甲方/领域专家确认后写入 |

View File

@ -11,6 +11,8 @@ namespace CounterDrone.Core.Algorithms
public RoutePlan Route { get; set; } = new();
public List<Waypoint> Waypoints { get; set; } = new();
public List<ControlZone> ControlZones { get; set; } = new();
/// <summary>偏好平台类型null = 算法自行决定,当前默认地基)</summary>
public PlatformType? PreferredPlatformType { get; set; }
}
/// <summary>防御推荐方案</summary>

View File

@ -105,21 +105,59 @@ namespace CounterDrone.Core.Algorithms
var expansionTime = (float)Math.Pow((effectiveR - r0) / k, 2);
var recommendedTiming = halfTime - expansionTime;
// 生成发射计划
// 平台类型
var platType = threat.PreferredPlatformType ?? PlatformType.GroundBased;
var isAirBased = platType == PlatformType.AirBased;
// 空基参数
var cruiseSpeed = 55f; // ~200 km/h
var releaseAlt = isAirBased ? (float)target.TypicalAltitude + 500f : 0f;
var fallTime = isAirBased ? Kinematics.AirDropFallTime(releaseAlt, (float)target.TypicalAltitude) : 0f;
// 生成发射计划和平台部署
var fireSchedule = new List<FireEvent>();
var platforms = new List<RecommendedPlatform>();
var gunCount = roundsNeeded;
for (int i = 0; i < roundsNeeded; i++)
{
var offset = (i - (roundsNeeded - 1) / 2f) * spacing;
fireSchedule.Add(new FireEvent
var tx = mid.X + offset; var ty = mid.Y; var tz = mid.Z;
if (isAirBased)
{
FireTime = recommendedTiming,
PlatformIndex = i % roundsNeeded,
TargetX = mid.X + offset,
TargetY = mid.Y,
TargetZ = mid.Z,
MuzzleVelocity = 800f,
// 巡逻点:防御圈边缘,偏离目标侧方
var patrolX = mid.X - 2000;
var patrolY = 2500f;
var patrolZ = mid.Z - 2000;
var flightDist = Kinematics.Distance3D(patrolX, patrolY, patrolZ, tx, releaseAlt, tz);
var flightTime = flightDist / cruiseSpeed;
var fireTime = recommendedTiming - flightTime - fallTime;
if (fireTime < 0.1f) fireTime = 0.1f;
fireSchedule.Add(new FireEvent { FireTime = fireTime, PlatformIndex = i, TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = 0f });
platforms.Add(new RecommendedPlatform
{
Type = PlatformType.AirBased,
Position = new Vector3(patrolX + i * 30, patrolY, patrolZ),
Quantity = 1, MunitionCount = 1,
CruiseSpeed = cruiseSpeed, ReleaseAltitude = releaseAlt,
Cooldown = 5f, CoverageVolume = (float)ammo.InitialVolume,
});
}
else
{
fireSchedule.Add(new FireEvent { FireTime = recommendedTiming, PlatformIndex = i, TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = 800f });
platforms.Add(new RecommendedPlatform
{
Type = PlatformType.GroundBased,
Position = new Vector3(mid.X + i * 50, 0, 50),
Quantity = 1, MunitionCount = 1,
MuzzleVelocity = 800f, Cooldown = 5f,
CoverageVolume = (float)ammo.InitialVolume,
});
}
}
var bestCloud = new CloudDispersal
{
@ -138,22 +176,6 @@ namespace CounterDrone.Core.Algorithms
EstimatedProbability = prob,
};
// 平台部署:假设齐射(同时发射),每门炮打一发后冷却 5s
// 需要 roundsNeeded 门炮同时发射
var gunCount = roundsNeeded;
var platforms = new List<RecommendedPlatform>();
for (int i = 0; i < gunCount; i++)
platforms.Add(new RecommendedPlatform
{
Type = PlatformType.GroundBased,
Position = new Vector3(mid.X + i * 50, 0, 50),
Quantity = 1,
MunitionCount = 1,
CoverageVolume = (float)ammo.InitialVolume,
Cooldown = 5f,
MuzzleVelocity = 800f,
});
result.Best = new DefenseSolution
{
RecommendedAerosolType = aerosolType,

View File

@ -114,7 +114,6 @@ namespace CounterDrone.Core.Simulation
SimulationTime = 0;
_entityCounter = 0;
_nextFireIndex = 0;
AdjustFireTimesForAirBased();
_frameDb = _frameStore.CreateOrOpen(_taskId);
State = SimulationState.Running;
}
@ -265,31 +264,6 @@ namespace CounterDrone.Core.Simulation
public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; }
public void Stop() { State = SimulationState.Stopped; _frameDb?.Close(); }
/// <summary>为空基平台提前发射时间,补偿飞行耗时</summary>
private void AdjustFireTimesForAirBased()
{
for (int i = 0; i < _fireSchedule.Count; i++)
{
var fe = _fireSchedule[i];
if (fe.PlatformIndex >= _platforms.Count) continue;
var p = _platforms[fe.PlatformIndex];
if (p.PlatformType != Models.PlatformType.AirBased) continue;
var releaseAlt = p.ReleaseAltitude > 0 ? p.ReleaseAltitude : _disperseHeight;
float dx = fe.TargetX - p.PatrolX;
float dy = releaseAlt - p.PatrolY;
float dz = fe.TargetZ - p.PatrolZ;
float flightDist = (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
float flightTime = flightDist / (p.CruiseSpeed > 0.1f ? p.CruiseSpeed : 55f);
float fallTime = Kinematics.AirDropFallTime(releaseAlt, _disperseHeight);
fe.FireTime -= flightTime + fallTime;
if (fe.FireTime < 0) fe.FireTime = 0.1f;
}
_fireSchedule.Sort((a, b) => a.FireTime.CompareTo(b.FireTime));
_nextFireIndex = 0;
}
private List<EntitySnapshot> CollectSnapshots()
{
var list = new List<EntitySnapshot>();

View File

@ -78,7 +78,7 @@ namespace CounterDrone.Core.Tests
}
/// <summary>将 DefenseAdvisor 推荐方案写入想定</summary>
private DefenseRecommendation GetAndApplyRecommendation()
private DefenseRecommendation GetAndApplyRecommendation(PlatformType? preferredPlatform = null)
{
var detail = _scenario.GetTaskDetail(_taskId)!;
var threat = new ThreatProfile
@ -87,6 +87,7 @@ namespace CounterDrone.Core.Tests
Targets = detail.Targets,
Route = detail.Routes[0],
Waypoints = detail.WaypointGroups["default"],
PreferredPlatformType = preferredPlatform,
};
var rec = new DefaultDefenseAdvisor(_ammoCatalog).Recommend(threat);
@ -115,7 +116,9 @@ namespace CounterDrone.Core.Tests
AerosolType = (int)rec.Best.RecommendedAerosolType,
MunitionCount = p.MunitionCount,
Cooldown = p.Cooldown,
MuzzleVelocity = p.MuzzleVelocity,
MuzzleVelocity = p.MuzzleVelocity > 0 ? p.MuzzleVelocity : null,
CruiseSpeed = p.CruiseSpeed > 0 ? p.CruiseSpeed : null,
ReleaseAltitude = p.ReleaseAltitude > 0 ? p.ReleaseAltitude : null,
});
_scenario.SaveDeployment(_taskId, equips);
_lastFireSchedule = rec.Best.FireSchedule;
@ -364,30 +367,13 @@ namespace CounterDrone.Core.Tests
new Waypoint { PosX = 15000, PosY = 500, PosZ = 0, Speed = 300 },
});
// 用推荐算法生成方案
var rec = GetAndApplyRecommendation();
// 用推荐算法生成空基方案
var rec = GetAndApplyRecommendation(PlatformType.AirBased);
Assert.NotNull(rec.Best.FireSchedule);
Assert.True(rec.Best.FireSchedule.Count > 0);
Assert.Equal(PlatformType.AirBased, rec.Best.Platforms[0].Type);
Assert.True(rec.Best.Platforms[0].CruiseSpeed > 0);
// 将推荐的地基平台改为空基:巡逻点在防御圈边缘空中
var saved = _scenario.GetTaskDetail(_taskId)!;
var midX = (float)(saved.Cloud.PositionX);
var airEquips = new List<EquipmentDeployment>();
foreach (var e in saved.Equipment.Where(eq => eq.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
{
e.PlatformType = (int)Models.PlatformType.AirBased;
// 巡逻点:防御圈边缘,侧方 2000m高度 2500m 巡航
e.PositionX = midX - 2000;
e.PositionY = 2500;
e.PositionZ = -2000;
e.CruiseSpeed = 55; // ~200 km/h
e.ReleaseAltitude = 1500; // 投放高度
e.MuzzleVelocity = null; // 空基不需要炮口初速
airEquips.Add(e);
}
_scenario.SaveDeployment(_taskId, airEquips);
// 更新 FireSchedule引擎初始化时会自动 AdjustFireTimesForAirBased
_lastFireSchedule = rec.Best.FireSchedule;
var eng = RunSimulation(8000);
@ -396,7 +382,7 @@ namespace CounterDrone.Core.Tests
var cloudsGenerated = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
var drone = eng.Drones[0];
var msg = $"AirBased: platforms={airEquips.Count}, launched={launched}, clouds={cloudsGenerated}, " +
var msg = $"AirBased: platforms={rec.Best.Platforms.Count}, launched={launched}, clouds={cloudsGenerated}, " +
$"droneStatus={drone.Status}, hp={drone.Hp:F3}, simTime={eng.SimulationTime:F1}s";
Assert.True(launched > 0, msg);