引擎扩展性与集成设计深度剖析:从 14 个扩展点到 DIS→HLA 演进之路

第8章 扩展性与集成设计

本章是本文档相对于模型分析报告的主要增量内容。扩展性设计决定了引擎能否被嵌入更大的仿真与分析体系,也是评估其工程成熟度的主要依据。

章节导读:本章按「总览---机制---集成---兼容」的脉络展开。8.1 先给出 14 个扩展点接口的全景清单,帮助读者建立整体边界;8.2 深入 Lua 脚本子系统,剖析沙箱上下文注入与四层 API 分层,是行为扩展的核心;8.3 讲解事件导出子系统的速率节流与声明式能力协商;8.4 聚焦分布式仿真接口 ISimConnector,对比 DIS 与 HLA 并给出演进方案;8.5 介绍想定级特性开关的向后兼容机制;8.6 汇总核心结论。关注行为扩展的读者可直奔 8.2,关注对外集成的读者建议通读 8.3 至 8.5。

本章摘要:本章围绕引擎的扩展性与集成设计展开,系统梳理了 14 个显式扩展点接口,并深入剖析三大核心子系统:Lua 脚本子系统采用「动态函数库---执行环境---领域 API---对象包装器」四层架构,以上下文注入与包装器投影实现行为扩展与版本兼容;事件导出子系统通过逐事件类型速率节流与声明式能力协商,在数据完整性与文件体积间取得平衡;分布式仿真接口 ISimConnector 当前为 DIS 量身定制,其两态状态机、水声辐射枚举位与弱类型字典载荷均为 DIS 特有设计,文中给出了向 HLA 演进的最小改造方案(新增连接器类型、扩展状态集、以强类型载荷替换弱类型字典)。最后,ScenarioFeatureOption 的 16 项想定级特性开关随想定持久化,保证了新模型加入后既有想定行为可复现,是引擎处理十余年演进与向后兼容矛盾的根本机制。

8.1 扩展点总览

内核共定义 14 个接口,构成全部显式扩展点(A 级)。

表 8-1 Command_Core 接口清单

接口 | 文件 | 性质 | 作用

ITerrainProvider | ITerrainProvider.cs | 数据提供者 | 地形高程数据源,可替换实现(见 5.2 节)

IEventExporter | IEventExporter.cs | 输出扩展 | 仿真事件导出,10 种目标格式(见 8.3 节)

ISimConnector | ISimConnector.cs | 互操作 | 分布式仿真连接器,当前实现为 DIS(见 8.4 节)

IFlier IFlier.cs 能力契约 可飞行实体------飞机与在飞弹药共同实现

IBoat IBoat.cs 能力契约 水面航行能力

IMobileGroundUnit IMobileGroundUnit.cs 能力契约 地面机动能力

IGeopoint IGeopoint.cs 能力契约 具备地理坐标------单元、参考点、航路点、区域顶点统一实现

ICargoHost / ICargoClient ICargoHost.cs / ICargoClient.cs 能力契约 货物承载方与被载方,支撑两栖投送与后勤运输

IRecurringMission IRecurringMission.cs 能力契约 可重复执行的任务

IEmitter RadarModel.cs 能力契约 电磁辐射源------雷达、干扰机、通信设备统一实现,ESM 探测据此统一处理

IDoubleMatrix RadarModel.cs 数值抽象 双精度矩阵,天线方向图与传播计算用

I_CacheableRow DBCache.cs 数据抽象 可缓存的数据库行

GInterface8 GInterface8.cs (混淆残留) 名称已被混淆,无法判定原始语义

接口可分为三类(B 级):能力契约类(IFlier、IGeopoint、IEmitter 等)用于横切多条继承线,弥补单继承的表达力不足------IEmitter 尤为关键,它使 ESM 探测无需关心辐射源是雷达还是通信电台;数据提供者类(ITerrainProvider)用于替换数据源;输出与互操作类(IEventExporter、ISimConnector)用于对外集成。真正意义上"允许第三方注入新行为"的只有后两类,因为前者的实现全部在内核内部(C 级)。

14 个扩展点的分类关系如下图所示:
#mermaid-svg-U7uJ0C4v4lC3U7TY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-U7uJ0C4v4lC3U7TY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-U7uJ0C4v4lC3U7TY .error-icon{fill:#552222;}#mermaid-svg-U7uJ0C4v4lC3U7TY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-U7uJ0C4v4lC3U7TY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-U7uJ0C4v4lC3U7TY .marker.cross{stroke:#333333;}#mermaid-svg-U7uJ0C4v4lC3U7TY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-U7uJ0C4v4lC3U7TY p{margin:0;}#mermaid-svg-U7uJ0C4v4lC3U7TY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-U7uJ0C4v4lC3U7TY .cluster-label text{fill:#333;}#mermaid-svg-U7uJ0C4v4lC3U7TY .cluster-label span{color:#333;}#mermaid-svg-U7uJ0C4v4lC3U7TY .cluster-label span p{background-color:transparent;}#mermaid-svg-U7uJ0C4v4lC3U7TY .label text,#mermaid-svg-U7uJ0C4v4lC3U7TY span{fill:#333;color:#333;}#mermaid-svg-U7uJ0C4v4lC3U7TY .node rect,#mermaid-svg-U7uJ0C4v4lC3U7TY .node circle,#mermaid-svg-U7uJ0C4v4lC3U7TY .node ellipse,#mermaid-svg-U7uJ0C4v4lC3U7TY .node polygon,#mermaid-svg-U7uJ0C4v4lC3U7TY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-U7uJ0C4v4lC3U7TY .rough-node .label text,#mermaid-svg-U7uJ0C4v4lC3U7TY .node .label text,#mermaid-svg-U7uJ0C4v4lC3U7TY .image-shape .label,#mermaid-svg-U7uJ0C4v4lC3U7TY .icon-shape .label{text-anchor:middle;}#mermaid-svg-U7uJ0C4v4lC3U7TY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-U7uJ0C4v4lC3U7TY .rough-node .label,#mermaid-svg-U7uJ0C4v4lC3U7TY .node .label,#mermaid-svg-U7uJ0C4v4lC3U7TY .image-shape .label,#mermaid-svg-U7uJ0C4v4lC3U7TY .icon-shape .label{text-align:center;}#mermaid-svg-U7uJ0C4v4lC3U7TY .node.clickable{cursor:pointer;}#mermaid-svg-U7uJ0C4v4lC3U7TY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-U7uJ0C4v4lC3U7TY .arrowheadPath{fill:#333333;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-U7uJ0C4v4lC3U7TY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U7uJ0C4v4lC3U7TY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-U7uJ0C4v4lC3U7TY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U7uJ0C4v4lC3U7TY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-U7uJ0C4v4lC3U7TY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-U7uJ0C4v4lC3U7TY .cluster text{fill:#333;}#mermaid-svg-U7uJ0C4v4lC3U7TY .cluster span{color:#333;}#mermaid-svg-U7uJ0C4v4lC3U7TY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-U7uJ0C4v4lC3U7TY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-U7uJ0C4v4lC3U7TY rect.text{fill:none;stroke-width:0;}#mermaid-svg-U7uJ0C4v4lC3U7TY .icon-shape,#mermaid-svg-U7uJ0C4v4lC3U7TY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U7uJ0C4v4lC3U7TY .icon-shape p,#mermaid-svg-U7uJ0C4v4lC3U7TY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-U7uJ0C4v4lC3U7TY .icon-shape rect,#mermaid-svg-U7uJ0C4v4lC3U7TY .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U7uJ0C4v4lC3U7TY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-U7uJ0C4v4lC3U7TY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-U7uJ0C4v4lC3U7TY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 实现全部在内核内部
仅替换数据源
14 个显式扩展点
能力契约类
数据提供者类
输出与互操作类
IFlier / IBoat / IMobileGroundUnit
IGeopoint / IEmitter / IRecurringMission
ICargoHost / ICargoClient
ITerrainProvider(可替换数据源)
IDoubleMatrix / I_CacheableRow(数值与数据抽象)
IEventExporter(输出扩展)
ISimConnector(互操作)
允许第三方注入新行为
不开放给第三方

