PCR 架构重构方案 (PoC 落地版)
Context: FCC & Dynamics Simulation (C++20) Date: 2026-05-05 Scope: 针对多航电闭环、语义总线抽象以及阶段/拓扑演化的详细实施蓝图。
1. 顶层架构:配置组合与宇宙模型
1.1 装配公式
系统的终极装配由四个完全正交的维度决定: Run = Rocket × Mission × World × Deployment
- Rocket: 物理资产与航电拓扑的装配清单(谁身上挂了 FCC,谁有发动机)。
- Mission: 任务时序、动力学初始约束(如是否锁死在发射台)、分离事件定义。
- World: 环境真相(大气、引力、发射场坐标)。
- Deployment: 部署模式(SIL、Dyn-HIL、FCC-HIL)。Runner 根据此配置决定加载哪些配置、实例化哪些代码。
1.2 PoC 验证范围
- Rocket: 二子级架构,包含:一子级(含中心与周边发动机)、二子级、整流罩、卫星载荷。
- Mission: 包含点火、起飞、一子级关机、一二级分离、一子级返回点火 ‖ 二级入轨点火(并行)。
- World: 维持现有不变。
- Deployment: 仅实现 SIL(透明无损总线)。
1.3 World 顶层容器
World 维持作为系统最高层容器的设计,不新增 Universe 层。 分离发生后,原有的 COMPOSITE Body 裂变为多个新的 Body。 航电系统与 Body 是一对 0..1 的关系。一子级返回需要航电,溅落/整流罩则无航电。
2. Mission 事件与 per-body 阶段代数
动力学域的阶段(Stage)按 per-body 独立维护。
2.1 PoC 阶段枚举
| Body | 可能的 Stage Tags |
|---|---|
| COMPOSITE | prelaunch, engine_startup, power_ascend |
| STAGE1 | descent_ballistic, descent_powered |
| STAGE2 | coast_to_ignition, upper_ascend |
| FAIRING / SATELLITE | ballistic (唯一且最终状态) |
2.2 核心事件流与触发源
| Mission 事件 | 触发源 | Body Stage 转移 | Hardware / Topology 变化 |
|---|---|---|---|
| IGNITION | FCC 下发 IcuCmd(Ignite) | prelaunch → engine_startup | 5 台发动机 FSM 进入 starting |
| LIFTOFF | 物理条件 (thrust > weight) | engine_startup → power_ascend | 无硬件状态变化 |
| STAGE1_SHUTDOWN | FCC 下发 IcuCmd(Shutdown) | 不变 (仍在 power_ascend) | 周边发动机 running → stopping → idle |
| FIRST_STAGE_SEP | FCC 下发 IcuCmd(FireBolt) | COMPOSITE 解体,产生四个新 Body,各自进入初始 Stage | ICU FSM armed → firing → done,产生拓扑突变事件 |
| STAGE1_REENTRY_IGNITION | fcc1 下发 IcuCmd | descent_ballistic → descent_powered | 一子级中心发动机 idle → starting → running |
| STAGE2_IGNITION | fcc2 下发 IcuCmd | coast_to_ignition → upper_ascend | 二子级发动机 idle → starting → running |
> 关键解耦: ICU 状态机与物理 Stage 严格解耦。程序不做“当 Stage=X 时,发动机必须为 Y”的断言校验,此一致性完全由配置正确性保证。
3. 硬件状态与 ICU 状态机 (Mealy Machine)
硬件状态管理分为跨 tick 累积的长期状态和瞬时产出。
// 持久化状态
struct HardwareState {
std::unordered_map<uint32_t, EngineFsm> engine_fsms; // {idle, starting, running, stopping}
std::unordered_map<uint32_t, IcuFsm> icu_fsms; // {armed, firing, done}
std::unordered_map<uint32_t, double> fsm_timers; // 延时与过程倒计时
};
// 瞬时状态 (由 Phase 1 产出,注入 Phase 2)
struct HardwareOutput {
std::vector<EngineEffect> engine_effects; // 推力输出
std::vector<BusMessage> emitted_bus_messages; // 上行遥测
std::vector<TopologyEvent> topology_events; // 延时结束后触发的物理断裂
};4. 语义总线协议 (Bus ADT)
在 SIL 部署下,总线作为透明路由存在,但必须严格遵守三元组约束和时钟时间戳约束。
4.1 消息载体结构
struct BusMessage {
uint32_t src; // 信源地址
uint32_t dst; // 信宿地址
Time emitted_at; // 发送端时钟
BusMessagePayload payload;
};4.2 强类型 Payload 集合
消息体不得增减结构,保持贴近物理的原始传递:
// 1. IMU -> FCC: 传感器增量数据
struct ImuPayload {
Vec3 delta_v_body;
Vec3 delta_theta_body;
};
// 2. GPS -> FCC: 导航真值数据
struct GpsPayload {
Vec3 pos_ecf;
Vec3 vel_ecf;
};
// 3. FCC -> SCU: 连续控制 (直接提供摆角参与计算)
struct ScuPayload {
struct EngineGimbalCmd { uint32_t slot_id; double pitch; double yaw; };
std::vector<EngineGimbalCmd> commands;
};
// 4. FCC -> ICU: 离散时序触发指令
struct IcuPayload {
enum class Cmd { IgniteEngines, ShutdownEngines, FireSeparationBolt };
Cmd cmd;
std::vector<uint32_t> targets; // 作用的槽位或螺栓地址
};
using BusMessagePayload = std::variant<ImuPayload, GpsPayload, ScuPayload, IcuPayload>;4.3 SIL 透明总线抽象
class IBus {
public:
virtual void publish(BusMessage msg) = 0;
virtual std::vector<BusMessage> poll(uint32_t local_addr) = 0;
virtual Clock& clock() = 0;
virtual ~IBus() = default;
};
// SIL 模式:无队列堆积,同 tick 直接路由
class TransparentBus : public IBus { /* 内存 HashMap 路由 */ };5. 分离事件与航电拓扑演化
由于火箭分离后可能会存在多个并行的航电闭环,我们需要定义在分离瞬间,子结构如何继承原有的航电状态。
struct TopologyEvent {
BodyId source; // 如 COMPOSITE
std::vector<BodySpawn> produces; // 如 STAGE1, STAGE2...
};
enum class AvionicsAction {
ActivateClone, // 深拷贝原系统(例如 fcc1,获取了所有的积分导航变量)
Continue, // 原系统沿用(原 fcc,继续给 STAGE2 用)
None // 纯物理弹道(如 FAIRING)
};
struct BodySpawn {
BodyId new_id;
BodyStage::Tag initial_stage;
AvionicsAction avionics;
};一子级返回时,一子级物理上携带的 FCC(fcc1)在分离事件时被激活,通过 ActivateClone 获取原组合体 FCC 的完整状态(偏置、导航位姿),随后二者在各自的总线上独立运行。
6. RuntimeTables (扁平化汇编输出)
彻底废除过度设计的 AssembledPlant 反射树。Runner 在启动期解析 YAML 后,生成极简的、全 uint32_t O(1) 寻址的静态运行期表格:
struct RuntimeTables {
std::vector<EngineRuntime> engines; // 下标为 slot_id
std::vector<IcuRuntime> icus;
std::vector<ImuRuntime> imus;
};前端 UI 若需展示,直接拉取并解析原始 YAML 文本;若需注入仿真修改,则生成 tmp.yaml(overrides)重新执行 Assembler。
7. 数据流主循环 (The World Tick)
一个标准的 0.1ms (或者动态 dt) 的 Tick 流程:
1. 遍历所有带 AvionicsStack 的 Body:
1a. FCC 唤醒: poll 传感器数据 -> Navigation/Guidance/Control -> publish SCU/ICU 指令
1b. IMU/GPS 唤醒: 读取 Body 物理真值 -> publish IMU/GPS 遥测
1c. ICU 唤醒: poll 指令 -> 推进 Mealy 状态机 -> 产出 (推力输出 & 拓扑突变事件)
1d. SCU 唤醒: poll 指令 -> 更新推力管道的摆角
2. 遍历所有的 Body (无论是否有 Avionics):
2a. 注入推力输出,计算 Thrust
2b. 结合 World 大气,计算 Aero
2c. 结合 World 引力,计算 Gravity
2d. RK4 积分
3. 拓扑演化 (Apply Topology Events):
3a. 消费所有的拓扑突变事件 (如 Separation)
3b. 销毁源 Body,创建新 Body
3c. 按 AvionicsAction 分配、深拷贝、或舍弃 FCC/Bus本重构蓝图抛弃了臃肿的抽象,坚实地贴合了 PCR 分层原则,使得逻辑既满足底层 TDD/纯函数的干净约束,又可无缝扩展至任意半实物联合部署场景。