RocketBody Composite Container
> Status: NEW · 已对齐 PCR Master Blueprint v1.0 > 范畴: simulation/state/RocketBody.h > 依赖: dynamics_core/state/{SpatialState, InertialState, AuxState}, plant/hardware/Mech, plant/model/*Spec, avionics/devices/*State, bus/BusBuffer, fcc/FccCore, contracts/*Id > 关键判据: Blueprint §2.6.1 / §7.5
1. 问题陈述
RocketBody 是跨域复合体——它同时持有:
- 物理状态(dynamics_core 关心):位姿、速度、质量、惯量、辅助比力/角速度
- 设备实体(plant + avionics 共同关心):发动机、伺服、栅格舵、IMU、GPS、ICU
- 航电基础设施(bus + fcc 关心):局部线束缓冲、可选的 FCC 核心
- 拓扑标记(dynamics_core algebra 关心):当前 WorldStage
没有任何一个子域可以独占它:
dynamics_core/不能持有它(会破坏 universal 承诺,引入 plant/avionics 依赖)plant/不能持有它(会引入 avionics + bus + fcc 依赖)avionics/不能持有它(同上,反向)fcc/不能持有它(HIL 部署时 FCC 在另一台机器,不能依赖整体)
→ simulation/state/ 是唯一合法归属(Blueprint §2.6.1 / §7.5 / §7.16)。
2. 类型布局
// simulation/state/RocketBody.h
namespace sim {
// 物理状态原语来自 dynamics_core(普适,无业务依赖)
using dynamics::SpatialState;
using dynamics::InertialState;
using dynamics::AuxState;
// ─── 设备实体:device-as-unified-entity 形态 ───
// 每个设备一个 struct,自身同时持有 mech(物理面)+ electronic state(电子面)
struct Engine {
contracts::EngineId id;
plant::model::InstallParams install;
const plant::model::EngineSpec* spec; // 非拥有指针,指向 WorldEnv.engine_specs[type_id]
plant::hardware::EngineMech mech; // 物理机械面(燃烧室压力、阀门开度、FSM 阶段)
avionics::device::ecu::EcuState ecu; // 电子控制面(Bus 命令解析、FSM 推进、计时器)
};
struct Servo {
contracts::ServoId id;
plant::model::InstallParams install;
const plant::model::ServoSpec* spec;
plant::hardware::ServoMech mech; // 摆角、速率、力矩
avionics::device::scu::ScuState scu; // PWM 解析、限位、健康
};
struct Fin {
contracts::FinId id;
plant::model::InstallParams install;
const plant::model::FinSpec* spec;
plant::hardware::FinMech mech; // 偏角、铰链力矩、扰动状态
avionics::device::fin_ctrl::FinCtrlState ctrl;
};
struct ImuUnit {
contracts::ImuId id;
plant::model::InstallParams install;
const plant::model::ImuSpec* spec;
avionics::device::imu::ImuState state; // 只有电子面:噪声状态机、累积量化
// 物理输入:body.aux.spec_force_b / body.aux.omega_b (tick 时显式传入)
};
struct GpsUnit {
contracts::GpsId id;
plant::model::InstallParams install;
const plant::model::GpsSpec* spec;
avionics::device::gps::GpsState state; // 只有电子面
// 物理输入:body.spatial.pos_ecf
};
struct Icu {
contracts::IcuId id;
const plant::model::IcuSpec* spec;
avionics::device::icu::IcuState state; // 纯电子,无物理输入
};
// ─── 复合容器 ───
struct RocketBody {
contracts::BodyId body_id;
dynamics::algebra::WorldStage world_stage; // 拓扑标记(详见 §6)
// ── 物理状态(dynamics_core 关心) ──
SpatialState spatial; // pos_lic, vel_lic, q_lic_body
InertialState inertial; // mass, centroid, inertia
AuxState aux; // spec_force_b, omega_b(供 IMU + 调试)
// ── 设备数组(plant + avionics 共同关心) ──
std::vector<Engine> engines;
std::vector<Servo> servos;
std::vector<Fin> fins;
std::vector<ImuUnit> imus;
std::vector<GpsUnit> gpss;
std::vector<Icu> icus;
// ── 航电基础设施(bus + fcc 关心) ──
bus::BusBuffer bus; // 局部线束副本,详见 §5
std::optional<fcc::FccCore> fcc; // 主箭有,分离后的子箭可能没有
};
} // namespace sim> 强制约束:#include 这个头文件的代码自动跨多个子域。这是 simulation/state/ 作为"合法跨域聚合层"的本质:只有这一层可以这样做。
3. device-as-unified-entity:物理面 + 电子面共属一个实体
3.1 为什么不分离?
历史上有过两种错误的尝试:
| 方案 | 问题 |
|---|---|
| A. EngineMech 在 plant/,ECU 在 avionics/,两边各跑各的 FSM | 谁先推进?谁同步给谁?两个 FSM 阶段不一致就崩溃。需要复杂的同步协议。 |
| B. 把 ECU 状态吃进 EngineMech 里 | plant 依赖 avionics → 倒挂;电子面在 HIL 模式下需要换实现,难以隔离 |
device-as-unified-entity 解法:每个设备是 RocketBody 的一个 struct,自身同时持有两面状态。两面的演化由 tick 函数同步推进:
// simulation/pipeline/BodyTick.cpp 内部(精简)
for (auto& eng : body.engines) {
// ECU 处理 Bus 命令 → 推进 EngineMech FSM → 输出 EngineEffect
auto effect = avionics::device::ecu::step(
eng.ecu, // [in/out] 电子面
eng.mech, // [in/out] 物理面
body.bus, // [in/out] 总线
*eng.spec, // [const] 本构表
body_env.aero, // [const] 环境
dt
);
out_engine_effects.push_back(effect);
}两面同步由 step 函数一次性完成;调用者不感知"哪边先动"。
3.2 三种持有形态
| 设备类型 | 持有 | 原因 |
|---|---|---|
| Engine / Servo / Fin | mech + 电子面 | 有物理机械实体(活塞、舵面) |
| ImuUnit / GpsUnit | 只有电子面 | 物理输入直接来自 body.aux / body.spatial,无独立机械实体 |
| Icu | 只有电子面 | 纯计时器/事件发生器,无物理实体 |
详见 03_Avionics_and_Bus/Device_Dual_Face.md。
4. spec 指针的所有权契约
每个设备的 spec 字段是非拥有指针,指向 WorldEnv.engine_specs[...] 等数组的元素。
// 装配时(runtime::Assembler)
eng.spec = &world_env.engine_specs[type_id]; // 借用,不拥有
// 运行时(BodyTick)
double thrust = eng.spec->tables.thrust_vs_throttle(eng.ecu.throttle);约束:
WorldEnv的生命周期必须严格长于WorldState(runtime::SimulationInstance保证)。WorldEnv装配完成后永不修改(spec 表不会被 realloc,指针稳定)。- 不允许把 spec 序列化进
RocketBody(深拷贝爆炸 + 失去单源)。
详见 WorldEnv_Assembly.md §6(指针稳定性约束)。
5. BusBuffer:局部线束副本
bus::BusBuffer 是 RocketBody 内部的总线缓冲——一个延时队列,供该 body 内的 device 互相通信:
┌─────────────────────────────────────────────────┐
│ RocketBody[i] │
│ │
│ ┌─────┐ publish ┌──────────┐ │
│ │ ECU │ ─────────────▶│ │ │
│ └─────┘ │ │ │
│ ┌─────┐ publish │ BusBuffer│ │
│ │ SCU │ ─────────────▶│ │ │
│ └─────┘ │ │ │
│ │ │ │
│ ┌─────┐ poll │ │ │
│ │ FCC │◀───────────── │ │ │
│ └─────┘ └──────────┘ │
│ │
└────────────────┬────────────────────────────────┘
│ tick 末尾
▼
┌──────────────────────┐
│ bus::IBus │ ← Runner 持有的外部总线
│ (InMemory / 1553B) │
└──────────────────────┘职责切割:
- RocketBody.bus(BusBuffer):body 内部 device-to-device + FCC 通信,由
sim::body_tick演化。 - bus::IBus(runtime 持有):跨 body 的总线、HIL 部署时的物理总线。由
sim::world_tick末尾统一路由:把 BusBuffer 中 dst 不在本地的消息 push 到 IBus,从 IBus 拉外部消息进 BusBuffer。
> Wave 1 必删 的旧概念:BusChannel + SignalTag(broadcast 单源)。新模型只有 BusBuffer(local) + IBus(global)。
详见 03_Avionics_and_Bus/Semantic_Bus_Pattern.md §4 / §5。
6. world_stage:箭体级拓扑标记
world_stage 是 dynamics::algebra::WorldStage 强类型 enum,标记该箭体当前的拓扑阶段(如 COMPOSITE_FLIGHT / STAGE_1_SPENT / CORE_COAST / PAYLOAD_ORBIT)。
用途:
- Per-Stage 预编译派发:
CompiledDynamics.per_stage[body.world_stage]选出该阶段的预编译 body pipeline(详见05_Dynamics_Core/Topology_Algebra.md§5)。 - 拓扑代数演化:
evolve_topology(bodies, StageOp::TransitionOp{body_id, next})把单个 body 推进到下一阶段。 - 分离/分级:
SeparationOp会从 source body 派生出 new body,各自有独立的world_stage。
与 FccStage 的关系:
world_stage(箭体物理阶段)与body.fcc->state.stage(FCC 内部控制相位)是两套独立代数,通过DiscreteEvent + ICU间接耦合。- 详见
05_Dynamics_Core/Topology_Algebra.md§4 / Blueprint §7.3。
7. 装配流程:从 YAML 到 RocketBody
data/input/rocket/falcon9.yaml
+ devices.yaml
+ 6 × engine_csv
│
▼
runtime::Assembler::assemble()
│
├── load_environment_() → WorldEnv.atmosphere / gravity / wind
├── load_plant_assets_() → WorldEnv.engine_specs / servo_specs / ...
│
└── instantiate_world_state_()
│
└── for each body in mission.yaml:
RocketBody body;
body.body_id = parse_body_id(...);
body.world_stage = parse_initial_stage(...);
body.spatial = parse_initial_spatial(...); // pos_lic = 0, vel_lic = 0
body.inertial = compute_initial_inertial(asset);
body.aux = {}; // 零初值
for each engine_layout_entry in body_yaml.engines:
Engine eng;
eng.id = entry.id;
eng.install = entry.install;
eng.spec = &world_env.engine_specs[type_id_lookup(entry.type)];
eng.mech = plant::hardware::EngineMech::initial(*eng.spec);
eng.ecu = avionics::device::ecu::EcuState::initial();
body.engines.push_back(std::move(eng));
// 同样初始化 servos / fins / imus / gpss / icus
// body.bus 默认空缓冲
// body.fcc 仅主箭装配:std::optional<fcc::FccCore>::emplace(...)
ws.bodies.push_back(std::move(body));详见 07_Runtime/Assembler.md(待写)。
8. 与"composite hardware factory"模式的关系
部分文档可能把 RocketBody 的设备数组称为"composite hardware factory"。准确说法:
| 概念 | 实质 |
|---|---|
| Composite container | RocketBody 自身(struct 字段聚合) |
| Factory | runtime::Assembler::instantiate_world_state_()(一次性装配) |
| Pipeline | simulation/pipeline/factories/make_*_body_pipeline()(编译期组合 force computer) |
三者职责不同:
- 容器只是 struct,无逻辑。
- 装配工厂创建 instance,把 spec 指针接上。
- Pipeline 工厂创建算子组合(thrust ⊕ aero ⊕ gravity),与 RocketBody 容器本身无关。
详见 05_Dynamics_Core/Topology_Algebra.md §5(per-stage 预编译 dynamics pipeline)。
9. 不变量与契约
| 契约 | 强度 | 检查点 |
|---|---|---|
body_id 在 WorldState.bodies 中唯一 | 必须 | Assembler 装配末尾 |
engines[i].spec 非空 | 必须 | Assembler;BodyTick 入口断言 |
engines[i].id == spec->id(type 自洽) | 必须 | Assembler |
spec 指针在 RocketBody 生命周期内稳定 | 必须 | WorldEnv 装配后冻结 |
bus 在 BodyTick 内部可演化 | 设计 | step 函数读写 |
fcc 在分离的 new_body 中可能为 nullopt | 设计 | TopologyOp::SeparationOp 创建 new_body 时显式决定 |
world_stage 切换只能由 evolve_topology 触发 | 必须 | World tick 末尾,不可在 BodyTick 内修改 |
spatial / inertial / aux 只能由 dynamics_core integrator 演化 | 必须 | BodyTick 物理阶段,不可在 avionics 阶段修改 |
10. 反模式
| 反模式 | 后果 | 正确做法 |
|---|---|---|
把 RocketBody 放在 dynamics/state/ | dynamics 必须依赖 plant/avionics/bus/fcc → universal 承诺破产 | 放在 simulation/state/(Blueprint §2.6.1) |
| 用继承表达不同 body(CoreBody : RocketBody) | OOP 多态运行时;C-distillation 困难 | 单一 struct + world_stage 枚举区分 |
每个 Engine 拥有自己的 EngineSpec 拷贝 | 内存爆炸;spec 表多源 → 不一致 | 只持有 const EngineSpec* 借用指针 |
BodyTick 中修改 body.world_stage | 拓扑演化语义破坏;并行不安全 | 通过 DiscreteEvent 出栈,World 末尾统一 evolve_topology |
把 bus::IBus* 塞进 RocketBody | RocketBody 不能在 HIL 模式 serialize;跨 body 路由耦合 | 只持有 BusBuffer(local),IBus 在 runtime |
BodyTick 末尾直接 world_state.event_history.push_back(...) | 越层 + 并行不安全 | 写入 BodyLog.emitted_events,World 升维统一收集 |
engines[i].mech 在 avionics 子域内被修改 | 跨域写入违反 device-as-unified-entity 约束 | 统一通过 ecu::step(eng.ecu, eng.mech, ...) 一次性同步 |
11. C-Distillation 路径
| C++ 抽象 | C 蜕化 |
|---|---|
std::vector<Engine> | 静态数组 + count(编译期上限) |
std::optional<fcc::FccCore> | fcc::FccCore + bool has_fcc |
const plant::model::EngineSpec* | uint32_t engine_spec_idx(索引到全局 spec 表) |
contracts::EngineId(强类型 wrap) | uint32_t typedef |
| 嵌套 struct | 平铺到一个大 struct(cache 友好) |
bus::BusBuffer(动态队列) | 环形缓冲(编译期容量) |
关键:RocketBody 在 C 形态下应该是一个单一连续内存块,所有指针变为索引。这是 RT 部署的前提。
详见 09_Cross_Cutting/C_Distillation.md。
12. 测试策略
12.1 单元层
Engine/Servo/Finstruct 默认构造测试。EngineMech::initial(spec)在不同 spec 下的初值正确性。
12.2 组件层
Assembler::instantiate_world_state_():给定 YAML,验证ws.bodies字段(数量、id、spec 指针匹配)。- spec 指针稳定性:装配后多次访问
engines[0].spec,地址不变。
12.3 集成层
- 整循环跑 1s,验证设备状态与物理状态联合演化(如 ECU 点火 → EngineMech FSM → spatial 加速度上升)。
详见 08_Verification/Test_Strategy.md。
13. 引用
- Blueprint §2.6.1(复合状态容器)、§7.5(RocketBody 形态)、§7.16(迁移条目)
Dual_Layer_RWS.md(RocketBody 作为 BodyRWS 的 State)WorldEnv_Assembly.md(spec 表归属与指针接线)Body_World_Tick.md(device step 函数同步推进两面)03_Avionics_and_Bus/Device_Dual_Face.md(device-as-unified-entity 详解)03_Avionics_and_Bus/Semantic_Bus_Pattern.md§4–§5(BusBuffer vs IBus 切割)05_Dynamics_Core/Topology_Algebra.md(world_stage 演化代数)