8.2 Lua 脚本子系统

脚本子系统位于 Command_Core\Lua\,共 32 个文件、合计约 1.6 MB 源码,基于 NLua + KeraLua 绑定(A 级)。这是引擎最主要的行为扩展机制。

8.2.1 沙箱设计

LuaSandBox(147.9 KB)是脚本执行环境,其成员揭示了设计要点(A 级):

private NLua.Lua lua; // 解释器实例

// ------ 上下文注入:脚本可直接访问触发时的现场对象 ------

public object UnitX; // 触发脚本的单元(弱类型)

public ActiveUnit UnitY; // 触发脚本的单元(强类型)

public Contact UnitC; // 相关接触

public List SensorsThatMadeDetection; // 达成探测的传感器集合

public SimEvent EventX; // 触发事件

public LuaEnuNames enumTable; // 枚举名称表(脚本可用名而非数字)

// ------ 执行控制 ------

public object ScriptLockObj; // 脚本执行锁(串行化)

public bool RunInteractive; // 交互式(控制台)/ 非交互式

public static bool _lua_event; // 事件脚本开关

public static bool _lua_console; // 控制台开关

// ------ 诊断 ------

public string currentFunction; // 当前执行的函数

public int currentLine; // 当前行号

public string lastError; // 最近一次错误

public event LuaPrintEventHandler LuaPrint; // print() 输出重定向

public CMANO luaDynamicFunctions; // 动态函数注册表

三个设计要点值得指出:

• 上下文注入而非全局查询:脚本被触发时,现场对象(触发单元、相关接触、达成探测的传感器)已作为字段直接可用。这使得"某单元被雷达探测到时执行某动作"这类脚本无需自行检索对象,降低了想定作者的使用门槛。

• ScriptLockObj 串行化执行:脚本执行被显式加锁,不与并行计算同时进行。这是必要的------脚本可任意修改想定状态,若允许其与 Parallel 计算并发,将无法保证一致性(B 级)。

• 诊断信息作为一等公民:currentFunction、currentLine、lastError 三个字段常驻,配合 LuaError 异常类型与 LuaPrint 输出重定向,构成完整的脚本调试支持。工程另集成了 ScintillaNET 提供编辑器语法高亮。

8.2.2 API 分层

脚本 API 采用包装器(Wrapper)模式:不直接暴露内核对象,而是为每类对象提供一个受控的投影类型(A 级)。

表 8-2 Lua 子系统文件构成

层次 文件 规模 职责

动态函数库 PrivateMethods.cs 417.1 KB 脚本可调用的全部内部函数实现,是子系统中最大的文件

执行环境 LuaSandBox.cs 147.9 KB 沙箱、上下文注入、执行控制、诊断

领域 API LuaDoctrine.cs 187.2 KB 条令读写 API

领域 API LuaEvent.cs 152.1 KB 事件(触发器/条件/动作)API

领域 API LuaMission.cs 119.0 KB 任务管理 API

领域 API LuaReferencePoint.cs 31.0 KB 参考点 API

对象包装器 LuaWrapper_ActiveUnit.cs (+_SE) 62.3 + 56.9 KB 单元投影,含"选定单元"变体

对象包装器 LuaWrapper_Mission.cs 45.4 KB 任务投影

对象包装器 LuaWrapper_Contact.cs 26.4 KB 接触投影

对象包装器 LuaWrapper_Side.cs 22.7 KB 阵营投影

对象包装器 LuaWrapper_Zone.cs 16.3 KB 区域投影

对象包装器 LuaWrapper_Device_Sensor / _Mount / _Magazine / _Weapon 4.6--8.0 KB 部件投影

对象包装器 LuaWrapper_Waypoint / _Group / _Loadout / _Cargo / _Facility / _Flight / _Geopoint / _Operation / _Doctrine / _Event / _ReferencePoint / _Scenario / _Serial 3.3--10.0 KB 其余对象投影

基础设施 CMANO.cs / LuaEnuNames.cs / LuaError.cs / LuaImportExport.cs / LuaUtility.cs / LuaSAO.cs / LuaSide.cs 0.2--11.5 KB 函数注册、枚举名表、错误类型、导入导出、特殊行动(Special Actions)

包装器模式的收益是版本兼容性(C 级):内核对象的字段可以随版本重构,只要包装器保持接口不变,想定作者写的脚本就不会失效。考虑到 CMO 有大量社区想定依赖脚本,这是一项必要的投入。

下面以一个事件动作脚本为例,展示上下文注入与动态函数库的配合方式。该脚本挂在 EventAction_LuaScript 上,当某事件触发时,读取触发单元坐标并调用动态函数库中的函数:

lua 复制代码
-- 事件动作脚本:读取触发单元坐标并调用动态函数库
-- 接入点:EventAction_LuaScript(事件动作,想定编排层)

-- 1. 上下文注入:UnitX 与 EventX 由 LuaSandBox 直接注入,无需自行检索
local unit = UnitX          -- 触发脚本的单元(弱类型投影,对象包装器层)
local evt  = EventX         -- 触发事件(领域 API 层:LuaEvent)

-- 2. 通过包装器投影读取单元坐标(对象包装器层:LuaWrapper_ActiveUnit)
local lat = unit:GetLatitude()    -- 纬度
local lon = unit:GetLongitude()   -- 经度

-- 3. 调用动态函数库中的函数(动态函数库层:PrivateMethods.cs)
--    例如向日志系统写入一条带坐标的记录
luaDynamicFunctions:LogEvent(string.format(
    "事件 %s 触发于单元 %s,坐标:%.4f, %.4f",
    evt.Name, unit.Name, lat, lon))

-- 4. 可选:按事件类型做分支处理(领域 API 层:LuaEvent)
if evt.Type == "WeaponFired" then
    luaDynamicFunctions:RecordWeaponLaunch(unit, lat, lon)
end

这段脚本体现了 8.2.1 节所述的两个设计要点:UnitX 与 EventX 由沙箱在触发时直接注入,脚本无需自行检索对象;而 luaDynamicFunctions 则把脚本可调用的内部函数统一收口在动态函数库中,与对象包装器层形成清晰的分层边界。

脚本的接入点有四处(A 级):EventCondition_LuaScript(事件条件)、EventAction_LuaScript(事件动作)、Mission 的 MissionStartTrigger_LUA 与 MissionCompletedTrigger_LUA(任务触发)、以及 ActiveUnit.HasCustomOODA(单元决策接管)。前三者是想定编排层面的扩展,最后一项则深入到单元行为层面。

下面再以一个「单元决策接管」脚本为例,展示 ActiveUnit.HasCustomOODA 的接入方式。该脚本在单元每次决策循环(OODA)时被调用,通过上下文注入的 UnitX 读取当前单元状态,并调用动态函数库中的决策函数:

lua 复制代码
-- 单元决策接管脚本:接管 ActiveUnit 的 OODA 决策循环
-- 接入点:ActiveUnit.HasCustomOODA(单元决策接管,单元行为层)

-- 1. 上下文注入:UnitX 由 LuaSandBox 在决策循环触发时直接注入
local unit = UnitX          -- 当前决策单元(弱类型投影,对象包装器层)

