refactor: 删除所有逻辑fallback——缺失配置报错而非静默跳过

- DefaultDefensePlanner: 空catalog抛ArgumentEx, 缺ammo类型抛InvalidOp
- AmmunitionSpec? → AmmunitionSpec(非空), 删除 ==null 静默返回
- 删除 0.8f射程安全系数, 1.5f时间窗口乘数
- AlgorithmFactory 移除IDefensePlanner(null)注册
- SimulationEngine 构造必须传planner,6个调用点全部更新
This commit is contained in:
tian 2026-06-13 12:19:21 +08:00
parent b5b561039b
commit 45387fee73
8 changed files with 28 additions and 25 deletions

View File

@ -10,7 +10,6 @@ namespace CounterDrone.Core.Algorithms
{
[typeof(ICloudDispersionModel)] = () => new GaussianPuffDispersion(),
[typeof(IDamageModel)] = () => new DamageModelRouter(),
[typeof(IDefensePlanner)] = () => new DefaultDefensePlanner(null),
};
public static void Register<TInterface>(Func<object> factory)

View File

@ -12,7 +12,9 @@ namespace CounterDrone.Core.Algorithms
public DefaultDefensePlanner(List<AmmunitionSpec> ammoCatalog)
{
_ammoCatalog = ammoCatalog ?? new List<AmmunitionSpec>();
_ammoCatalog = ammoCatalog ?? throw new ArgumentNullException(nameof(ammoCatalog));
if (_ammoCatalog.Count == 0)
throw new ArgumentException("弹药规格目录不能为空");
}
private static readonly Dictionary<PowerType, AerosolType> MatchTable = new()
@ -60,6 +62,17 @@ namespace CounterDrone.Core.Algorithms
var sorted = threats.OrderByDescending(t => t.Priority).ToList();
// Step 2-4: 贪心分配求解
// 预先检查:所有需要的弹药类型都在目录中
var neededTypes = sorted
.Select(t => MatchAmmo((PowerType)t.Target.PowerType))
.Distinct()
.ToList();
foreach (var t in neededTypes)
{
if (!_ammoCatalog.Any(a => a.AerosolType == (int)t))
throw new InvalidOperationException($"弹药规格目录中缺少类型: {t}");
}
result.Best = Solve(sorted, fireUnits, environment);
// Step 5: 临界方案
@ -230,8 +243,7 @@ namespace CounterDrone.Core.Algorithms
{
var candidates = new List<InterceptCandidate>();
var neededAmmo = MatchAmmo((PowerType)threat.Target.PowerType);
var ammo = _ammoCatalog.FirstOrDefault(a => a.AerosolType == (int)neededAmmo);
if (ammo == null) return candidates;
var ammo = _ammoCatalog.First(a => a.AerosolType == (int)neededAmmo);
var ammoEff = ComputeEffectiveRadius(threat, ammo, env);
@ -265,7 +277,7 @@ namespace CounterDrone.Core.Algorithms
if (unit.MuzzleVelocity <= 0) return null;
float maxRange = unit.MuzzleVelocity * unit.MuzzleVelocity / 9.81f;
if (dist > maxRange * 0.8f) return null;
if (dist > maxRange) return null;
float shellTime = dist / unit.MuzzleVelocity;
float interceptTime = threat.ArrivalTime;
@ -302,7 +314,7 @@ namespace CounterDrone.Core.Algorithms
float totalTime = flightTime + fallTime;
float interceptTime = threat.ArrivalTime;
if (totalTime > interceptTime * 1.5f) return null;
if (totalTime > interceptTime) return null;
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
float neededExposure = ammoType == AerosolType.ActiveMaterial ? 2f : 6f;
@ -365,11 +377,9 @@ namespace CounterDrone.Core.Algorithms
return (effectiveR, expansionTime);
}
private int CalcRoundsNeeded(DroneGroup threat, AmmunitionSpec? ammo,
private int CalcRoundsNeeded(DroneGroup threat, AmmunitionSpec ammo,
CombatScene env, bool isAirBased)
{
if (ammo == null) return 1;
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
var (effectiveR, _) = ComputeEffectiveRadius(threat, ammo, env);
float spacing = effectiveR * 1.5f;
@ -383,10 +393,8 @@ namespace CounterDrone.Core.Algorithms
}
private float ComputeInterceptProbability(DroneGroup threat,
AmmunitionSpec? ammo, int rounds, CombatScene env)
AmmunitionSpec ammo, int rounds, CombatScene env)
{
if (ammo == null) return 0f;
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
var (effectiveR, _) = ComputeEffectiveRadius(threat, ammo, env);
float spacing = effectiveR * 1.5f;
@ -404,10 +412,9 @@ namespace CounterDrone.Core.Algorithms
// ═══════════════════════════════════════════════
private List<FireEvent> GenerateFireEventsAt(DroneGroup threat, FireUnit unit,
AerosolType ammoType, AmmunitionSpec? ammo, CombatScene env, float targetOffset)
AerosolType ammoType, AmmunitionSpec ammo, CombatScene env, float targetOffset)
{
var events = new List<FireEvent>();
if (ammo == null) return events;
var mid = ThreatMidpoint(threat);
var (effectiveR, expansionTime) = ComputeEffectiveRadius(threat, ammo, env);

View File

@ -53,13 +53,13 @@ namespace CounterDrone.Core.Simulation
private int _entityCounter;
public SimulationEngine(IScenarioService scenarioService, FrameDataStore frameStore,
IDamageModel damageModel, IPathProvider paths, IDefensePlanner? planner = null)
IDamageModel damageModel, IPathProvider paths, IDefensePlanner planner)
{
_scenarioService = scenarioService;
_frameStore = frameStore;
_damageModel = damageModel;
_paths = paths;
_planner = planner ?? new DefaultDefensePlanner(new List<AmmunitionSpec>());
_planner = planner;
}
public void SetFireSchedule(List<FireEvent> schedule)

