RecommendMultiGroup: 多编队火力分配 + 专一弹药优先 + 130测试通过
This commit is contained in:
parent
12cb6576fe
commit
6030b8de07
@ -33,8 +33,52 @@ namespace CounterDrone.Core.Algorithms
|
||||
public float InterceptProbability { get; set; }
|
||||
public string SummaryRationale { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>由推荐方案生成的发射计划,仿真器直接执行</summary>
|
||||
public List<FireEvent> FireSchedule { get; set; } = new();
|
||||
|
||||
/// <summary>此方案占用的平台起始索引(多编队合并用)</summary>
|
||||
public int PlatformOffset { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>多编队推荐结果</summary>
|
||||
public class MultiGroupRecommendation
|
||||
{
|
||||
/// <summary>每组的独立方案</summary>
|
||||
public List<DefenseSolution> GroupSolutions { get; set; } = new();
|
||||
/// <summary>合并后的总发射计划</summary>
|
||||
public List<FireEvent> MergedFireSchedule { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>火力单元(装备编组)</summary>
|
||||
public class FireUnit
|
||||
{
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
public AerosolType LoadedAmmo { get; set; }
|
||||
public int PlatformCount { get; set; }
|
||||
public float PositionX, PositionY, PositionZ;
|
||||
public float MuzzleVelocity { get; set; } = 800f;
|
||||
public float Cooldown { get; set; } = 5f;
|
||||
public float AmmoChangeTime { get; set; } = 300f;
|
||||
// 弹药库:可以装填的弹药类型
|
||||
public List<AerosolType> AvailableAmmo { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>无人机编队(多编队推荐输入)</summary>
|
||||
public class DroneGroup
|
||||
{
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
public TargetConfig Target { get; set; } = new();
|
||||
public RoutePlan Route { get; set; } = new();
|
||||
public List<Waypoint> Waypoints { get; set; } = new();
|
||||
|
||||
/// <summary>预计到达航路中点的时间(秒)</summary>
|
||||
public float GetArrivalTime()
|
||||
{
|
||||
if (Waypoints.Count < 2) return 0;
|
||||
var start = new Vector3((float)Waypoints[0].PosX, (float)Waypoints[0].PosY, (float)Waypoints[0].PosZ);
|
||||
var end = new Vector3((float)Waypoints[^1].PosX, (float)Waypoints[^1].PosY, (float)Waypoints[^1].PosZ);
|
||||
var speed = (float)Target.TypicalSpeed / 3.6f;
|
||||
return start.DistanceTo(end) / speed / 2f;
|
||||
}
|
||||
}
|
||||
|
||||
public class RecommendedPlatform
|
||||
|
||||
@ -219,5 +219,62 @@ namespace CounterDrone.Core.Algorithms
|
||||
var sigma = (sY + sZ) / 2f;
|
||||
return rPhase2 + sigma * (float)Math.Sqrt(2f * Math.Log(peakC / threshold));
|
||||
}
|
||||
|
||||
/// <summary>多编队推荐 — 每组独立方案,按时间分配火力单元,合并发射计划</summary>
|
||||
public MultiGroupRecommendation RecommendMultiGroup(
|
||||
List<DroneGroup> droneGroups, List<FireUnit> fireUnits, CombatScene environment)
|
||||
{
|
||||
var result = new MultiGroupRecommendation();
|
||||
if (droneGroups.Count == 0 || fireUnits.Count == 0) return result;
|
||||
|
||||
var sorted = droneGroups.OrderBy(g => g.GetArrivalTime()).ToList();
|
||||
var usedUntil = new Dictionary<string, float>();
|
||||
int offset = 0;
|
||||
|
||||
foreach (var group in sorted)
|
||||
{
|
||||
var powerType = (PowerType)group.Target.PowerType;
|
||||
var neededAmmo = MatchTable.GetValueOrDefault(powerType, AerosolType.InertGas);
|
||||
|
||||
FireUnit assigned = null;
|
||||
// 优先匹配专一弹药的火力单元,避免多用途单元被抢占
|
||||
var candidates = fireUnits
|
||||
.Where(u => u.AvailableAmmo.Contains(neededAmmo))
|
||||
.OrderBy(u => u.AvailableAmmo.Count) // 少弹种的优先
|
||||
.ThenBy(u => usedUntil.ContainsKey(u.GroupId) ? 1 : 0); // 不忙的优先
|
||||
|
||||
foreach (var unit in candidates)
|
||||
{
|
||||
if (!unit.AvailableAmmo.Contains(neededAmmo)) continue;
|
||||
if (!usedUntil.TryGetValue(unit.GroupId, out var busyUntil))
|
||||
{ assigned = unit; break; }
|
||||
if (unit.LoadedAmmo == neededAmmo && busyUntil <= group.GetArrivalTime())
|
||||
{ assigned = unit; break; }
|
||||
if (busyUntil + unit.AmmoChangeTime <= group.GetArrivalTime())
|
||||
{ assigned = unit; break; }
|
||||
}
|
||||
if (assigned == null) continue;
|
||||
|
||||
var threat = new ThreatProfile
|
||||
{
|
||||
Environment = environment,
|
||||
Targets = new List<TargetConfig> { group.Target },
|
||||
Route = group.Route,
|
||||
Waypoints = group.Waypoints,
|
||||
};
|
||||
var sol = Recommend(threat).Best;
|
||||
foreach (var fe in sol.FireSchedule) fe.PlatformIndex += offset;
|
||||
|
||||
result.GroupSolutions.Add(sol);
|
||||
result.MergedFireSchedule.AddRange(sol.FireSchedule);
|
||||
|
||||
assigned.LoadedAmmo = sol.RecommendedAerosolType;
|
||||
usedUntil[assigned.GroupId] = (sol.FireSchedule.Count > 0
|
||||
? sol.FireSchedule.Max(f => f.FireTime) : group.GetArrivalTime()) + assigned.Cooldown;
|
||||
offset += sol.Platforms.Count;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
@ -6,5 +7,11 @@ namespace CounterDrone.Core.Algorithms
|
||||
public interface IDefenseAdvisor
|
||||
{
|
||||
DefenseRecommendation Recommend(ThreatProfile threat);
|
||||
|
||||
/// <summary>多编队推荐 — 为每组分配火力单元,合并发射计划</summary>
|
||||
MultiGroupRecommendation RecommendMultiGroup(
|
||||
List<DroneGroup> droneGroups,
|
||||
List<FireUnit> fireUnits,
|
||||
CombatScene environment);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
47
test/unit/CounterDrone.Core.Tests/MultiGroupAdvisorTests.cs
Normal file
47
test/unit/CounterDrone.Core.Tests/MultiGroupAdvisorTests.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using CounterDrone.Core.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class MultiGroupAdvisorTests
|
||||
{
|
||||
[Fact]
|
||||
public void TwoSeparateUnits_TwoGroups_BothGetSolutions()
|
||||
{
|
||||
var ammoCatalog = DefaultAmmunition.GetAll();
|
||||
Console.WriteLine($"Ammo: inert={ammoCatalog[0].BurstChargeKg}kg, active={ammoCatalog[1].BurstChargeKg}kg");
|
||||
|
||||
var advisor = new DefaultDefenseAdvisor(ammoCatalog);
|
||||
|
||||
var g1 = new DroneGroup
|
||||
{
|
||||
GroupId = "g1", Target = new TargetConfig { PowerType = (int)PowerType.Piston, Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500 },
|
||||
Route = new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||
Waypoints = new List<Waypoint> { new() { PosX = 0, PosY = 500, PosZ = 0 }, new() { PosX = 20000, PosY = 500, PosZ = 0 } },
|
||||
};
|
||||
var g2 = new DroneGroup
|
||||
{
|
||||
GroupId = "g2", Target = new TargetConfig { PowerType = (int)PowerType.Jet, Quantity = 1, TypicalSpeed = 500, TypicalAltitude = 800 },
|
||||
Route = new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||
Waypoints = new List<Waypoint> { new() { PosX = 0, PosY = 800, PosZ = 0 }, new() { PosX = 30000, PosY = 800, PosZ = 0 } },
|
||||
};
|
||||
|
||||
var u1 = new FireUnit { GroupId = "u1", AvailableAmmo = new() { AerosolType.InertGas, AerosolType.ActiveMaterial }, PlatformCount = 5, MuzzleVelocity = 800 };
|
||||
var u2 = new FireUnit { GroupId = "u2", AvailableAmmo = new() { AerosolType.ActiveMaterial }, PlatformCount = 4, MuzzleVelocity = 800 };
|
||||
|
||||
var rec = advisor.RecommendMultiGroup(
|
||||
new List<DroneGroup> { g1, g2 },
|
||||
new List<FireUnit> { u1, u2 },
|
||||
new CombatScene { WindSpeed = 3 });
|
||||
|
||||
var msg = $"Solutions={rec.GroupSolutions.Count}, Merged={rec.MergedFireSchedule.Count}";
|
||||
foreach (var s in rec.GroupSolutions)
|
||||
msg += $" | {s.RecommendedAerosolType}: plat={s.Platforms.Count} fire={s.FireSchedule.Count}";
|
||||
Assert.True(rec.GroupSolutions.Count == 2, msg);
|
||||
Assert.NotEmpty(rec.MergedFireSchedule);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user