-- 2. 通过包装器投影读取当前单元状态(对象包装器层:LuaWrapper_ActiveUnit)
local fuel   = unit:GetFuel()          -- 剩余燃油
local speed  = unit:GetSpeed()         -- 当前速度
local threat = unit:GetThreatLevel()   -- 威胁等级(由动态函数库计算)

-- 3. 调用动态函数库中的决策函数(动态函数库层:PrivateMethods.cs)
--    根据状态决定是否执行规避机动
if threat > 0.8 and fuel > 0.2 then
    luaDynamicFunctions:ExecuteEvasiveManeuver(unit, "break_turn")
elseif fuel < 0.1 then
    luaDynamicFunctions:RequestRefuel(unit)   -- 燃油告急,请求加油
else
    luaDynamicFunctions:ContinueCurrentCourse(unit)
end

-- 4. 返回 true 表示本帧决策已由脚本接管,内核不再执行默认 AI
return true

对应的 C# 注册代码把该脚本挂接到单元的决策循环上:

csharp 复制代码
// ------ C# 注册代码:把 Lua 脚本挂接到 ActiveUnit 的 OODA 决策循环 ------

// 1. 在单元初始化时,为指定单元启用自定义 OODA 接管
var unit = scenario.GetUnit("F-16A_01");
unit.HasCustomOODA = true;   // 开启决策接管,内核将调用脚本而非默认 AI

// 2. 把 Lua 脚本文件加载进沙箱,并绑定到该单元的决策入口
//    脚本路径指向上面那段 Lua 代码;沙箱负责注入 UnitX 等上下文
luaSandBox.LoadScript("Scripts/UnitOODA_Override.lua");
luaSandBox.AttachToUnit(unit);   // 绑定后,每次 OODA 循环都会执行该脚本

// 3. 执行流程说明:
//    - 内核进入该单元的决策循环时,检测到 HasCustomOODA == true
//    - 沙箱注入 UnitX(当前单元)并执行脚本
//    - 脚本读取状态、调用动态函数库决策,返回 true 表示接管完成
//    - 返回 false 则回退到内核默认 AI,实现"部分接管"的降级策略

这段示例与事件动作脚本的差异在于接入层级(C 级):事件动作脚本挂在想定编排层(EventAction_LuaScript),由事件触发、一次性执行;而 ActiveUnit.HasCustomOODA 深入到单元行为层,在每个决策循环被反复调用,因此脚本末尾的 return true 是关键的接管语义------它告诉内核本帧决策已由脚本完成,无需再走默认 AI。同时,脚本通过 UnitX 读取的燃油、速度、威胁等级等状态,正是 8.2.1 节「上下文注入而非全局查询」设计在单元行为层面的直接体现。

Lua 脚本子系统的四层 API 架构与上下文注入路径如下图所示:
#mermaid-svg-cwrwELERtp3fc1hb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cwrwELERtp3fc1hb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cwrwELERtp3fc1hb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cwrwELERtp3fc1hb .error-icon{fill:#552222;}#mermaid-svg-cwrwELERtp3fc1hb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cwrwELERtp3fc1hb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cwrwELERtp3fc1hb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cwrwELERtp3fc1hb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cwrwELERtp3fc1hb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cwrwELERtp3fc1hb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cwrwELERtp3fc1hb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cwrwELERtp3fc1hb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cwrwELERtp3fc1hb .marker.cross{stroke:#333333;}#mermaid-svg-cwrwELERtp3fc1hb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cwrwELERtp3fc1hb p{margin:0;}#mermaid-svg-cwrwELERtp3fc1hb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cwrwELERtp3fc1hb .cluster-label text{fill:#333;}#mermaid-svg-cwrwELERtp3fc1hb .cluster-label span{color:#333;}#mermaid-svg-cwrwELERtp3fc1hb .cluster-label span p{background-color:transparent;}#mermaid-svg-cwrwELERtp3fc1hb .label text,#mermaid-svg-cwrwELERtp3fc1hb span{fill:#333;color:#333;}#mermaid-svg-cwrwELERtp3fc1hb .node rect,#mermaid-svg-cwrwELERtp3fc1hb .node circle,#mermaid-svg-cwrwELERtp3fc1hb .node ellipse,#mermaid-svg-cwrwELERtp3fc1hb .node polygon,#mermaid-svg-cwrwELERtp3fc1hb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cwrwELERtp3fc1hb .rough-node .label text,#mermaid-svg-cwrwELERtp3fc1hb .node .label text,#mermaid-svg-cwrwELERtp3fc1hb .image-shape .label,#mermaid-svg-cwrwELERtp3fc1hb .icon-shape .label{text-anchor:middle;}#mermaid-svg-cwrwELERtp3fc1hb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cwrwELERtp3fc1hb .rough-node .label,#mermaid-svg-cwrwELERtp3fc1hb .node .label,#mermaid-svg-cwrwELERtp3fc1hb .image-shape .label,#mermaid-svg-cwrwELERtp3fc1hb .icon-shape .label{text-align:center;}#mermaid-svg-cwrwELERtp3fc1hb .node.clickable{cursor:pointer;}#mermaid-svg-cwrwELERtp3fc1hb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cwrwELERtp3fc1hb .arrowheadPath{fill:#333333;}#mermaid-svg-cwrwELERtp3fc1hb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cwrwELERtp3fc1hb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cwrwELERtp3fc1hb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cwrwELERtp3fc1hb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cwrwELERtp3fc1hb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cwrwELERtp3fc1hb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cwrwELERtp3fc1hb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cwrwELERtp3fc1hb .cluster text{fill:#333;}#mermaid-svg-cwrwELERtp3fc1hb .cluster span{color:#333;}#mermaid-svg-cwrwELERtp3fc1hb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cwrwELERtp3fc1hb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cwrwELERtp3fc1hb rect.text{fill:none;stroke-width:0;}#mermaid-svg-cwrwELERtp3fc1hb .icon-shape,#mermaid-svg-cwrwELERtp3fc1hb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cwrwELERtp3fc1hb .icon-shape p,#mermaid-svg-cwrwELERtp3fc1hb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cwrwELERtp3fc1hb .icon-shape rect,#mermaid-svg-cwrwELERtp3fc1hb .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cwrwELERtp3fc1hb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cwrwELERtp3fc1hb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cwrwELERtp3fc1hb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Lua 脚本子系统(Command_Core\Lua)
上下文注入
脚本直接访问现场对象
执行控制
诊断
包装器投影隔离内核对象
动态函数库

PrivateMethods.cs(417.1 KB)
执行环境

LuaSandBox.cs(147.9 KB)
领域 API

LuaDoctrine / LuaEvent / LuaMission
对象包装器

LuaWrapper_ActiveUnit / _Mission / _Contact
UnitX / UnitY / UnitC / EventX

SensorsThatMadeDetection
ScriptLockObj 串行化执行
currentFunction / currentLine / lastError
内核对象(可随版本重构)

8.3 事件导出子系统

IEventExporter 是全工程契约最详尽的接口------仅接口定义即 210 行,含 3 个嵌套枚举、2 个嵌套类型、25 个属性与 6 个方法(A 级)。其详尽程度本身即说明该能力的定位。

8.3.1 事件类型与导出目标

表 8-3 ExportedEventType 可导出事件(19 项)

事件 含义 事件 含义

WeaponFired 武器发射 Explosion 爆炸

WeaponEndgame 武器末段结局 AirOps 航空作业

WeaponImpact 武器命中 DockingOps 靠泊作业

UnitDestroyed 单元被摧毁 WaterSplash 入水溅落

