FCC State Machine: GNC Mealy + Stage Algebra
> Aligned with PCR Master Blueprint v1.0 — see Blueprint §3.4(GNC 三模块 Mealy 机)、§3.5(FccStage + FccStageOp)、§7.3(WorldStage 与 FccStage 双代数化)。 > 职责:FCC 内部包含两套并存的状态机: > 1. GNC Mealy 机:Navigation / Guidance / Control 三模块的细粒度算法状态(卡尔曼协方差、PID 积分项、姿态阵) > 2. FccStage Mealy 机:FCC 自身的宏观飞行阶段(Boost / Coast / Reentry / Landed 等),由 FccStageOp 代数演化 > > 这两套机器与 dynamics::algebra::WorldStage 完全独立——FCC 决策不直接驱动 WorldStage,二者通过 DiscreteEvent + ICU 间接耦合。 > C-Distillation note:FccStage 是 enum class : uint16_t,蒸馏后是 uint16_t;GNC Mealy 状态是裸 struct,蒸馏后是 PoD。
1. 两套状态机为什么并存
| 维度 | GNC Mealy | FccStage Mealy |
|---|---|---|
| 粒度 | 算法内态(协方差矩阵、PID 积分、四元数) | 任务相位(Boost / Coast / Landing / …) |
| 转换频率 | 每 tick 都演化(连续) | 跨数秒到数百秒触发(离散) |
| 触发条件 | 由数学公式驱动(如 EKF prediction) | 由 TaskCondition 触发(YAML 配置) |
| 数据归属 | FccState.{nav, guidance, control} | FccState.current_stage |
| 代数 | 无独立代数;状态直接 in-place 演化 | FccStageOp = variant<NoOp, Transition, ScheduleEvent> |
并存的原因:GNC 是连续控制系统的内部记忆,FccStage 是离散任务调度的外部坐标。把它们合到一起会破坏"算法不知道相位、相位不知道算法实现"的解耦。
2. GNC 三模块 Mealy 机(Blueprint §3.4)
2.1 形态
// fcc/state/FCCState.h(简化)
namespace fcc {
struct NavState {
Quat q_b2n; // 体到导航系四元数
Vec3_T<dynamics::frame::NUE> vel_nue;
Vec3_T<dynamics::frame::LLA> lla;
// EKF / SINS 内部
Matrix9x9 covariance;
Vec3 bias_g_estimated;
Vec3 bias_a_estimated;
};
struct GuidanceState {
enum class Mode : uint16_t { Ballistic, IterativeGuidance, TargetLock } mode;
Vec3 target_position;
Vec3 target_velocity;
Time t_remaining; // 关机预测
// 闭环迭代制导内部状态
double residual_dv;
};
struct ControlState {
Vec3 pid_integral_att; // 姿态 PID 积分项
Vec3 pid_integral_omega; // 角速率 PID 积分项
// 控制律内部记忆
Vec3 last_cmd;
};
struct FccState {
NavState nav;
GuidanceState guidance;
ControlState control;
FccStage current_stage; // ← 宏观相位(见 §3)
Time clock; // FCC 自身时钟(晶振孪生)
std::map<TaskName, Time> task_timers; // 多速率调度计时
std::deque<PendingEvent> pending_events; // 倒计时事件队列
FccOutFrame pending_output; // 本拍待发出
};
}2.2 Mealy 演化(每拍纯函数)
// fcc/navigation/Navigation.h(在 src/fcc/core/Navigation.h,待迁移)
namespace fcc::navigation {
// Mealy: NavState × ImuMsg × FccEnv → NavState' (含 output 副效)
NavState step_nav(const NavState& prev,
const ImuMsg& imu,
const FccEnv& env,
Time dt);
}
namespace fcc::guidance {
GuidanceState step_guidance(const GuidanceState& prev,
const NavState& nav, // 上游 GNC 模块输出
const FccEnv& env,
Time dt);
}
namespace fcc::control {
// Mealy 的 output 部分:直接产出 controls
std::pair<ControlState, ControlOutput>
step_control(const ControlState& prev,
const GuidanceState& g,
const NavState& nav,
const FccEnv& env,
Time dt);
}特性:
- 纯函数:(prev_state, inputs) → (next_state, output)
- 不知道 FccStage:算法函数与相位解耦
- 不知道 Bus / Free Monad:纯数学
- 不知道物理引擎:FCC 只看
FccInFrame.imu/.gps/.events
2.3 在四段式预编译架构中的位置
GNC step 函数不被外壳每拍按 schedule 查表调用——那是 cache-thrashing 反模式。架构上 GNC 算法位于四段式的第 ①段(pipeline 工厂)内部,工厂本身层级无知(不知道自己被装在哪个 FccStage):
fcc/algorithms/ step_nav / step_guidance / step_control (纯函数,不知道 stage)
▲
│ 被调用
│
fcc/pipelines/ make_iter_guidance_pipeline(env) → InnerPipeline
│ (工厂层级无知:只知道"是迭代制导那套",不知道 FccStage)
▲
│ 选择 + 绑定
│
fcc/firmware/ MissionProfile { FccStage → factory }
compile_fcc(profile, env) → CompiledFcc.per_stage[]
(这里才知道"Boost1 用迭代制导")
▲
│ 启动期触发
│
runtime::Assembler 调用一次 fcc::firmware::compile_fcc(...)
│
│ const CompiledFcc& 传递
▼
simulation::FccTick 每拍读 Bus → 喂解释器 → 写 Bus(跨域)关键解耦:step_nav / step_guidance / step_control 永远不知道 FccStage;只有 fcc/firmware/MissionProfile 知道"Boost1 用哪个工厂"。详见 Pipeline_Factory_and_Compilation.md。
工厂示例:层级无知
// fcc/pipelines/iter_guidance_pipeline.cpp
// 签名里没有 FccStage——工厂不关心自己装在哪
InnerPipeline fcc::pipelines::make_iter_guidance_pipeline(const FccEnv& env) {
const auto& nav_cfg = env.nav_config;
const auto& guidance_cfg = env.guidance_config_iter;
const auto& control_cfg = env.control_config_att_pid;
constexpr auto rates = SchedulePolicy::IterGuidance; // 编译期常量
return [&nav_cfg, &guidance_cfg, &control_cfg]
(const FccState& prev, const ImuMsg& imu, Time t, Time dt) -> FccState {
FccState s = prev;
// 多速率:编译期已知 SINS@100Hz / IterGuidance@10Hz / AttPID@50Hz
// rates.*_due(t) 是简单整除判断,无 map / hash
if (rates.nav_due(t)) s.nav = step_nav(s.nav, imu, nav_cfg, dt);
if (rates.guidance_due(t)) s.guidance = step_guidance(s.guidance, s.nav, guidance_cfg, dt);
if (rates.control_due(t)) {
auto [next_c, out] = step_control(s.control, s.guidance, s.nav, control_cfg, dt);
s.control = next_c;
s.pending_output = out;
}
// 注意:工厂不在这里做 FccStage 转换——转换是另一条职责线
// FccStage 演化在 §3 的 evolve_stage + transitions check 中处理
return s;
};
}Mission profile:FccStage → 工厂的唯一映射地
// fcc/firmware/MissionProfile.cpp
MissionProfile fcc::firmware::default_mission() {
using namespace fcc::pipelines;
MissionProfile p{};
p.stage_to_factory[(size_t)FccStage::Boost1] = make_iter_guidance_pipeline;
p.stage_to_factory[(size_t)FccStage::Coast1] = make_ballistic_pipeline;
p.stage_to_factory[(size_t)FccStage::TerminalDescent] = make_recovery_pipeline;
// ...
return p;
}顶层外壳:所有 stage 共用一段代码
// fcc/free_monad/Strategies.h
FccFree<std::monostate> flight_control_loop(const CompiledFcc& compiled) {
return read_imu() >>= [&compiled](ImuMsg imu) {
return get_time() >>= [&compiled, imu](Time t) {
return get_state() >>= [&compiled, imu, t](FccState s) {
const InnerPipeline& pipeline = compiled.per_stage[(size_t)s.current_stage];
FccState next = pipeline(s, imu, t, compute_dt(s.clock, t));
return update_state(next) >> output_controls(next.pending_output);
}; }; };
}关键性能点:
- Env 不每拍传递:FccEnv heavy(气动表、增益矩阵、调度表),其切片在工厂构造期以
const&烘焙进闭包,cache locality 由编译器决定 - 速率静态化:
rates.nav_due(t)编译为(t.ticks % 10) == 0之类的常量比较 - module 派发静态化:每个 stage 编译期已知调用哪几个
step_*,编译器可全部 inline - stage 切换零成本:array 索引切到不同闭包,无 strategy swap、无 AST 重建
注意三条层级承诺:
step_nav/step_guidance/step_control不知道FccStage(算法 ↔ 相位解耦)fcc/pipelines/make_*_pipeline不知道自己装在哪个FccStage(工厂 ↔ mission 解耦)simulation/pipeline/FccTick不知道 FccStage→factory 映射(跨域 ↔ firmware 解耦)
详见 Free_Monad_DSL.md §5 / §7、Pipeline_Factory_and_Compilation.md、Static_Compilation_FSM.md §3 Layer 3。
3. FccStage Mealy 机(Blueprint §3.5)
3.1 强类型定义
// fcc/stage/FccStage.h
namespace fcc::stage {
enum class FccStage : uint16_t {
PreLaunch,
Boost1,
Coast1,
Boost2,
Coast2,
Reentry,
TerminalDescent,
Landed,
Aborted,
// YAML 可扩展;FccStage 的具体取值集合由 mission 决定
};
}3.2 FccStageOp 代数
// fcc/stage/FccStageOp.h
namespace fcc::stage {
struct NoOp {};
struct Transition {
FccStage next;
};
// 注册一个未来事件(Fcc_Clock 倒计时)
struct ScheduleEvent {
Time fire_at; // 绝对仿真时间
EventKind kind; // MECO / Separation / Ignition / Abort / …
uint32_t target_id; // EngineId / BoltId
};
// 立即触发离散事件(写入本拍 FccOutFrame.events)
struct EmitDiscreteEvent {
EventKind kind;
uint32_t target_id;
};
using FccStageOp = std::variant<NoOp, Transition, ScheduleEvent, EmitDiscreteEvent>;
// 演化:FccStage × FccStageOp → FccStage'
FccStage evolve_stage(FccStage prev, const FccStageOp& op);
}3.3 触发:TaskCondition + 数据驱动
绝不在 C++ 代码里硬编码 if (time > 10s) transition_to(Coast1)。触发条件定义在 YAML 中:
# fcc_schedule.yaml
stages:
Boost1:
tasks:
- { name: SINS_Nav, module: NAV, period_ms: 10 }
- { name: Pitch_Program, module: GUIDANCE, period_ms: 100 }
- { name: Att_PID, module: CONTROL, period_ms: 20 }
transitions:
- when:
condition: TimeSinceLiftoff_gt
value_s: 65.0
op:
kind: EmitDiscreteEvent
event_kind: MECO
target_id: 0
- when:
condition: AltitudeMSL_gt
value_m: 70000.0
op:
kind: Transition
next: Coast1FccCore 在每拍检查 transitions[*].when 是否满足,命中则把对应 FccStageOp 应用到 FccState.current_stage 与 FccState.pending_events。
TaskCondition 是 sum type,所有可能的触发谓词在 fcc/config/TaskCondition.h 中枚举,不允许业务模块自由插入新的 C++ 谓词;要加新条件 = 加新 enum 值 + 加对应纯函数判断器。
3.4 与 WorldStage 的关系(双代数化)
| FccStage | WorldStage | |
|---|---|---|
| 归属 | fcc/stage/ | dynamics_core/algebra/ |
| 代数 | FccStageOp | StageOp |
| 演化 | evolve_stage | evolve_topology<Body> |
| 关心什么 | FCC 任务相位(用哪套调度表) | 火箭物理拓扑(分离 / 落级 / 多体合并) |
| 触发源 | TaskCondition(YAML) | DiscreteEvent → interpret_event → StageOp |
两者通过 DiscreteEvent + ICU 间接关联:
FCC 决策 (FccStage 转换) → 产生 EmitDiscreteEvent (Fcc中Op的Action)
↓
FccOutFrame.events.push(MECO)
↓
Bus → ICU
↓
ICU 把 MECO 转写为 EngineMech 的 ShutdownEngine
↓ (并发 Bus DiscreteEvent → DynInFrame)
sim::world_tick 末尾收集 events
↓
sim::interpret_event(MECO) → StageOp::TransitionOp{next=Coast}
↓
dynamics::algebra::evolve_topology(bodies, op)
↓
新 WorldStage关键解耦:
- FCC 永远不直接调用
evolve_topology - WorldStage 转换由 simulation 解释 DiscreteEvent 决定,不由 FCC 决定
- FCC 不知道分离后的 body 数量、不知道气动模型是否切换
4. Fcc_Clock 与事件倒计时
Fcc_Clock 是 FCC 自身的"晶振孪生"(详见 Static_Compilation_FSM.md §2):
struct FccClock {
Time now;
std::priority_queue<PendingEvent> scheduled; // 按 fire_at 排序
};
struct PendingEvent {
Time fire_at;
EventKind kind;
uint32_t target_id;
bool operator>(const PendingEvent& o) const { return fire_at > o.fire_at; }
};每拍 FCC 入口:
void fcc_tick_head(FccState& s, Time t) {
s.clock.now = t;
while (!s.clock.scheduled.empty() &&
s.clock.scheduled.top().fire_at <= t) {
auto ev = s.clock.scheduled.top(); s.clock.scheduled.pop();
s.pending_output.events.push_back({ev.kind, ev.target_id});
}
}ScheduleEvent 代数算子把事件写入 scheduled。到点自动写入 pending_output.events,下游由 Bus 投递给 ICU。
5. 完整 tick 流程
FCC tick (50Hz),由 `simulation::pipeline::FccTick::run_one_tick` 驱动:
① fcc_tick_head(在 InnerPipeline 进入前由解释器调用):
- 推进 FccClock
- 把到期的 scheduled events 写入 pending_output.events
② Bus decode(在 simulation::FccTick 内):
- bus::decode_to_fcc_in 把上拍 Bus 上的 IMU/GPS payload 装包为 FccInFrame
③ Free Monad 解释器 interpret(flight_control_loop(compiled)):
- read_imu / get_time / get_state(Layer 1 I/O)
- 通过 compiled.per_stage[current_stage] O(1) 拿到 InnerPipeline
- InnerPipeline(s, imu, t, dt) 内部按编译期固化速率跑 GNC 三模块
- 检查 transitions → 应用 FccStageOp 演化 current_stage
- update_state(next) + output_controls(cmd) + write_telemetry(log)
④ Bus encode(在 simulation::FccTick 内):
- bus::encode_from_fcc_out 把 pending_output 拆为 ScuPayload / IcuPayload publish> 注意 ②④ 跨域帧转换发生在 simulation/pipeline/FccTick,FCC 内部不知道 Bus(详见 Pipeline_Factory_and_Compilation.md §3.4)。
6. 反模式
| 反模式 | 为什么不行 |
|---|---|
if (time > 10s) current_stage = Coast1; 硬编码 | 违反 Blueprint §7.2(YAML 驱动);违反 §7.3(FccStage 由代数演化) |
GNC 模块内直接读 FccState.current_stage | 破坏 GNC ↔ Stage 解耦;GNC 算法应只依赖 (prev_state, inputs) |
FCC 直接调用 evolve_topology | 越权进入 dynamics_core;违反 §3.3 隔离 |
| FccStageOp 与 StageOp 合并为同一个代数 | 违反 §7.3 双代数化;二者关心的"是什么"完全不同 |
| 把 PID 积分状态放进 FccStage | Stage 是粗粒度相位;连续控制内态在 ControlState |
| 在 Free Monad 算子里写 GNC 算法 | 违反 §3.1 三层架构;GNC 是纯函数,不是 I/O 算子 |
在 GNC 内调用 Fcc_Clock(耦合硬件) | 时间应作为参数(Time t, Time dt)透传,不要让算法依赖时钟实例 |
fcc/pipelines/make_*_pipeline 内部 if 检查 FccStage | 工厂必须层级无知(§2.3);要分支应在 fcc/firmware/MissionProfile 通过选择工厂实现 |
simulation/pipeline/FccTick 内做 FccStage→algorithm 决策 | 跨域判据:FccStage 是 intra-FCC firmware 知识,应在 fcc/firmware/(详见 Pipeline_Factory_and_Compilation.md) |
把 MissionProfile 表写在 runtime/Assembler 内 | Assembler 应薄;只触发 fcc::firmware::compile_fcc,不持有 mission 数据 |
7. 测试策略
| 测试目标 | 级别 | 描述 |
|---|---|---|
step_nav 纯函数等价类 | bench | 喂同样 (prev, imu),多次调用结果完全一致 |
evolve_stage 代数律 | bench | NoOp 是单位元;Transition 幂等;ScheduleEvent 不修改 current_stage |
| TaskCondition 配置驱动 | bench | 喂不同 (FccState, FccEnv),验证转换触发与 schedule.yaml 一致 |
| Fcc_Clock 倒计时 | bench | 注册 MECO @ t=65s,跑到 64.999s 不触发,65.001s 触发 |
| GNC 三模块联调 | subsystem | 模拟 60s 上升段,验证姿态收敛 + MECO 时机 |
| WorldStage ↔ FccStage 解耦 | subsystem | 注入"分离失败"故障,验证 FCC 依然按 MECO+T 转入 Coast |
详见 08_Cross_Cutting/Testing_Framework.md。
8. C-Distillation 视角
C++ 阶段:
FccStage是enum class : uint16_tFccStageOp是std::variantevolve_stage是泛型自由函数
C 蒸馏阶段:
FccStage退化为uint16_t(值集合不变)FccStageOp退化为带 tag 的union(discriminated union)evolve_stage退化为static FccStage evolve_stage(FccStage prev, FccStageOp op),switch on tag- TaskCondition 谓词由编译期生成的 jump table 索引(YAML → table → flat C code)
整个状态机的语义与代数律完全保留,只是表面 syntax 变了。
9. Cross References
- 三层 FCC 架构(Free Monad / FSM Router / RWS Pipelines)→
Static_Compilation_FSM.md - Free Monad DSL(I/O 算子集合 + 四段式 strategy)→
Free_Monad_DSL.md§5 / §7 - 四段式层级职责详图与禁忌 →
Pipeline_Factory_and_Compilation.md - 解释器与 RWS →
Interpreter_and_RWS.md - YAML 调度(ScheduleConfig + TaskCondition)→
Data_Driven_Scheduling.md - 函数式核心 vs 命令式外壳的边界 →
Hardware_Decoupling.md - Navigation 算法细节 →
Navigation.md - DiscreteEvent 跨域路由 →
06_Simulation/World_Tick_Event_Routing.md(待写) - WorldStage 与 StageOp →
05_Dynamics_Core/Topology_Algebra.md - Blueprint §2.6.4(跨域判据)/ §3.4 / §3.5 / §7.3