From c6aee276fbe967a36be19e36d5e415feca7e4178 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sun, 14 Jun 2026 21:21:37 +0800 Subject: [PATCH] fix: weather display bug + report enrichment + destroy dedup Weather: WindDirection showed raw int (0-7), now translates to N/NE/E. Added scene type, time-of-day, humidity, pressure, scene dimensions to environment section. Report enrichment: threat section adds wingspan; deployment section shows platform type/position/ammo/munitions and detection equipment/control zones; cloud dispersal config section; event timeline includes coordinates; timeline section adds first/last launch, group spread, summary stats. Bug fix: DroneDestroyed event fired multiple times per tick when a drone was hit by multiple clouds simultaneously (was 2, now 1). Added break after destroy in cloud loop. Tests: 191 pass. --- .../Services/ReportGenerator.cs | 157 ++++++++++++++---- .../Simulation/SimulationEngine.cs | 7 +- 2 files changed, 128 insertions(+), 36 deletions(-) diff --git a/src/CounterDrone.Core/Services/ReportGenerator.cs b/src/CounterDrone.Core/Services/ReportGenerator.cs index 4447785..5373192 100644 --- a/src/CounterDrone.Core/Services/ReportGenerator.cs +++ b/src/CounterDrone.Core/Services/ReportGenerator.cs @@ -52,11 +52,16 @@ namespace CounterDrone.Core.Services sb.AppendLine(); sb.AppendLine($"| 参数 | 值 |"); sb.AppendLine($"|------|-----|"); + sb.AppendLine($"| 场景类型 | {TranslateScene((SceneType)scene.SceneType)} |"); + sb.AppendLine($"| 时间段 | {scene.TimeOfDay} |"); sb.AppendLine($"| 天气 | {TranslateWeather((WeatherType)scene.WeatherType)} |"); - sb.AppendLine($"| 风速 | {scene.WindSpeed} m/s |"); - sb.AppendLine($"| 风向 | {scene.WindDirection} |"); - sb.AppendLine($"| 温度 | {scene.Temperature}°C |"); - sb.AppendLine($"| 能见度 | {scene.Visibility} m |"); + sb.AppendLine($"| 风速 | {scene.WindSpeed:F1} m/s |"); + sb.AppendLine($"| 风向 | {TranslateWindDirection((WindDirection)scene.WindDirection)} |"); + sb.AppendLine($"| 温度 | {scene.Temperature:F1}°C |"); + sb.AppendLine($"| 湿度 | {scene.Humidity:F0}%RH |"); + sb.AppendLine($"| 气压 | {scene.Pressure:F0} hPa |"); + sb.AppendLine($"| 能见度 | {scene.Visibility:F0} m |"); + sb.AppendLine($"| 场景范围 | {scene.SceneWidth:F0} × {scene.SceneLength:F0} × {scene.SceneHeight:F0} m (W×L×H) |"); sb.AppendLine(); // === 三、威胁目标 === @@ -65,26 +70,36 @@ namespace CounterDrone.Core.Services sb.AppendLine($"| 参数 | 值 |"); sb.AppendLine($"|------|-----|"); sb.AppendLine($"| 类型 | {TranslateTarget((TargetType)(target?.TargetType ?? 0))} |"); - sb.AppendLine($"| 数量 | {totalDrones} 架 |"); - sb.AppendLine($"| 速度 | {target?.TypicalSpeed ?? 0} km/h |"); - sb.AppendLine($"| 高度 | {target?.TypicalAltitude ?? 0} m |"); sb.AppendLine($"| 动力 | {TranslatePower((PowerType)(target?.PowerType ?? 0))} |"); + sb.AppendLine($"| 数量 | {totalDrones} 架 |"); + sb.AppendLine($"| 翼展 | {target?.Wingspan ?? 0:F1} m |"); + sb.AppendLine($"| 速度 | {target?.TypicalSpeed ?? 0:F0} km/h |"); + sb.AppendLine($"| 高度 | {target?.TypicalAltitude ?? 0:F0} m |"); sb.AppendLine(); - // === 四、推荐方案 === + // === 四、云团抛撒配置 === + sb.AppendLine("## 四、云团抛撒配置"); + sb.AppendLine(); + sb.AppendLine($"| 参数 | 值 |"); + sb.AppendLine($"|------|-----|"); + sb.AppendLine($"| 气溶胶类型 | {TranslateAerosol((AerosolType)config.Cloud.AerosolType)} |"); + sb.AppendLine($"| 抛撒高度 | {config.Cloud.DisperseHeight:F0} m |"); + sb.AppendLine($"| 触发模式 | {TranslateTrigger((TriggerMode)config.Cloud.TriggerMode)} |"); + sb.AppendLine($"| 释放方式 | {TranslateRelease((ReleaseMode)config.Cloud.ReleaseMode)} |"); + sb.AppendLine($"| 扩散方向 | {config.Cloud.SpreadAngle:F0}° |"); + sb.AppendLine($"| 初始规模 | {config.Cloud.InitialScale:F0} m³ |"); + sb.AppendLine($"| 持续时间 | {config.Cloud.Duration:F0} s |"); + sb.AppendLine($"| 释放总量 | {config.Cloud.TotalAmount:F1} kg |"); + sb.AppendLine($"| 配置来源 | {config.Cloud.Source} |"); if (config.Cloud.Source == "Algorithm" && config.Cloud.EstimatedProbability.HasValue) { - sb.AppendLine("## 四、推荐方案"); - sb.AppendLine(); - sb.AppendLine($"| 参数 | 值 |"); - sb.AppendLine($"|------|-----|"); - sb.AppendLine($"| 发射方式 | {TranslateRelease((ReleaseMode)config.Cloud.ReleaseMode)} |"); - sb.AppendLine($"| 弹药数 | {config.Cloud.SalvoRounds} 发 |"); + sb.AppendLine($"| **算法推荐** | |"); + sb.AppendLine($"| 推荐弹药数 | {config.Cloud.SalvoRounds} 发 |"); sb.AppendLine($"| 云团间距 | {config.Cloud.SalvoSpacing:F0} m |"); - sb.AppendLine($"| 拦截概率 | {config.Cloud.EstimatedProbability.Value:P0} |"); + sb.AppendLine($"| 拦截概率估计 | {config.Cloud.EstimatedProbability.Value:P0} |"); sb.AppendLine($"| 抛撒时机 | {config.Cloud.RecommendedTiming:F0}s |"); - sb.AppendLine(); } + sb.AppendLine(); // 构建 PlatformId → 火力单元 映射(去重,五和六共用) var platToEquip = new Dictionary(); @@ -111,11 +126,52 @@ namespace CounterDrone.Core.Services // === 五、我方部署 === sb.AppendLine("## 五、我方部署"); sb.AppendLine(); - sb.AppendLine($"| 火力单元 | 发射数 |"); - sb.AppendLine($"|------|------|"); - foreach (var kv in unitLaunchCounts.OrderBy(kv => kv.Key)) - sb.AppendLine($"| #{kv.Key} | {kv.Value} |"); + sb.AppendLine($"### 发射平台"); sb.AppendLine(); + sb.AppendLine($"| # | 平台类型 | 位置 (X,Y,Z) | 弹药类型 | 挂载量 | 发射数 |"); + sb.AppendLine($"|---|---------|-------------|---------|:------:|:------:|"); + int unitNum2 = 0; + foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform)) + { + int channels = (eq.GunCount ?? 1) * (eq.ChannelsPerGun ?? 1); + for (int j = 0; j < eq.Quantity; j++) + { + unitNum2++; + int fired = unitLaunchCounts.GetValueOrDefault(unitNum2, 0); + string ptype = ((PlatformType?)(eq.PlatformType ?? 0)) == PlatformType.AirBased ? "空基" : "地基"; + string ammo = eq.AerosolType.HasValue ? TranslateAerosol((AerosolType)eq.AerosolType) : "-"; + sb.AppendLine($"| #{unitNum2} | {ptype} | ({eq.PositionX + j * 50:F0}, {eq.PositionY:F0}, {eq.PositionZ:F0}) | {ammo} | {eq.MunitionCount ?? channels} | {fired} |"); + } + } + sb.AppendLine(); + // 探测设备 + var detections = config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.Detection).ToList(); + if (detections.Count > 0) + { + sb.AppendLine($"### 探测设备"); + sb.AppendLine(); + sb.AppendLine($"| # | 位置 (X,Y,Z) | 探测半径 | 数量 |"); + sb.AppendLine($"|---|-------------|---------|:----:|"); + int detIdx = 0; + foreach (var d in detections) + { + for (int j = 0; j < d.Quantity; j++) + sb.AppendLine($"| D{++detIdx} | ({d.PositionX:F0}, {d.PositionY:F0}, {d.PositionZ:F0}) | {(d.DetectionRadius ?? 0):F0} m | 1 |"); + } + sb.AppendLine(); + } + // 管控区域 + if (config.ControlZones.Count > 0) + { + sb.AppendLine($"### 管控区域"); + sb.AppendLine(); + sb.AppendLine($"| # | 名称 | 高度范围 |"); + sb.AppendLine($"|---|------|---------|"); + int zIdx = 0; + foreach (var z in config.ControlZones) + sb.AppendLine($"| Z{++zIdx} | {z.Name} | {(int)z.MinAltitude}-{(int)z.MaxAltitude} m |"); + sb.AppendLine(); + } // === 六、事件时序 === sb.AppendLine("## 六、事件时序"); @@ -127,34 +183,47 @@ namespace CounterDrone.Core.Services { string unitCell = e.Type == SimEventType.MunitionLaunched && platToEquip.TryGetValue(e.SourceId, out var u) ? $"#{u}" : "-"; - var desc = e.Type switch + // 编号前缀 + 引擎生成的 Description(含坐标等调试信息) + string prefix = e.Type switch { SimEventType.MunitionLaunched => $"第 {++munitionIdx} 发", SimEventType.CloudGenerated => $"云团 #{++cloudIdx}", - SimEventType.DroneDestroyed => "目标被摧毁", - SimEventType.DroneReachedTarget => "目标抵达终点", - SimEventType.ZoneIntruded => "侵入管控区", - SimEventType.SimulationEnd => "仿真结束", - _ => e.Description, + _ => string.Empty, }; + string desc = string.IsNullOrEmpty(prefix) ? e.Description : $"{prefix} · {e.Description}"; sb.AppendLine($"| {e.OccurredAt:F2} | {TranslateEvent(e.Type)} | {desc} | {unitCell} |"); } sb.AppendLine(); // === 七、时间线 === - var launchTime = events.Where(e => e.Type == SimEventType.MunitionLaunched).Select(e => e.OccurredAt).DefaultIfEmpty(0).Min(); - var cloudTime = events.Where(e => e.Type == SimEventType.CloudGenerated).Select(e => e.OccurredAt).DefaultIfEmpty(0).Min(); - var destroyTime = events.Where(e => e.Type == SimEventType.DroneDestroyed).Select(e => e.OccurredAt).DefaultIfEmpty(0).Min(); + var launches = events.Where(e => e.Type == SimEventType.MunitionLaunched).Select(e => e.OccurredAt).OrderBy(t => t).ToList(); + var clouds = events.Where(e => e.Type == SimEventType.CloudGenerated).Select(e => e.OccurredAt).OrderBy(t => t).ToList(); + var destroys = events.Where(e => e.Type == SimEventType.DroneDestroyed).Select(e => e.OccurredAt).OrderBy(t => t).ToList(); + float launchFirst = launches.DefaultIfEmpty(0).FirstOrDefault(); + float launchLast = launches.DefaultIfEmpty(0).LastOrDefault(); + float cloudFirst = clouds.DefaultIfEmpty(0).FirstOrDefault(); + float destroyFirst = destroys.DefaultIfEmpty(0).FirstOrDefault(); sb.AppendLine("## 七、时间线"); sb.AppendLine(); sb.AppendLine($"| 阶段 | 时间 | 耗时 |"); sb.AppendLine($"|------|------|------|"); - if (launchTime > 0) - sb.AppendLine($"| 发射 → 云团生成 | {launchTime:F1}s → {cloudTime:F1}s | {(cloudTime - launchTime):F1}s(弹药飞行) |"); - if (destroyTime > 0) - sb.AppendLine($"| 云团生成 → 击毁 | {cloudTime:F1}s → {destroyTime:F1}s | {(destroyTime - cloudTime):F1}s(暴露毁伤) |"); + if (launchFirst > 0) + { + sb.AppendLine($"| 首发发射 | {launchFirst:F1}s | - |"); + if (launches.Count > 1) + sb.AppendLine($"| 末发发射 | {launchLast:F1}s | {launchLast - launchFirst:F1}s(弹群展开) |"); + sb.AppendLine($"| 发射 → 云团生成 | {launchFirst:F1}s → {cloudFirst:F1}s | {cloudFirst - launchFirst:F1}s(弹药飞行) |"); + } + if (destroyFirst > 0) + sb.AppendLine($"| 云团生成 → 首个击毁 | {cloudFirst:F1}s → {destroyFirst:F1}s | {destroyFirst - cloudFirst:F1}s(暴露毁伤) |"); sb.AppendLine($"| 总耗时 | 0 → {simulationDuration:F1}s | {simulationDuration:F1}s |"); sb.AppendLine(); + // 汇总统计(调试用) + sb.AppendLine("**统计汇总**:"); + sb.AppendLine($"- 弹药发射 {launches.Count} 发,云团生成 {clouds.Count} 朵,击毁 {destroys.Count} 架"); + if (launchFirst > 0 && launchLast > 0) + sb.AppendLine($"- 发射窗口 {launchLast - launchFirst:F1}s,弹药飞行 {(cloudFirst - launchFirst):F1}s"); + sb.AppendLine(); return sb.ToString(); } @@ -190,6 +259,28 @@ namespace CounterDrone.Core.Services WeatherType.Night => "夜间", _ => w.ToString(), }; + private static string TranslateWindDirection(WindDirection d) => d switch + { + WindDirection.N => "北 (N)", WindDirection.NE => "东北 (NE)", + WindDirection.E => "东 (E)", WindDirection.SE => "东南 (SE)", + WindDirection.S => "南 (S)", WindDirection.SW => "西南 (SW)", + WindDirection.W => "西 (W)", WindDirection.NW => "西北 (NW)", + _ => d.ToString(), + }; + + private static string TranslateScene(SceneType s) => s switch + { + SceneType.Urban => "城市", SceneType.Plain => "平原", + SceneType.Mountain => "山区", SceneType.Coast => "海岸", + _ => s.ToString(), + }; + + private static string TranslateTrigger(TriggerMode t) => t switch + { + TriggerMode.Time => "时间触发", TriggerMode.Area => "区域触发", + TriggerMode.Manual => "手动触发", _ => t.ToString(), + }; + private static string TranslateTarget(TargetType t) => t switch { TargetType.Rotor => "旋翼", TargetType.FixedWing => "固定翼", diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index 4141624..c87dc2c 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -182,7 +182,7 @@ namespace CounterDrone.Core.Simulation fe.FireTime); _munitions.Add(m); OnMunitionLaunched?.Invoke(m); - frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = fe.FireTime, SourceId = platform.Id, Description = $"计划发射 {m.Id}" }); + frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = fe.FireTime, SourceId = platform.Id, Description = $"计划发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" }); } } @@ -216,7 +216,7 @@ namespace CounterDrone.Core.Simulation _clouds.Add(cloud); _munitions.Remove(m); OnCloudGenerated?.Invoke(cloud); - frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = m.ArrivalTime, Description = "云团生成" }); + frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = m.ArrivalTime, Description = $"云团生成 @({m.PosX:F0},{m.PosY:F0},{m.PosZ:F0})" }); } } @@ -271,7 +271,8 @@ namespace CounterDrone.Core.Simulation if (drone.Status == DroneStatus.Destroyed) { OnDroneDestroyed?.Invoke(drone); - frameEvents.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁" }); + frameEvents.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁 @({drone.PosX:F0},{drone.PosY:F0},{drone.PosZ:F0})" }); + break; // 已击毁,不再判定本 tick 其他云团,避免重复触发 } } }