UnitDamaged 单元受损 ContactPositions 接触位置

UnitPositions 单元位置 SimExecution 仿真执行状态

UnitPositions_Destruction 单元位置(毁灭) FuelConsumed 燃油消耗

SensorDetectionAttempt 传感器探测尝试 FuelTransfer 燃油转移

EngagementCycle 交战周期 CargoTransfer 货物转移

SensorDetectionAttempt 值得特别注意:导出的是探测尝试而非探测结果,且接口另设 ExportSensorDetectionSuccess 与 ExportSensorDetectionFailure 两个独立开关。这意味着分析者可以获得"为什么没探测到"的记录------这是探测性能分析所必需的,也是普通推演软件不会提供的能力(C 级)。

表 8-4 EventExporterType 导出目标(10 项)

目标 说明

XMLFile / CSVFile 文件导出,通用分析

SQLite / MSAccess 数据库导出,支持结构化查询与批量统计

TacviewRealtime / TacviewPipe Tacview 战术分析软件,实时流与管道两种方式

SIMDIS 美海军研究实验室(NRL)的 SIMDIS 三维态势分析工具

None / const_4 / const_5 / const_7 未启用或名称已被混淆的枚举位

8.3.2 输出速率节流

导出子系统的核心设计是按事件类型分别设定输出速率(A 级)。

void SetOutputRate(ExportedEventType type, EventExportOutputRate rate);

bool ShouldOutputNow(ExportedEventType type, Scenario scen);

enum EventExportOutputRate {

Unknown = -1,

Continuous, // 每脉冲

Secondx1, Secondx2, Secondx5, // 1/2/5 秒

Minutex1, Minutex5, // 1/5 分

Hourx1, Hourx6, Hourx12, Hourx24, // 1/6/12/24 小时

SimSpeed // 跟随仿真速度自适应

}

这套速率枚举与 4.3 节的 11 级时间刻度同构------秒、5 秒、分、5 分、小时、6 小时、12 小时、24 小时。因此 ShouldOutputNow 的实现只需读取 Scenario 上对应的刻度变化标志,无需自行计时(B 级)。这是一处很经济的复用:时间轮既服务于计算分频,也服务于输出节流。

速率控制的必要性显而易见:UnitPositions 若按 Continuous 导出,在千单元想定下每秒产生上千条记录;而 UnitDestroyed 这类稀疏事件则必须逐条捕获。允许逐事件类型设定速率,使分析者可以在数据完整性与文件体积之间自行权衡(C 级)。

8.3.3 其余契约要点

表 8-5 IEventExporter 关键属性

属性 类型 设计含义

QueueLength int 待导出队列长度,供背压监测

UsesRAMQueue bool 内存队列 / 磁盘队列(DiskQueue)二选一,见 5.5 节

RunMode EventExporterRunMode Interactive / NonInteractive,无界面批量运行时切换

UseZeroHour bool 时间基准是否用想定 ZeroHour 相对时间,便于多次运行结果对齐比较

RequiresHeartbeatForStaticUnits bool 静止单元是否也需周期性上报------部分下游工具据心跳判定实体存活

UsesUnitMissionAndStatus / UsesUnitDamage bool ×2 声明式能力协商:导出器告知内核自己需要哪些扩展信息,内核据此决定是否付出采集代价

ExportXxx(14 个布尔开关) bool 逐事件类型的启用开关,与速率设定正交

UsesUnitMissionAndStatus 与 UsesUnitDamage 这两个属性体现了一种声明式能力协商:内核不无条件采集全部信息,而是询问导出器需要什么,按需付出代价。这在高频导出场景下是有意义的优化(C 级)。

事件通知对象 EventExportNotification 标记为 Serializable,并提供 ToByteArray / FromByteArray 方法(内部经 BinaryFormatter + RecyclableMemoryStream)。这说明导出通路支持跨进程传输------EventExporter_TacviewPipe 即通过管道把事件送往外部进程(B 级)。

事件导出子系统的处理流程与速率节流机制如下图所示:
#mermaid-svg-XyjHOcqd5pp8GQCu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XyjHOcqd5pp8GQCu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XyjHOcqd5pp8GQCu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XyjHOcqd5pp8GQCu .error-icon{fill:#552222;}#mermaid-svg-XyjHOcqd5pp8GQCu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XyjHOcqd5pp8GQCu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XyjHOcqd5pp8GQCu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XyjHOcqd5pp8GQCu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XyjHOcqd5pp8GQCu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XyjHOcqd5pp8GQCu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XyjHOcqd5pp8GQCu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XyjHOcqd5pp8GQCu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XyjHOcqd5pp8GQCu .marker.cross{stroke:#333333;}#mermaid-svg-XyjHOcqd5pp8GQCu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XyjHOcqd5pp8GQCu p{margin:0;}#mermaid-svg-XyjHOcqd5pp8GQCu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XyjHOcqd5pp8GQCu .cluster-label text{fill:#333;}#mermaid-svg-XyjHOcqd5pp8GQCu .cluster-label span{color:#333;}#mermaid-svg-XyjHOcqd5pp8GQCu .cluster-label span p{background-color:transparent;}#mermaid-svg-XyjHOcqd5pp8GQCu .label text,#mermaid-svg-XyjHOcqd5pp8GQCu span{fill:#333;color:#333;}#mermaid-svg-XyjHOcqd5pp8GQCu .node rect,#mermaid-svg-XyjHOcqd5pp8GQCu .node circle,#mermaid-svg-XyjHOcqd5pp8GQCu .node ellipse,#mermaid-svg-XyjHOcqd5pp8GQCu .node polygon,#mermaid-svg-XyjHOcqd5pp8GQCu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XyjHOcqd5pp8GQCu .rough-node .label text,#mermaid-svg-XyjHOcqd5pp8GQCu .node .label text,#mermaid-svg-XyjHOcqd5pp8GQCu .image-shape .label,#mermaid-svg-XyjHOcqd5pp8GQCu .icon-shape .label{text-anchor:middle;}#mermaid-svg-XyjHOcqd5pp8GQCu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XyjHOcqd5pp8GQCu .rough-node .label,#mermaid-svg-XyjHOcqd5pp8GQCu .node .label,#mermaid-svg-XyjHOcqd5pp8GQCu .image-shape .label,#mermaid-svg-XyjHOcqd5pp8GQCu .icon-shape .label{text-align:center;}#mermaid-svg-XyjHOcqd5pp8GQCu .node.clickable{cursor:pointer;}#mermaid-svg-XyjHOcqd5pp8GQCu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XyjHOcqd5pp8GQCu .arrowheadPath{fill:#333333;}#mermaid-svg-XyjHOcqd5pp8GQCu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XyjHOcqd5pp8GQCu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XyjHOcqd5pp8GQCu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XyjHOcqd5pp8GQCu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XyjHOcqd5pp8GQCu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XyjHOcqd5pp8GQCu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XyjHOcqd5pp8GQCu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XyjHOcqd5pp8GQCu .cluster text{fill:#333;}#mermaid-svg-XyjHOcqd5pp8GQCu .cluster span{color:#333;}#mermaid-svg-XyjHOcqd5pp8GQCu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XyjHOcqd5pp8GQCu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XyjHOcqd5pp8GQCu rect.text{fill:none;stroke-width:0;}#mermaid-svg-XyjHOcqd5pp8GQCu .icon-shape,#mermaid-svg-XyjHOcqd5pp8GQCu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XyjHOcqd5pp8GQCu .icon-shape p,#mermaid-svg-XyjHOcqd5pp8GQCu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XyjHOcqd5pp8GQCu .icon-shape rect,#mermaid-svg-XyjHOcqd5pp8GQCu .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XyjHOcqd5pp8GQCu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XyjHOcqd5pp8GQCu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XyjHOcqd5pp8GQCu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 未到输出时刻
到达输出时刻
仿真内核事件
IEventExporter

