PlantScope — 部署切片
> Status: NEW · 已对齐 PCR Master Blueprint v1.0 §2.8 > 范畴: runtime/PlantScope.h、data/input/sim/deployments/*.yaml > 依赖: contracts(BusAddr) > 被依赖: runtime/Assembler, runtime/Runner
1. 问题陈述
PlantPhysics 资产是全局唯一的真理(Blueprint §7.15)。但在不同部署中,本进程实际实例化什么会变:
| 部署 | 本进程要不要 physics body? | 本进程要不要传感器真值采样? | 本进程要不要作动器效应? | 本进程要不要 FCC? |
|---|---|---|---|---|
avionics_dry | ❌(冻结) | ✓(FCC 看到 dummy 传感数据) | ✓(FCC 命令进 mech FSM) | ✓ |
sil_monolithic | ✓ | ✓ | ✓ | ✓ |
hil_dyn(plant 节点) | ✓ | ✓ | ✓ | ❌ |
hil_dyn(fcc 节点) | ❌ | ❌ | ❌ | ✓ |
hil_fcc(plant 节点) | ✓ | ✓ | ✓ | ❌ |
反例:每种部署写一个独立的 WorldEnv 构造分支,把"实例化什么"硬编码。 正解:用 PlantScope 显式声明本进程切片,Runner 与 BodyTick 据此跳过不需要的步骤。
2. 结构定义
// runtime/PlantScope.h
namespace runtime {
struct PlantScope {
// ─── 物理仿真切片 ───
bool physics_bodies; // 是否演化 RocketBody.spatial/inertial(ODE 积分)
bool sensor_truth; // 是否采样 IMU/GPS 真值(plant 真值→ noise → bus)
bool actuator_effect; // 是否计算 EngineEffect/FinDefl(mech FSM)
bool fcc; // 是否实例化 RocketBody.fcc 并运行 FccTick
// ─── 信道选择 ───
enum class TransportKind { InMemory, UdpServer, UdpClient };
TransportKind dyn_transport;
UdpConfig udp_config; // dyn_transport == Udp* 时有效
enum class BusKind { InMemory, Bridge1553B, RealtimeBus };
BusKind bus_kind;
BusBridgeConfig bus_bridge_config; // bus_kind == Bridge1553B 时有效
BusRtConfig bus_rt_config; // bus_kind == RealtimeBus 时有效
// ─── 总线地址路由(本进程能访问哪些设备地址) ───
std::vector<contracts::BusAddr> local_bus_addrs;
std::vector<contracts::BusAddr> remote_bus_addrs;
};
} // namespace runtime关键性质:
- 四个
bool描述本进程承担哪些 plant 职责 dyn_transport/bus_kind决定信道实例化local_bus_addrs/remote_bus_addrs决定 bus 路由:本地地址直接 InMemory 处理,远端地址通过 BusBridge 转发
3. 四种部署的 Scope 配置
3.1 sil_monolithic(默认 SIL)
# data/input/sim/deployments/sil_monolithic.yaml
plant_scope:
physics_bodies: true
sensor_truth: true
actuator_effect: true
fcc: true
dyn_transport: InMemory
bus_kind: InMemory
local_bus_addrs: [ALL]
remote_bus_addrs: []含义:所有 plant 职责本地化,DynChannel + Bus 都内存零拷贝。最快路径用于调试 + CI 回归。
3.2 avionics_dry(FCC 模飞测试)
plant_scope:
physics_bodies: false # ← 物理冻结
sensor_truth: true # ← 但传感器真值仍采样(plant 静态)
actuator_effect: true # ← 设备 FSM 接 FCC 命令推进
fcc: true
dyn_transport: InMemory
bus_kind: InMemory含义:火箭不动,但 FCC 跑全闭环。验证调度/状态机/总线时序。
BodyTick 行为(详见 06_Simulation/Body_World_Tick.md §10):
- ①Avionics step:跑(包括 sensor 采样和 actuator FSM)
- ②FCC tick:跑
- ③Plant Physics:跑(计算 Forces)
- ④Dynamics integrate:跳过(spatial/inertial 不更新)
- ⑤DynOutFrame 封装:跑(取冻结值)
3.3 hil_dyn(plant 在本机,FCC 在远端)
plant 节点:
plant_scope:
physics_bodies: true
sensor_truth: true
actuator_effect: true
fcc: false # ← FCC 在远端
dyn_transport: UdpServer # ← 等待 FCC 连接
udp_config:
local_port: 31000
bus_kind: InMemory # ← 远端 FCC 通过 BusBridge 看本机 Busfcc 节点(在另一台机器,可能跑同一 binary 但不同 YAML):
plant_scope:
physics_bodies: false
sensor_truth: false
actuator_effect: false
fcc: true
dyn_transport: UdpClient
udp_config:
remote_host: "192.168.1.100"
remote_port: 31000
bus_kind: Bridge1553B # ← 真实 1553B 转换器3.4 hil_fcc(FCC 在 RTOS,plant 在 SIL 仿真器)
plant 节点(SIL 仿真器):
plant_scope:
physics_bodies: true
sensor_truth: true
actuator_effect: true
fcc: false
dyn_transport: InMemory # ← FCC 与 plant 通过物理总线,不走 DynChannel
bus_kind: RealtimeBus # ← 真实 1553B / TTE 直接驱动 FCC 板卡fcc 节点(RTOS):跑独立 binary,由 plant 节点的 RealtimeBus 驱动。
4. PlantScope 在代码各层的解释
4.1 Runner 构造
Runner::Runner(SimulationInstance inst) : instance_(std::move(inst)) {
const auto& scope = instance_.scope;
bus_ = make_bus(scope); // 据 bus_kind 决定
dyn_channel_ = make_dyn_channel(scope); // 据 dyn_transport 决定
if (!scope.fcc) {
// 各 body 的 RocketBody.fcc 应该已是空(mission YAML 控制实例化)
// 这里只是 sanity check
}
}4.2 WorldTick(部署透明)
world_tick 本身不读 scope;它只调用 BodyTick。BodyTick 根据 RocketBody 内的 fcc.has_value() + scope.physics_bodies 来决定跳哪步。
// simulation/pipeline/BodyTick.cpp(伪代码)
BodyRWS<BodyOut> body_tick(Time dt) {
return body_ask() >>= [dt](const BodyEnv& env) {
// ... ① avionics step(条件:scope.sensor_truth || scope.actuator_effect)...
// ... ② fcc tick(条件:body.fcc.has_value() && env.fcc_should_tick)...
// ... ③ plant physics(总是跑,但 force computer 可能为 no-op) ...
// ... ④ dynamics integrate(条件:env.scope.physics_bodies)...
// 注:env.scope 是 BodyEnv 中的 PlantScope const& 引用
};
}实施细节:BodyEnv 需要持有 const PlantScope& scope 引用(来自 WorldEnv)。详见 06_Simulation/WorldEnv_Assembly.md §4 与下文 §5。
4.3 Bus 路由
// runtime/Runner main loop C 阶段(cf Body_World_Tick.md §3.2)
void route_bus_global_to_local(IBus& global, BusBuffer& local, const PlantScope& scope) {
for (auto& msg : global.poll_all()) {
if (in_set(scope.local_bus_addrs, msg.dst)) {
local.publish(msg);
}
// remote_bus_addrs 的消息通过 BusBridge 转发(由 IBus 实现负责)
}
}5. WorldEnv 中的 scope 注入
// simulation/env/WorldEnv.h
namespace sim {
struct WorldEnv {
// ... environment + plant_assets + scheduler_config + mission_config ...
// 部署切片:装配期由 Assembler 写入,运行期只读
runtime::PlantScope scope;
};
} // namespace sim> 为什么 scope 进 WorldEnv 而不是 BodyEnv? > scope 全局唯一,所有 body 共享。BodyEnv 通过 world.scope 间接访问。
> 为什么 scope 进 WorldEnv 而不是 SimulationInstance? > 双层: > - SimulationInstance.scope:runtime 的副本(用于工厂选信道) > - WorldEnv.scope:simulation 的副本(用于 BodyTick 内部跳步) > 两者由 Assembler 同源装配,保证一致。
> 反例:每次 BodyTick 都从 SimulationInstance 中查 scope(破坏 BodyEnv 的纯输入语义)。 > 正解:scope 在 WorldEnv 内冻结,BodyEnv 引用之,BodyTick 是纯函数。
6. 不变量与契约
| 契约 | 强度 |
|---|---|
| PlantScope 在 Runner 构造后不可修改 | 必须 |
WorldEnv.scope 与 SimulationInstance.scope 严格相等(Assembler 同源装配) | 必须 |
local_bus_addrs ∩ remote_bus_addrs == ∅ | 必须 |
local_bus_addrs ∪ remote_bus_addrs == 所有 mission 配置的设备地址 | 强烈建议 |
dyn_transport == InMemory ↔ 双向消息全部本地(仅 SIL / avionics_dry) | 推导关系 |
fcc=false 时各 RocketBody.fcc 必须为空 | 必须 |
physics_bodies=false 时 ④Dynamics integrate 必须 skip | 必须 |
7. 反模式
| 反模式 | 后果 | 正确做法 |
|---|---|---|
| PlantScope 中混入业务参数("用 PID 还是 LQR") | 部署配置膨胀;与 fcc_v1/control.yaml 重复 | 业务策略归 fcc_v1/,scope 只描述实例化什么 |
if (scope.fcc) { compile_fcc_pipeline(...) } 在 BodyTick 内 | 热路径分支 + 反复编译 | FCC 编译在 Assembler;BodyTick 仅查 body.fcc.has_value() |
| 三个 main_*.cpp(sil / hil_dyn / hil_fcc) | 漂移 | 一个 main + 三个 YAML |
Runner 直接读取 scope.bus_kind 之外的字段决定逻辑分支 | 部署切换逻辑分散 | 部署逻辑集中在 ChannelFactory + Assembler ⑤ load_scope_ |
HIL 节点上 PlantScope 还配 physics_bodies=true 但实际不演化 | 与 actual 行为不符;调试痛苦 | 部署 YAML 严格反映 actual 切片 |
8. C-Distillation 路径
| C++ 抽象 | C 蜕化 |
|---|---|
PlantScope 结构 | C struct(POD),bool → uint8_t |
TransportKind / BusKind enum | enum |
std::vector<BusAddr> local_bus_addrs | 固定大小数组 + count |
UdpConfig / BusBridgeConfig 嵌套结构 | 嵌套 C struct |
| YAML 解析 | 编译期 codegen:YAML → header static const PlantScope scope_xxx = {...}; |
| 工厂分发(switch case) | #ifdef DEPLOYMENT_* |
9. 测试策略
9.1 单元层
- 四种 deployment YAML 各加载一次,验证字段正确
9.2 组件层
make_dyn_channel(scope)× 4 scope:返回正确具体类型route_bus_global_to_local× scope:本地地址消息进 local,远端消息走 bridge
9.3 集成层
- avionics_dry × 1s:state.bodies[0].spatial 保持不变(physics 冻结)
- sil_monolithic × 1s:与 avionics_dry 对比,spatial 演化非平凡
10. 引用
- Blueprint §2.8(PlantScope 定义)、§1.3(三种部署)、§7.18(runtime 退化)
07_Runtime/Assembler_and_Runner.md(Assembler 装配 scope;Runner 构造时调工厂)07_Runtime/IDynChannel_SIL_HIL.md(信道工厂据 scope 分发)07_Runtime/PCR_Configuration.md(YAML 目录结构)06_Simulation/WorldEnv_Assembly.md(scope 字段在 WorldEnv 内)06_Simulation/Body_World_Tick.md§10(BodyTick 根据 scope 跳步)