refactor: 每单元锁定一个Y车道,非轮转分配

- 单元→车道固定分配(管转向跟不上一发一个Y)
- 车道需求 tracked,单元依次填满当前车道
This commit is contained in:
tian 2026-06-13 14:04:00 +08:00
parent 29aa11665b
commit 4f509e5a2a

View File

@ -145,68 +145,50 @@ namespace CounterDrone.Core.Algorithms
}
int totalRoundsNeeded = singleNeeded * yLanes;
// 计算所需齐射轮数
int totalChannels = candidates.Sum(c => c.Unit.TotalChannels);
int salvosNeeded = (int)Math.Ceiling((float)totalRoundsNeeded / Math.Max(1, totalChannels));
// 逐轮齐射分配
// 逐单元分配:每个单元锁定一个 Y 车道
var (effR2, _) = ComputeEffectiveRadius(threat, ammo, env);
float spacing = effR2 * 1.5f;
int[] laneNeeded = new int[yLanes];
for (int l = 0; l < yLanes; l++) laneNeeded[l] = singleNeeded;
int currentLane = 0;
int roundsCollected = 0;
int eventIdx = 0;
var assignedUnits = new List<(FireUnit unit, int rounds, List<FireEvent> events)>();
// Y 车道间距
var (effR2, _) = ComputeEffectiveRadius(threat, ammo, env);
float laneSpacingY = yLanes > 1 ? formationWidth / (yLanes - 1) : 0f;
for (int salvo = 0; salvo < salvosNeeded; salvo++)
foreach (var c in candidates.OrderBy(c => c.EarliestInterceptTime)
.ThenByDescending(c => c.KillProbability))
{
float salvoDelay = salvo * candidates[0].Unit.Cooldown;
if (roundsCollected >= totalRoundsNeeded) break;
int remaining = remainingMunitions.GetValueOrDefault(c.Unit.Id, 0);
if (remaining <= 0) continue;
// 车道参数
var (laneEffR, _) = ComputeEffectiveRadius(threat, ammo, env);
float cloudDiameter = laneEffR * 2f;
float droneSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
float physicsInterval = Math.Max(candidates[0].Unit.ChannelInterval,
cloudDiameter / droneSpeed);
float spacing = laneEffR * 1.5f;
// 找下一个有需求的车道
while (currentLane < yLanes && laneNeeded[currentLane] <= 0) currentLane++;
if (currentLane >= yLanes) break;
// 所有弹药以中点偏移=0为基准计算基础发射时间
var refEvent = GenerateFireEventsAt(threat, candidates[0].Unit, neededAmmo, ammo, env, 0, 0, yLanes, formationWidth);
float globalBaseFireTime = refEvent[0].FireTime;
int toTake = Math.Min(Math.Min(c.Unit.TotalChannels, remaining), laneNeeded[currentLane]);
if (toTake <= 0) continue;
foreach (var c in candidates.OrderBy(c => c.EarliestInterceptTime)
.ThenByDescending(c => c.KillProbability))
// 此单元的所有弹药锁定同一个 Y 车道
int yLane = currentLane;
var refEvt = GenerateFireEventsAt(threat, c.Unit, c.AmmoType, ammo, env, 0, yLane, yLanes, formationWidth);
float unitBaseTime = refEvt[0].FireTime;
var fevents = new List<FireEvent>();
for (int ch = 0; ch < toTake; ch++)
{
if (roundsCollected >= totalRoundsNeeded) break;
int remaining = remainingMunitions.GetValueOrDefault(c.Unit.Id, 0);
if (remaining <= 0) continue;
int maxSalvo = Math.Min(c.Unit.TotalChannels, remaining);
int toTake = Math.Min(maxSalvo, totalRoundsNeeded - roundsCollected);
if (toTake <= 0) continue;
// 所有单元以航路中点计算相同的基准发射时间,仅通道间隔错开
var refEvt = GenerateFireEventsAt(threat, c.Unit, c.AmmoType, ammo, env, 0, 0, yLanes, formationWidth);
float unitBaseTime = refEvt[0].FireTime;
var fevents = new List<FireEvent>();
for (int ch = 0; ch < toTake; ch++)
{
float offset = (eventIdx - (totalRoundsNeeded - 1) / 2f) * spacing;
int yLane = eventIdx % yLanes;
var fe = GenerateFireEventsAt(threat, c.Unit, c.AmmoType, ammo, env, offset, yLane, yLanes, formationWidth);
foreach (var e in fe)
{
e.FireTime = unitBaseTime + salvoDelay + ch * c.Unit.ChannelInterval;
}
fevents.AddRange(fe);
eventIdx++;
}
assignedUnits.Add((c.Unit, toTake, fevents));
remainingMunitions[c.Unit.Id] -= toTake;
roundsCollected += toTake;
float offset = (eventIdx - (totalRoundsNeeded - 1) / 2f) * spacing;
var fe = GenerateFireEventsAt(threat, c.Unit, c.AmmoType, ammo, env, offset, yLane, yLanes, formationWidth);
foreach (var e in fe)
e.FireTime = unitBaseTime + ch * c.Unit.ChannelInterval;
fevents.AddRange(fe);
eventIdx++;
}
assignedUnits.Add((c.Unit, toTake, fevents));
remainingMunitions[c.Unit.Id] -= toTake;
laneNeeded[currentLane] -= toTake;
roundsCollected += toTake;
}
if (roundsCollected <= 0) { plan.ThreatsUnengaged++; continue; }