(210 行契约)
ShouldOutputNow

按事件类型判断速率
丢弃 / 等待下一刻度
ExportXxx 开关过滤
声明式能力协商

UsesUnitMissionAndStatus / UsesUnitDamage
导出目标
XMLFile / CSVFile
SQLite / MSAccess
TacviewRealtime / TacviewPipe
SIMDIS
跨进程传输

EventExportNotification(Serializable)

8.4 分布式仿真接口

ISimConnector 定义了引擎接入分布式仿真体系的契约(A 级)。当前唯一实现类型为 DIS(IEEE 1278 分布式交互仿真协议),由 OpenDis 与 DISnet 两套协议栈支撑(合计 710 个源文件,见 2.2 节)。

public interface ISimConnector

{

public enum DistributedSimulationState : byte { StoppedFrozen, Running }

复制代码
public enum ExportedInfoType {
    None, Heartbeat, EntityState, WeaponFire,
    WeaponImpactOrDetonation, UnitDestroyed,
    AcousticEmission_Passive
}

public enum SimConnectorType { None, DIS }

string  Name        { get; }
int     QueueLength { get; }
SimConnectorType            ConnectorType { get; }
DistributedSimulationState  SessionState  { get; }

bool ExportUnitPositions            { get; set; }
bool ExportWeaponFired              { get; set; }
bool ExportWeaponImpactOrDetonation { get; set; }
bool ExportUnitDestroyed            { get; set; }

void ExportInfo(ExportedInfoType type,
                Dictionary<string, Tuple<Type, string>> payload,
                Scenario scen);
void Start();
void SetScenario(Scenario scen);
void StopCleanUpAndReset();
void PerformHousekeeping();
bool UnitHasToSendHeartbeat(Module_Unit.Unit unit);

}

三处细节值得说明(B 级):

• SessionState 只有 StoppedFrozen 与 Running 两态,直接对应 DIS 协议中的仿真管理状态。接口不试图抽象出更通用的状态机,而是贴合 DIS 语义------这与 SimConnectorType 目前只有 DIS 一个取值是一致的:接口是为 DIS 而设计的,扩展到 HLA 等其他协议需要再抽象。

• AcousticEmission_Passive 被单列为一种导出信息类型。在 DIS 协议中水声辐射(Underwater Acoustic PDU)确实是独立的 PDU 族。把它与实体状态、开火、毁伤并列,说明该连接器面向的是包含水下战的联合仿真场景。

• UnitHasToSendHeartbeat 以 Module_Unit.Unit 为参数,而非 ActiveUnit。这印证了 3.4 节的判断:Module_Unit.Unit 是跨越内核边界的规范载体,DIS 连接器与表现层使用同一套投影对象。

ExportInfo 的载荷类型 Dictionary<string, Tuple<Type, string>> 值得批评(C 级):它把字段名映射到"类型 + 字符串化的值",即用弱类型的字符串字典传递本可强类型化的数据。这牺牲了编译期检查,把错误推迟到运行期。推测其动因是让连接器与内核可以独立演进(新增字段无需改接口),但代价是双方必须在文档层面约定字段名,任何一侧的笔误都只能在运行期发现。IEventExporter 的载荷同样采用了这一模式(Dictionary<string, EventNotificationParameter>),只是后者至少保留了 Type 与 Value 而非字符串化。

8.4.1 与 HLA 的对比及演进方向

DIS 与 HLA(High Level Architecture,高层体系架构)是两种主流的分布式仿真互操作标准。前者以 PDU(协议数据单元)广播为基本交互方式,后者则以联邦(Federation)成员间的对象模型与声明式数据分发为核心。二者在三个维度上存在本质差异(B 级):

• 仿真管理状态机:DIS 的仿真管理状态只有 StoppedFrozen 与 Running 两态,直接对应 IEEE 1278 中仿真管理 PDU 的有限状态集合;HLA 则定义了更完整的联邦执行生命周期------包括 Initializing、ReadyToPopulate、Populating、ReadyToRun、Running、Paused、Saving、Restoring、Shutdown 等状态,并允许联邦成员通过 Federation Management 服务显式推进状态迁移。ISimConnector 的 DistributedSimulationState 枚举正是为 DIS 量身定制的,无法表达 HLA 的中间态

• 数据分发管理:DIS 依赖广播式 PDU 分发,接收方按实体类型自行过滤,网络带宽随实体数量线性增长;HLA 则通过声明管理(Declaration Management)与数据分发管理(Data Distribution Management)实现基于发布/订阅的按需路由,联邦成员只接收自己订阅的对象属性与交互类,带宽开销显著更低。ISimConnector 的 ExportInfo 采用"全量导出 + 布尔开关过滤"的模式,语义上更接近 DIS 的广播过滤,而非 HLA 的订阅式分发

• 时间管理:DIS 的时间推进是松散的,各仿真节点按自身时钟运行,通过 PDU 中的时间戳尽力对齐;HLA 则提供显式的时间管理服务(Time Management),支持保守同步(Conservative)与乐观同步(Optimistic)两种推进策略,并允许联邦成员协商时间步长与前瞻量(Lookahead)。ISimConnector 完全没有时间管理相关成员------它把时间推进完全交给内核的仿真时钟,这在 DIS 场景下可行,但接入 HLA 联邦时无法满足联邦的时间协商要求

对照上述差异,当前 ISimConnector 接口中哪些设计是 DIS 特有、哪些可复用,可以作如下划分(C 级):

• DIS 特有、扩展 HLA 时需改造的部分:DistributedSimulationState 的两态枚举(需扩展为 HLA 的联邦执行状态集);AcousticEmission_Passive 枚举位(这是 DIS 水声辐射 PDU 族的直接映射,HLA 中水声辐射应作为对象类或交互类在 FOM 中建模,而非硬编码枚举位);ExportInfo 的弱类型字典载荷(HLA 要求按 FOM 定义的对象属性/交互参数强类型传递)

• 可复用的通用设计:ExportUnitPositions / ExportWeaponFired 等布尔开关所体现的"按需导出"思想,与 HLA 的发布/订阅在语义上同构,可映射为联邦成员的发布声明;UnitHasToSendHeartbeat 以 Module_Unit.Unit 为参数的做法,与 HLA 以对象实例句柄(ObjectInstanceHandle)标识实体的思路一致,可保留;QueueLength 与背压监测机制对任何分布式协议都适用

若未来需要接入 HLA,最小接口改造方案如下(C 级):

csharp 复制代码
public enum SimConnectorType { None, DIS, HLA }

public enum DistributedSimulationState : byte {
    StoppedFrozen, Running,
    // HLA 联邦执行生命周期扩展
    Initializing, ReadyToPopulate, Populating,
    ReadyToRun, Paused, Saving, Restoring, Shutdown
}

// 将弱类型字典载荷替换为强类型载荷
public struct ExportPayload {
    public ExportedInfoType Type;
    public IReadOnlyDictionary<string, object> Fields; // 按 FOM 定义强类型化
}

该方案对现有 DIS 实现的影响是可控的(C 级):新增 SimConnectorType.HLA 枚举值不影响既有 DIS 连接器的取值判断;扩展 DistributedSimulationState 状态集只需在 DIS 实现中把新增状态映射回 StoppedFrozen / Running 两态即可保持兼容;而将 ExportInfo 的载荷从 Dictionary<string, Tuple<Type, string>> 替换为强类型 ExportPayload,则需要同步修改 DIS 连接器与内核的调用点------这是唯一需要改动现有代码的环节,但收益是编译期类型检查的回归,消除了前文批评的弱类型字典缺陷。