View File

@ -47,7 +47,7 @@ namespace CounterDrone.Unity
new RoutePlanRepository(_db), new WaypointRepository(_db));
_frameStore = new FrameDataStore(_paths);
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths);
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefaultDefensePlanner(DefaultAmmunition.GetAll()));
}
public void LoadAndStart(string taskId)

View File

@ -13,9 +13,6 @@ namespace CounterDrone.Core.Tests
var damage = AlgorithmFactory.Create<IDamageModel>();
Assert.IsType<DamageModelRouter>(damage);
var planner = AlgorithmFactory.Create<IDefensePlanner>();
Assert.IsType<DefaultDefensePlanner>(planner);
}
[Fact]

View File

@ -65,7 +65,7 @@ namespace CounterDrone.Core.Tests
});
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
new DamageModelRouter(), new TestPathProvider(_testDir));
new DamageModelRouter(), new TestPathProvider(_testDir), new DefaultDefensePlanner(DefaultAmmunition.GetAll()));
engine.Initialize(_taskId);
engine.TimeScale = 4f;
@ -107,7 +107,7 @@ namespace CounterDrone.Core.Tests
});
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
new DamageModelRouter(), new TestPathProvider(_testDir));
new DamageModelRouter(), new TestPathProvider(_testDir), new DefaultDefensePlanner(DefaultAmmunition.GetAll()));
engine.Initialize(_taskId);
engine.TimeScale = 4f;
@ -148,7 +148,7 @@ namespace CounterDrone.Core.Tests
});
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
new DamageModelRouter(), new TestPathProvider(_testDir));
new DamageModelRouter(), new TestPathProvider(_testDir), new DefaultDefensePlanner(DefaultAmmunition.GetAll()));
engine.Initialize(_taskId);
engine.TimeScale = 4f;

View File

@ -49,7 +49,7 @@ namespace CounterDrone.Core.Tests
private SimulationEngine RunSimulation(int maxTicks, float tickDt = 1f / 20f)
{
var engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths);
var engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefaultDefensePlanner(_ammoCatalog));
engine.Initialize(_taskId);
engine.TimeScale = 8f; // 8倍速加速

View File

@ -36,7 +36,7 @@ namespace CounterDrone.Core.Tests
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
_engine = new SimulationEngine(_scenarioService, new FrameDataStore(paths),
new DamageModelRouter(), paths);
new DamageModelRouter(), paths, new DefaultDefensePlanner(DefaultAmmunition.GetAll()));
}
public void Dispose()