Contracts: Per-Body Frames
> Aligned with PCR Master Blueprint v1.0 — see Blueprint §2.2. > 职责:定义所有跨子系统对话用的 POD 数据包。这些类型是 header-only,零 .cpp 文件,纯 struct,可被任何子域(plant / avionics / fcc / bus / dynamics_core / simulation)#include。 > C-Distillation note: 所有类型都是平铺 struct,无虚函数、无继承、无 STL 容器(除 std::vector / std::variant / std::optional,蒸馏时会被替换为定长 C 数组 + tag union + nullable 指针)。
1. 为什么需要 contracts/?
13 库依赖图中,业务子域之间互不依赖(除了 avionics→plant 的特例)。但跨子域通信必然需要共享的数据语义。如果让 fcc 直接 include avionics 的内部 header 来读 IMU 数据,整个无知性承诺就崩塌了。
contracts/ 就是中立词汇表:
- fcc 只 include
contracts/来理解FccInFrame.imu.delta_v - avionics 只 include
contracts/来产出BusMessage与DynInFrame - dynamics_core 不 include
contracts/(它是 universal,连具体数据包都不知道)— 唯一例外是Forces在 dynamics_core 内部定义 - simulation 是唯一一层会全部 include,因为它装配所有边界
graph TB
contracts[contracts/PerBodyFrames.h<br/>header-only POD]
plant -.read.-> contracts
avionics -.read+write.-> contracts
fcc -.read+write.-> contracts
bus -.read+write.-> contracts
sim -.read+write.-> contracts
contracts --> types[types/Vec3, Quat, Time]
contracts --> frames[frames/]2. 强类型 IDs
避免 uint32_t 互相误传:
// contracts/Ids.h
enum class BodyId : uint32_t {};
enum class EngineId : uint32_t {};
enum class ServoId : uint32_t {};
enum class FinId : uint32_t {};
enum class ImuId : uint32_t {};
enum class GpsId : uint32_t {};
enum class IcuId : uint32_t {};
enum class BusAddr : uint32_t {};enum class 强类型 + uint32_t 底层 → 无虚表、无封装、与 C 完全互兼。
BodyId 直接是 WorldState.bodies 的下标(uint32_t cast),无 hash map 开销。EngineId 在 RocketBody.engines 内 unique,跨 body 不复用。
3. Dynamics 侧契约(plant ⇄ dynamics_core)
3.1 DynInFrame:Avionics → Plant Physics
per-body,每 tick 由 avionics device step functions 累积生成,喂给 plant::physics::compute_* 与 dynamics_core pipeline。
struct DynInFrame {
BodyId body_id;
std::vector<EngineEffect> engine_effects;
std::vector<FinDeflection> fin_deflections;
std::vector<DiscreteEvent> events;
};
struct EngineEffect {
EngineId engine_id;
double vacuum_thrust; // 真空推力(N)
double mass_flow_fuel; // 燃料质量流(kg/s)
double mass_flow_ox; // 氧化剂质量流(kg/s)
double current_throttle; // 0.0 ~ 1.0
Angle nozzle_pitch; // 俯仰摆角
Angle nozzle_yaw; // 偏航摆角
};
struct FinDeflection {
FinId fin_id;
Angle actual_angle; // 来自 plant::hardware::FinMech 演化
};关键设计:DynInFrame 不携带 Vec3_T<BODY> force_b 之类的"已经算好的合力"。力的计算是 plant/physics/compute_* 的职责,本帧只携带"激励量"(engine state、fin angle)。这保持了 plant/physics 的"配方与流水线分离"。
3.2 DynOutFrame:Plant Physics → Avionics
每 body 一份,作为 Sensor 真值输入。所有空间量用 ECF。
struct DynOutFrame {
BodyId body_id;
Vec3_T<frame::ECF> pos_ecf;
Vec3_T<frame::ECF> vel_ecf;
Quat_T<frame::LIC, frame::BODY> att_q; // 体相对 LIC 的姿态
Vec3_T<frame::BODY> omega_b; // 体角速度
Vec3_T<frame::BODY> spec_force_b; // 比力(IMU 测量)
double total_mass;
Vec3_T<frame::BODY> centroid_b;
};用法:
ImuUnit::step读spec_force_b+omega_b,加噪声/量化,产出ImuPayloadGpsUnit::step读pos_ecf+vel_ecf,加噪声/延迟,产出GpsPayload- Telemetry 读所有字段下传地面
4. FCC 侧契约(bus ⇄ fcc)
4.1 FccInFrame:Bus → FCC
struct FccInFrame {
ImuPayload imu;
GpsPayload gps;
Time timestamp;
};精简结构:FCC 只关心 IMU 与 GPS 两类传感器输入。其他航电态信息(如 ECU 反馈)通过 BusBuffer 独立访问,不打包进 FccInFrame。
4.2 FccOutFrame:FCC → Bus → Avionics
struct FccOutFrame {
ScuPayload scu; // 伺服控制指令
IcuPayload icu; // 时序/事件指令
EcuPayload ecu; // 引擎控制指令
TelemetryPayload tlm; // 下行遥测
};每个 *Payload 直通对应的设备 step function 解码。FccOutFrame 是 FCC 周期性产出的"控制周期结论"。
5. Bus 侧契约(所有 device ⇄ bus)
5.1 BusMessage:总线上的统一信封
struct BusMessage {
BusAddr src;
BusAddr dst;
Time emitted_at;
std::variant<
ImuPayload, GpsPayload,
ScuPayload, EcuPayload, IcuPayload, FinCtrlPayload,
TelemetryPayload
> payload;
};std::variant 是 tagged union — 在 C-Distillation 阶段会被替换为:
struct BusMessage {
uint32_t src;
uint32_t dst;
int64_t emitted_at; // ticks
uint8_t payload_kind;
union {
ImuPayload imu;
GpsPayload gps;
/* ... */
} payload;
};5.2 各类 Payload
// 传感器输出 payload(来自 avionics::device::*)
struct ImuPayload {
Vec3_T<frame::BODY> delta_v; // 周期内累积比力积分
Vec3_T<frame::BODY> delta_theta; // 周期内累积角速度积分
Time dt;
};
struct GpsPayload {
Vec3_T<frame::ECF> pos_ecf;
Vec3_T<frame::ECF> vel_ecf;
Time timestamp;
uint8_t fix_quality;
};
// 控制 payload(FCC → 设备)
struct ScuPayload {
ServoId servo_id;
Angle target_angle;
};
struct EcuPayload {
EngineId engine_id;
enum class Cmd : uint8_t { Ignite, Throttle, Shutdown };
Cmd cmd;
double throttle_target;
};
struct IcuPayload {
enum class Event : uint8_t { StageSeparation, FinDeploy, BoltFire };
Event event;
Time execute_at;
};
struct FinCtrlPayload {
FinId fin_id;
Angle target_angle;
};
struct TelemetryPayload {
// 下行遥测打包格式(视具体地面站协议而定)
std::array<uint8_t, 128> frame;
uint16_t length;
};6. 离散事件(DiscreteEvent)
DiscreteEvent 是跨 tick 的状态拓扑改变信号。它被 avionics device step function 产出,由 dynamics::algebra::evolve_topology 在下个 tick 之前消费。
struct DiscreteEvent {
enum class Kind : uint8_t {
EngineShutdown,
BoltFired,
StageSeparation,
FinDeploy
};
Kind kind;
BodyId source_body;
uint32_t payload_id; // 配合 kind 解释(如 EngineId 数值)
Time emitted_at;
};使用流程:
- ECU step 检测到推力耗尽 → 产出
DiscreteEvent{EngineShutdown, body_id, engine_id, now} - 写入当前 body 的
DynInFrame.events sim::world_tick末尾,dynamics::algebra::evolve_topology扫描所有 body 的 events,决定WorldStage转移与 body 拓扑变更- 下一 tick 开始时,新拓扑生效
> 详见 05_Dynamics_Core/Topology_Algebra.md 与 Blueprint §2.9 / §7.3。
7. 命名与设计约定
| 约定 | 说明 |
|---|---|
后缀 _ecf / _lic / _b | 显式标注空间向量所在 frame,避免靠类型推断 |
Payload 后缀 | 仅用于 BusMessage::payload 内部 variant 候选 |
Frame 后缀 | 跨子域大颗粒数据包(DynInFrame, FccOutFrame) |
Effect 后缀 | 设备 step 函数产出的"激励量"(EngineEffect) |
Event 后缀 | 离散拓扑改变信号(DiscreteEvent) |
| 强类型 ID | 全部 enum class : uint32_t {},无空 enumerator |
| 零虚函数 | 所有类型为 POD-like,可 trivially copy |
| 零 .cpp | contracts/ 全部 header-only |
8. 反模式(必须避免)
| 反模式 | 为什么不行 |
|---|---|
在 DynInFrame 里塞 Vec3_T<BODY> total_force_b | 越权:合力计算属 plant/physics,本帧只能携带"激励" |
在 FccInFrame 里塞 std::vector<BusMessage> uplink 队列 | uplink 通过 BusBuffer 独立通道,不入 FccInFrame |
在 BusMessage 加新 payload 类型时不更新 variant | variant 闭合,新 payload 必须显式列入 |
EngineId 直接传 uint32_t 字面量到接口 | 编译期防误传依赖 enum class |
在 contracts 里 include plant/model/EngineSpec.h | contracts 是中立层,不依赖任何业务子域 |
| 在 contracts 里定义带虚函数的 base class | 破坏 C-Distillation 路径 |
9. Cross References
- Frame 命名与转换 →
01_Foundation/Coordinate_Frames.md - 推力流如何从
EcuPayload走到EngineEffect→ Blueprint §4(推力跨域数据流五阶段) Forces(Monoid)与DynInFrame的区别 →05_Dynamics_Core/Forces_Monoid.md- BusMessage 编解码器 →
03_Avionics_and_Bus/Semantic_Bus_Pattern.md - 离散事件如何驱动拓扑演化 →
05_Dynamics_Core/Topology_Algebra.md