下面以一个最小 HLA 连接器骨架为例,展示上述改造方案在代码层面的落地。该骨架实现了 SimConnectorType.HLA 的枚举扩展、DistributedSimulationState 的 HLA 状态映射,以及强类型 ExportPayload 的构造与发送流程:

csharp 复制代码
// ------ 最小 HLA 连接器骨架:展示与 DIS 实现的差异点 ------

// 1. 枚举扩展:新增 HLA 连接器类型(与 DIS 并存,互不影响)
public enum SimConnectorType { None, DIS, HLA }

// 2. 状态映射:HLA 联邦执行生命周期 → 内核两态
//    与 DIS 实现不同,HLA 连接器必须维护完整的联邦状态机,
//    并在推进时把中间态映射回内核的 StoppedFrozen / Running 两态
public enum DistributedSimulationState : byte {
    StoppedFrozen, Running,
    // HLA 联邦执行生命周期扩展(新增中间态)
    Initializing, ReadyToPopulate, Populating,
    ReadyToRun, Paused, Saving, Restoring, Shutdown
}

// 3. 强类型载荷:替代 DIS 的 Dictionary<string, Tuple<Type, string>>
//    与 DIS 的弱类型字典不同,HLA 要求按 FOM 定义的对象属性/交互参数强类型传递
public struct ExportPayload {
    public ExportedInfoType Type;                       // 载荷类型(实体状态/开火/毁伤等)
    public IReadOnlyDictionary<string, object> Fields;  // 按 FOM 强类型化的字段集合
}

public sealed class HlaConnector : ISimConnector
{
    public SimConnectorType ConnectorType => SimConnectorType.HLA;  // 与 DIS 实现的分叉点

    private DistributedSimulationState _state = DistributedSimulationState.Initializing;

    // 4. 状态推进:HLA 连接器通过 Federation Management 服务显式推进状态
    //    与 DIS 实现不同------DIS 只有两态,HLA 需处理完整的联邦生命周期
    public void AdvanceFederationState(DistributedSimulationState next)
    {
        _state = next;
        // 把 HLA 中间态映射回内核两态,保持对既有调用方的兼容
        SessionState = (next == DistributedSimulationState.Running)
            ? DistributedSimulationState.Running
            : DistributedSimulationState.StoppedFrozen;
    }

    public DistributedSimulationState SessionState { get; private set; }

    // 5. 强类型载荷构造与发送:替代 DIS 的弱类型字典 ExportInfo
    //    与 DIS 实现不同------DIS 用字符串字典传递字段,HLA 按 FOM 强类型化,
    //    编译期即可校验字段名与类型,消除运行期笔误
    public void PublishEntityState(Module_Unit.Unit unit, double lat, double lon)
    {
        var payload = new ExportPayload {
            Type = ExportedInfoType.EntityState,
            Fields = new Dictionary<string, object> {
                ["UnitHandle"] = unit,          // 复用 Module_Unit.Unit 作为实体标识
                ["Latitude"]   = lat,           // 强类型 double,而非字符串化
                ["Longitude"]  = lon
            }
        };
        // 通过发布声明(Publish)把载荷按订阅路由分发,而非 DIS 的广播过滤
        SendToFederation(payload);
    }

    // 6. 其余成员:背压监测、心跳判断等通用设计可直接复用 DIS 实现
    public int QueueLength => _queue.Count;
    public bool ExportUnitPositions { get; set; }
    public bool ExportWeaponFired { get; set; }
    public bool ExportWeaponImpactOrDetonation { get; set; }
    public bool ExportUnitDestroyed { get; set; }

    private readonly Queue<ExportPayload> _queue = new Queue<ExportPayload>();

    private void SendToFederation(ExportPayload payload) => _queue.Enqueue(payload);

    public void ExportInfo(ExportedInfoType type,
        Dictionary<string, Tuple<Type, string>> payload, Scenario scen)
        => throw new NotSupportedException("HLA 连接器使用强类型 ExportPayload,不再支持弱类型字典");

    public void Start() { /* 加入联邦执行 */ }
    public void SetScenario(Scenario scen) { /* 绑定想定 */ }
    public void StopCleanUpAndReset() { /* 退出联邦并清理 */ }
    public void PerformHousekeeping() { /* 周期清理 */ }
    public bool UnitHasToSendHeartbeat(Module_Unit.Unit unit) => true;
}

这段骨架与现有 DIS 实现的差异点集中在三处(C 级):一是 ConnectorType 返回 SimConnectorType.HLA,与 DIS 连接器在类型层面分叉;二是 AdvanceFederationState 显式维护 HLA 联邦生命周期,并把中间态映射回内核两态以保持兼容;三是 PublishEntityState 以强类型 ExportPayload 构造载荷并按订阅路由分发,取代了 DIS 的弱类型字典广播过滤------这正是前文批评的弱类型缺陷在 HLA 路径上的回归修复。

ISimConnector 从 DIS 向 HLA 演进的路径与差异点如下图所示:
#mermaid-svg-saTF8Gg2Kfw9zznz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-saTF8Gg2Kfw9zznz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-saTF8Gg2Kfw9zznz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-saTF8Gg2Kfw9zznz .error-icon{fill:#552222;}#mermaid-svg-saTF8Gg2Kfw9zznz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-saTF8Gg2Kfw9zznz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-saTF8Gg2Kfw9zznz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-saTF8Gg2Kfw9zznz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-saTF8Gg2Kfw9zznz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-saTF8Gg2Kfw9zznz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-saTF8Gg2Kfw9zznz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-saTF8Gg2Kfw9zznz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-saTF8Gg2Kfw9zznz .marker.cross{stroke:#333333;}#mermaid-svg-saTF8Gg2Kfw9zznz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-saTF8Gg2Kfw9zznz p{margin:0;}#mermaid-svg-saTF8Gg2Kfw9zznz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-saTF8Gg2Kfw9zznz .cluster-label text{fill:#333;}#mermaid-svg-saTF8Gg2Kfw9zznz .cluster-label span{color:#333;}#mermaid-svg-saTF8Gg2Kfw9zznz .cluster-label span p{background-color:transparent;}#mermaid-svg-saTF8Gg2Kfw9zznz .label text,#mermaid-svg-saTF8Gg2Kfw9zznz span{fill:#333;color:#333;}#mermaid-svg-saTF8Gg2Kfw9zznz .node rect,#mermaid-svg-saTF8Gg2Kfw9zznz .node circle,#mermaid-svg-saTF8Gg2Kfw9zznz .node ellipse,#mermaid-svg-saTF8Gg2Kfw9zznz .node polygon,#mermaid-svg-saTF8Gg2Kfw9zznz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-saTF8Gg2Kfw9zznz .rough-node .label text,#mermaid-svg-saTF8Gg2Kfw9zznz .node .label text,#mermaid-svg-saTF8Gg2Kfw9zznz .image-shape .label,#mermaid-svg-saTF8Gg2Kfw9zznz .icon-shape .label{text-anchor:middle;}#mermaid-svg-saTF8Gg2Kfw9zznz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-saTF8Gg2Kfw9zznz .rough-node .label,#mermaid-svg-saTF8Gg2Kfw9zznz .node .label,#mermaid-svg-saTF8Gg2Kfw9zznz .image-shape .label,#mermaid-svg-saTF8Gg2Kfw9zznz .icon-shape .label{text-align:center;}#mermaid-svg-saTF8Gg2Kfw9zznz .node.clickable{cursor:pointer;}#mermaid-svg-saTF8Gg2Kfw9zznz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-saTF8Gg2Kfw9zznz .arrowheadPath{fill:#333333;}#mermaid-svg-saTF8Gg2Kfw9zznz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-saTF8Gg2Kfw9zznz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-saTF8Gg2Kfw9zznz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-saTF8Gg2Kfw9zznz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-saTF8Gg2Kfw9zznz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-saTF8Gg2Kfw9zznz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-saTF8Gg2Kfw9zznz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-saTF8Gg2Kfw9zznz .cluster text{fill:#333;}#mermaid-svg-saTF8Gg2Kfw9zznz .cluster span{color:#333;}#mermaid-svg-saTF8Gg2Kfw9zznz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-saTF8Gg2Kfw9zznz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-saTF8Gg2Kfw9zznz rect.text{fill:none;stroke-width:0;}#mermaid-svg-saTF8Gg2Kfw9zznz .icon-shape,#mermaid-svg-saTF8Gg2Kfw9zznz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-saTF8Gg2Kfw9zznz .icon-shape p,#mermaid-svg-saTF8Gg2Kfw9zznz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-saTF8Gg2Kfw9zznz .icon-shape rect,#mermaid-svg-saTF8Gg2Kfw9zznz .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-saTF8Gg2Kfw9zznz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-saTF8Gg2Kfw9zznz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-saTF8Gg2Kfw9zznz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 最小改造
FOM 建模
替换载荷
ISimConnector 契约
当前实现:DIS

