Skip to content

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 MealyFccStage 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 形态

cpp
// 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 演化(每拍纯函数)

cpp
// 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

工厂示例:层级无知

cpp
// 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 → 工厂的唯一映射地

cpp
// 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 共用一段代码

cpp
// 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 重建

注意三条层级承诺

  1. step_nav / step_guidance / step_control 不知道 FccStage(算法 ↔ 相位解耦)
  2. fcc/pipelines/make_*_pipeline 不知道自己装在哪个 FccStage(工厂 ↔ mission 解耦)
  3. simulation/pipeline/FccTick 不知道 FccStage→factory 映射(跨域 ↔ firmware 解耦)

详见 Free_Monad_DSL.md §5 / §7、Pipeline_Factory_and_Compilation.mdStatic_Compilation_FSM.md §3 Layer 3。


3. FccStage Mealy 机(Blueprint §3.5)

3.1 强类型定义

cpp
// 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 代数

cpp
// 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 中:

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: Coast1

FccCore 在每拍检查 transitions[*].when 是否满足,命中则把对应 FccStageOp 应用到 FccState.current_stageFccState.pending_events

TaskCondition 是 sum type,所有可能的触发谓词在 fcc/config/TaskCondition.h 中枚举,不允许业务模块自由插入新的 C++ 谓词;要加新条件 = 加新 enum 值 + 加对应纯函数判断器。

3.4 与 WorldStage 的关系(双代数化)

FccStageWorldStage
归属fcc/stage/dynamics_core/algebra/
代数FccStageOpStageOp
演化evolve_stageevolve_topology<Body>
关心什么FCC 任务相位(用哪套调度表)火箭物理拓扑(分离 / 落级 / 多体合并)
触发源TaskCondition(YAML)DiscreteEvent → interpret_event → StageOp

两者通过 DiscreteEvent + ICU 间接关联

text
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):

cpp
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 入口:

cpp
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 流程

text
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 积分状态放进 FccStageStage 是粗粒度相位;连续控制内态在 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/AssemblerAssembler 应薄;只触发 fcc::firmware::compile_fcc,不持有 mission 数据

7. 测试策略

测试目标级别描述
step_nav 纯函数等价类bench喂同样 (prev, imu),多次调用结果完全一致
evolve_stage 代数律benchNoOp 是单位元;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++ 阶段:

  • FccStageenum class : uint16_t
  • FccStageOpstd::variant
  • evolve_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