(IEEE 1278)
演进目标:HLA

(高层体系架构)
SessionState 两态

StoppedFrozen / Running
AcousticEmission_Passive 枚举位
弱类型字典载荷

Dictionary<string, Tuple>
扩展状态集

Initializing ... Shutdown
水声辐射在 FOM 中建模
强类型 ExportPayload

编译期类型检查
可复用通用设计
按需导出布尔开关(语义同构于发布/订阅)
Module_Unit.Unit 作为实体标识
QueueLength 背压监测

8.5 想定级特性开关

Scenario.ScenarioFeatureOption 定义了 16 项可逐想定启用的特性(A 级)。这是引擎处理模型演进与向后兼容矛盾的机制。

表 8-6 ScenarioFeatureOption 特性开关

开关 启用后的效果

None 基线,不启用任何可选特性

DetailedGunFireControl 精细火炮射控模型

UnlimitedBaseMagazines 基地弹药库不受限(简化后勤)

AircraftDamage 飞机毁伤细化建模

CommsJamming 通信干扰

CommsDisruption 通信中断

EMP_Omni 全向电磁脉冲效应(见分析报告 5.3 节)

HEL_Phase2 高能激光武器第二阶段模型

RailgunsAndHVPs 电磁轨道炮与高速弹丸

RealisticSubComms 潜艇通信的真实约束(深度/速度限制)

LandTypeEffects 地表覆盖效应(影响探测与毁伤)

LandTypeEffects_Advanced 地表覆盖高级效应

WeatherAffectsShipSpeed 海况影响舰艇航速

FixedSideColors 固定阵营配色(表现层)

AllowLandingPlannerInstantLoading 着陆规划器即时装载

ACS_NAW_Limitations 全天候作战能力限制

这一机制的设计价值在于(C 级):新模型(如激光武器、轨道炮、通信干扰)加入引擎后,既有想定的行为不会被改变------旧想定不启用新开关,其推演结果保持可复现;而新想定可以选择性启用。对于一款有十余年演进历史、积累了大量社区想定的产品,这是必需的设计。

代码层面的用法在 MainForm 中可见(A 级):

if (Client.CurrentScenario.DeclaredFeatures

.Contains(Scenario.ScenarioFeatureOption.LandTypeEffects))

{

// 查询地表覆盖类型并计入计算

}

else { /* 走简化路径 */ }

注意开关存放在 Scenario.DeclaredFeatures 集合中,随想定一同持久化------因而它是想定的属性而非用户偏好设置,这保证了同一想定在任何机器上行为一致。

下面以一个想定初始化时的 C# 代码示例,展示如何读取 DeclaredFeatures 并根据开关启用 LandTypeEffects 高级地表覆盖模型。该代码位于想定加载后的初始化流程中,通过条件分支决定是否启用高级地表覆盖计算:

csharp 复制代码
// ------ 想定初始化:读取 DeclaredFeatures 并启用高级地表覆盖模型 ------

// 1. 想定加载完成后,读取其声明的特性集合
//    DeclaredFeatures 随想定持久化,是想定的属性而非用户偏好
var declared = Client.CurrentScenario.DeclaredFeatures;

// 2. 条件分支:判断是否启用了地表覆盖高级效应
//    注意 LandTypeEffects_Advanced 是 LandTypeEffects 的增强版,
//    启用高级效应时需同时确认基础地表覆盖也已开启
bool useAdvancedLandType = declared.Contains(
    Scenario.ScenarioFeatureOption.LandTypeEffects_Advanced);

if (useAdvancedLandType)
{
    // 3. 启用高级地表覆盖模型:查询地表覆盖类型并计入计算
    //    高级模型会考虑地表覆盖对探测与毁伤的精细影响,
    //    例如植被、建筑、地形起伏对雷达遮蔽与弹道衰减的差异化处理
    terrainModel.EnableAdvancedLandTypeEffects();

    // 4. 可选:确认基础地表覆盖开关也已启用,保证模型链路完整
    if (!declared.Contains(Scenario.ScenarioFeatureOption.LandTypeEffects))
    {
        // 高级效应依赖基础地表覆盖数据,缺失时给出告警
        logger.Warn("LandTypeEffects_Advanced 已启用,但基础 LandTypeEffects 未启用,"
                    + "高级地表覆盖将无法获得完整的地表类型数据。");
    }
}
else
{
    // 5. 走简化路径:仅启用基础地表覆盖(若声明)
    if (declared.Contains(Scenario.ScenarioFeatureOption.LandTypeEffects))
    {
        terrainModel.EnableBasicLandTypeEffects();
    }
    else
    {
        // 6. 未声明任何地表覆盖特性,使用默认地形模型
        terrainModel.UseDefaultTerrain();
    }
}

这段代码体现了 8.5 节所述机制的两个要点(C 级):一是 DeclaredFeatures 作为想定的持久化属性,在初始化时被读取以决定模型路径,保证了同一想定在任何机器上行为一致;二是 LandTypeEffects_Advanced 与 LandTypeEffects 的层级关系------高级效应依赖基础地表覆盖数据,因此代码中做了依赖检查与告警,避免因开关组合不当导致模型链路不完整。

想定级特性开关的初始化决策流程如下图所示:
#mermaid-svg-JjYwJmJdMYKOLrkv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JjYwJmJdMYKOLrkv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JjYwJmJdMYKOLrkv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JjYwJmJdMYKOLrkv .error-icon{fill:#552222;}#mermaid-svg-JjYwJmJdMYKOLrkv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JjYwJmJdMYKOLrkv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JjYwJmJdMYKOLrkv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JjYwJmJdMYKOLrkv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JjYwJmJdMYKOLrkv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JjYwJmJdMYKOLrkv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JjYwJmJdMYKOLrkv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JjYwJmJdMYKOLrkv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JjYwJmJdMYKOLrkv .marker.cross{stroke:#333333;}#mermaid-svg-JjYwJmJdMYKOLrkv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JjYwJmJdMYKOLrkv p{margin:0;}#mermaid-svg-JjYwJmJdMYKOLrkv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JjYwJmJdMYKOLrkv .cluster-label text{fill:#333;}#mermaid-svg-JjYwJmJdMYKOLrkv .cluster-label span{color:#333;}#mermaid-svg-JjYwJmJdMYKOLrkv .cluster-label span p{background-color:transparent;}#mermaid-svg-JjYwJmJdMYKOLrkv .label text,#mermaid-svg-JjYwJmJdMYKOLrkv span{fill:#333;color:#333;}#mermaid-svg-JjYwJmJdMYKOLrkv .node rect,#mermaid-svg-JjYwJmJdMYKOLrkv .node circle,#mermaid-svg-JjYwJmJdMYKOLrkv .node ellipse,#mermaid-svg-JjYwJmJdMYKOLrkv .node polygon,#mermaid-svg-JjYwJmJdMYKOLrkv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JjYwJmJdMYKOLrkv .rough-node .label text,#mermaid-svg-JjYwJmJdMYKOLrkv .node .label text,#mermaid-svg-JjYwJmJdMYKOLrkv .image-shape .label,#mermaid-svg-JjYwJmJdMYKOLrkv .icon-shape .label{text-anchor:middle;}#mermaid-svg-JjYwJmJdMYKOLrkv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JjYwJmJdMYKOLrkv .rough-node .label,#mermaid-svg-JjYwJmJdMYKOLrkv .node .label,#mermaid-svg-JjYwJmJdMYKOLrkv .image-shape .label,#mermaid-svg-JjYwJmJdMYKOLrkv .icon-shape .label{text-align:center;}#mermaid-svg-JjYwJmJdMYKOLrkv .node.clickable{cursor:pointer;}#mermaid-svg-JjYwJmJdMYKOLrkv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JjYwJmJdMYKOLrkv .arrowheadPath{fill:#333333;}#mermaid-svg-JjYwJmJdMYKOLrkv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JjYwJmJdMYKOLrkv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JjYwJmJdMYKOLrkv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JjYwJmJdMYKOLrkv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JjYwJmJdMYKOLrkv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JjYwJmJdMYKOLrkv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JjYwJmJdMYKOLrkv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JjYwJmJdMYKOLrkv .cluster text{fill:#333;}#mermaid-svg-JjYwJmJdMYKOLrkv .cluster span{color:#333;}#mermaid-svg-JjYwJmJdMYKOLrkv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JjYwJmJdMYKOLrkv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JjYwJmJdMYKOLrkv rect.text{fill:none;stroke-width:0;}#mermaid-svg-JjYwJmJdMYKOLrkv .icon-shape,#mermaid-svg-JjYwJmJdMYKOLrkv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JjYwJmJdMYKOLrkv .icon-shape p,#mermaid-svg-JjYwJmJdMYKOLrkv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JjYwJmJdMYKOLrkv .icon-shape rect,#mermaid-svg-JjYwJmJdMYKOLrkv .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JjYwJmJdMYKOLrkv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JjYwJmJdMYKOLrkv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JjYwJmJdMYKOLrkv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
想定加载完成
读取 DeclaredFeatures

(随想定持久化)
包含

LandTypeEffects_Advanced?
启用高级地表覆盖模型
包含基础

LandTypeEffects?
告警:高级效应依赖基础数据
模型链路完整
包含基础

LandTypeEffects?
启用基础地表覆盖模型
使用默认地形模型

8.6 本章小结

本章围绕引擎的扩展性与集成设计展开,核心结论可归纳为以下要点:

• 14 个接口构成全部显式扩展点,按能力契约、数据提供者、输出与互操作三类划分,其中真正允许第三方注入新行为的只有输出与互操作两类------这界定了引擎对外集成的边界(C 级)。

• Lua 脚本子系统采用「动态函数库---执行环境---领域 API---对象包装器」四层架构,以包装器投影隔离内核对象,使内核字段可随版本重构而脚本不失效------这是对大量社区想定依赖脚本这一现实约束的必要投入(C 级)。

• 上下文注入而非全局查询是脚本沙箱的核心设计:触发时的现场对象直接作为字段可用,配合 ScriptLockObj 串行化执行与诊断信息一等公民,显著降低了想定作者的使用门槛并保证了执行一致性(C 级)。

• 事件导出子系统按事件类型分别设定输出速率,且速率枚举与 4.3 节的时间刻度同构复用,使分析者可在数据完整性与文件体积之间自行权衡------这是高频导出场景下控制数据量的经济手段(C 级)。

• UsesUnitMissionAndStatus 与 UsesUnitDamage 体现的声明式能力协商,让内核按导出器所需按需付出采集代价,避免了无条件采集全部信息的浪费,是高频导出场景下的关键优化(C 级)。

• ISimConnector 当前为 DIS 量身定制:SessionState 两态、AcousticEmission_Passive 枚举位与弱类型字典载荷均为 DIS 特有设计;而按需导出开关、以 Module_Unit.Unit 为参数及背压监测则可复用于 HLA 等其它协议(C 级)。

• 向 HLA 演进的最小改造方案(新增 SimConnectorType.HLA、扩展状态集、以强类型 ExportPayload 替换弱类型字典)对现有 DIS 实现影响可控,且能回归编译期类型检查------这为未来接入高层体系架构预留了清晰的路径(C 级)。

• ScenarioFeatureOption 的 16 项想定级特性开关随想定持久化,使新模型加入后既有想定的行为保持可复现,而新想定可选择性启用------这是引擎处理十余年演进与向后兼容矛盾的根本机制(C 级)。

整体评价:本章揭示的扩展性设计呈现出一种务实的工程取向(C 级)。一方面,引擎通过 Lua 脚本、事件导出与分布式仿真接口,为第三方行为注入、数据分析与外部系统集成提供了清晰的边界;另一方面,包装器投影、声明式能力协商与想定级特性开关等机制,始终把「版本兼容」与「行为可复现」放在首位------这正是一款拥有十余年演进历史、积累了大量社区想定的产品所必需的取舍。14 个扩展点中真正开放给第三方的只有输出与互操作两类,说明引擎在保持内核封闭的同时,把扩展的着力点放在了对外集成而非内部重构上,这是一种稳健的架构策略。

后续展望:本章的演进方向已初现端倪(C 级)。一是分布式仿真接口向 HLA 的扩展路径已经清晰,最小改造方案(新增连接器类型、扩展状态集、强类型载荷)为接入高层体系架构预留了接口层面的空间;二是 Lua 脚本子系统的四层架构与上下文注入设计,为未来引入更丰富的领域 API 或脚本调试工具提供了稳定的底座;三是想定级特性开关的持久化机制,使新模型(如激光武器、轨道炮、通信干扰)可以持续加入而不破坏既有想定的可复现性。可以预期,随着引擎向更大规模的仿真与分析体系嵌入,这三条路径将成为其扩展性与集成能力持续演进的支点。

相关推荐
码爸1 小时前
支持向量机介绍与示例
算法·机器学习·支持向量机
怕浪猫1 小时前
2028编程圈很火的10个Skills
算法·面试·github
Best-Wishes1 小时前
科来网络分析系统(技术交流版)使用说明书
开发语言·junit·php
代码不停2 小时前
自动化测试(java)
java·开发语言·selenium·自动化
鬼手点金4 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
爱上网的小舟4 小时前
星际战士2启动错误怎么办?5种游戏报错解决方法
开发语言·php
阿里嘎多学长5 小时前
2026-10-12 GitHub 热点项目精选
开发语言·程序员·github·代码托管
wangchunyu1146 小时前
Python 数据可视化实战入门教程
开发语言·python·信息可视化
Nil2086 小时前
leetcode 198打家劫舍
算法·leetcode·职场和发展