Free Monad DSL
> Aligned with PCR Master Blueprint v1.0 — see Blueprint §3.1(FCC 三层架构)、§3.3(FCC 隔离)、§7.12(C-Distillation 软化)。 > 职责:定义 FCC 顶层"做什么"的算子集合(FccOp)。这是 Layer 1 命令式外壳——只包含 I/O 与状态借入借出,不包含 GNC 数学算法。 > 关键架构原则:FccOp 只描述副作用 / 与外界交互;GNC 数学是纯 C++ 函数,不进入 FccOp 算子集合。这条约束是 v1.0 设计的关键演进——早期版本曾把 RunNavigation/RunGuidance 当算子是反模式。 > C-Distillation note:std::variant + visitor 在蒸馏后退化为 tagged union + switch。
1. 三层架构定位
┌──────────────────────────────────────────────────────────────┐
│ Layer 1:Declarative Strategy(声明式策略) │
│ Free<FccOp, A> │
│ "做什么"——读 IMU / 取 State / 输出 controls │
│ 无副作用,只构造 AST │
└──────────────────────────────────────────────────────────────┘
│ interpret
▼
┌──────────────────────────────────────────────────────────────┐
│ Layer 2:Interpretation(解释执行) │
│ FccInterpreter(visitor)+ RWS<FccEnv, FccLog, FccState> │
│ "怎么做"——访问内存 / 时钟 / 调用纯函数 │
└──────────────────────────────────────────────────────────────┘
│ 调用
▼
┌──────────────────────────────────────────────────────────────┐
│ Layer 3:Pure Functional Core(函数式核心) │
│ step_nav / step_guidance / step_control │
│ 纯 C++ 函数,零副作用,可独立测试 │
└──────────────────────────────────────────────────────────────┘DSL 在 Layer 1,只做 I/O 抽象;GNC 算法在 Layer 3。FccOp 不包含 RunNavigation / RunGuidance / CalcControl 这些"假算子"。
2. Algebra:实际的 FccOp(与代码对齐)
来自 src/fcc/free_monad/FccOp.h:
namespace fcc {
// 1. 读 IMU 真值(从 FccInFrame)
struct ReadIMU {}; // returns ImuMsg
// 2. 读当前 FCC 时钟
struct GetTime {}; // returns Time
// 3. 借入只读 Env
struct GetEnv {}; // returns const FccEnv*
// 4. 借入只读 State(解释器返回 copy)
struct GetState {}; // returns FccState
// 5. 写回 State
struct UpdateState { FccState state; }; // returns std::monostate
// 6. 写出 controls 到外部世界(Bus 装包责任在 simulation)
struct OutputControls { FccOutFrame cmd; }; // returns std::monostate
// 7. 写遥测日志(树状结构)
struct WriteTelemetry { FccLog log; }; // returns std::monostate
// 8. 写一行 debug 日志
struct LogMessage { std::string msg; }; // returns std::monostate
using FccOp = std::variant<
ReadIMU,
GetTime,
GetEnv,
GetState,
UpdateState,
OutputControls,
WriteTelemetry,
LogMessage
>;
}8 个算子的归类:
| 算子 | 类别 | 副作用 |
|---|---|---|
ReadIMU | 读输入 | 无(读 FccInFrame) |
GetTime | 读输入 | 无 |
GetEnv | 读配置 | 无 |
GetState | 借入内存 | 无(拷贝) |
UpdateState | 写内存 | ✅ 写 FccState |
OutputControls | 写外部 | ✅ 写 FccOutFrame |
WriteTelemetry | 写外部 | ✅ 写 FccLog |
LogMessage | 写外部 | ✅ 写 stdout / journal |
注意:没有 RunNavigation、RunGuidance、CalcControl、CheckStage、TransitionStage 这些"业务算子"。这些不是 I/O,不应出现在 DSL 里。
3. Free Monad 模板
monad::Free<F, A> 在 src/types/monad/Free.h:
template <typename F, typename A>
class Free {
// 二态:Pure 或 Suspend
// Pure { A val; }
// Suspend { F op; std::function<Free<F,A>(std::any result)> cont; }
public:
static Free pure(A val);
template <typename Result>
static Free liftF(F op); // 把算子升入 Free
bool is_pure() const;
A get_pure() const;
const F& get_op() const;
Free resume(std::any result) const; // 由 interpreter 喂回 result 推进
template <typename B>
Free<F, B> flatMap(std::function<Free<F, B>(A)> f) const;
template <typename B>
Free<F, B> operator>>=(std::function<Free<F, B>(A)> f) const { return flatMap(f); }
};std::any 是当前实现的 type erasure 通道;蒸馏阶段会被 template-resolved return types 替换(Blueprint §7.12 + Hardware_Decoupling.md §2.4)。
4. Smart Constructors(实际 DSL)
来自 src/fcc/free_monad/FccDSL.h:
namespace fcc {
template <typename A>
using FccFree = monad::Free<FccOp, A>;
inline FccFree<ImuMsg> read_imu();
inline FccFree<Time> get_time();
inline FccFree<const FccEnv*> get_env();
inline FccFree<FccState> get_state();
inline FccFree<std::monostate> update_state(const FccState& s);
inline FccFree<std::monostate> output_controls(const FccOutFrame& cmd);
inline FccFree<std::monostate> write_telemetry(const FccLog& log);
inline FccFree<std::monostate> log_msg(const std::string& msg);
}每个 smart constructor 调用 FccFree<T>::liftF<T>(Op{...}),结果类型与 FccOp 的语义返回类型一致。
5. Strategy 写法:四段式职责分离
关键模式:每个 FccStage 在初始化期预编译为一条独立的 inner pipeline;运行期外壳只做 I/O,按 current_stage 做 O(1) 查表派发。
> 为什么必须预编译:FccEnv 是 heavy 对象(气动表、增益矩阵、卡尔曼参数、调度表)。如果每拍在 strategy 内部 tasks_for_stage(stage) 查找 + 字符串 hash + 函数指针解引用 + AST 重建,cache locality 会塌掉。10ms 级热点循环这种动态派发是禁区。dynamics 侧同样按 WorldStage 预编译 BodyRWS pipeline,原因相同。
5.0 四段式职责分离(核心架构)
预编译涉及四个明确分层的职责,不要混在一起:
| 段 | 物件 | 归属库 | 知道什么 |
|---|---|---|---|
| ① Pipeline 工厂 | make_iter_guidance_pipeline(FccEnv) → InnerPipeline | fcc/pipelines/ | 只知道自己是"哪套算法 + 哪些速率";不知道自己将被用于哪个 FccStage |
| ② Mission Profile + 编译 | compile_fcc(MissionProfile, FccEnv) → CompiledFcc | fcc/firmware/ | 知道"哪个 FccStage 用哪个工厂"——这是 firmware mission 知识 |
| ③ 装配触发 | Assembler 启动期一次调用 fcc::compile_fcc(...) | runtime/ | 不知道 FCC 内部结构;只是 wire |
| ④ 每拍 tick | FccTick 读 Bus → 喂解释器 → 写 Bus | simulation/pipeline/ | 持有 const CompiledFcc&;做跨域帧转换 |
> 依据:Blueprint §2.6.4 的判据——跨域内容才进 simulation/;FccStage→Pipeline 映射是 intra-FCC,不进 simulation/。详见 Pipeline_Factory_and_Compilation.md。
fcc/pipelines/ ← 层级无知工厂(不知道 FccStage)
▲
│ 选择 + 绑定
│
fcc/firmware/ ← MissionProfile + FccCompiler(intra-FCC firmware 知识)
▲
│ 启动期调用一次
│
runtime::Assembler ← 通用装配器(不知道 FCC 内部)
│
│ const CompiledFcc& 传递
▼
simulation::FccTick ← 跨域 tick(每拍调用解释器)5.1 段 ①:fcc/pipelines/——层级无知工厂
每个工厂只知道自己"是什么算法 + 在什么速率",完全不知道自己将装在哪个 FccStage:
// fcc/pipelines/iter_guidance_pipeline.h
namespace fcc::pipelines {
// 输入 FccEnv,烘焙出一条 inner pipeline 闭包
// 工厂签名里没有 FccStage——它不关心
InnerPipeline make_iter_guidance_pipeline(const FccEnv& env);
}
// fcc/pipelines/iter_guidance_pipeline.cpp
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; // 姿态 PID 参数
constexpr auto rates = SchedulePolicy::IterGuidance; // 编译期常量:SINS@100 / Iter@10 / AttPID@50
return [&nav_cfg, &guidance_cfg, &control_cfg]
(const FccState& prev, const ImuMsg& imu, Time t, Time dt) -> FccState {
FccState s = prev;
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;
}
return s;
};
}其他工厂(同形态):
fcc/pipelines/ballistic_pipeline.{h,cpp}—— 仅 SINS + 姿态保持fcc/pipelines/recovery_pipeline.{h,cpp}—— 着陆段制导fcc/pipelines/standby_pipeline.{h,cpp}—— PreLaunch 占位
这与 plant/physics/Drag 不知道自己装在哪个 body 完全同构。算法只关心自己的输入输出。
5.2 段 ②:fcc/firmware/——MissionProfile + FccCompiler
这里才是"FccStage → 用哪个工厂"映射的合法归属。这是 firmware mission 知识:
// fcc/firmware/MissionProfile.h
namespace fcc::firmware {
// 工厂指针类型——decouple MissionProfile 与具体工厂实现
using PipelineFactory = InnerPipeline(*)(const FccEnv&);
struct MissionProfile {
// FccStage → 工厂指针;表本身可以从 mission YAML 加载
std::array<PipelineFactory, kFccStageCount> stage_to_factory;
};
// 默认 mission(C++ 内嵌示例;也可由 assets/missions/<name>.yaml 加载)
MissionProfile default_mission();
}
// fcc/firmware/MissionProfile.cpp
fcc::firmware::MissionProfile fcc::firmware::default_mission() {
using namespace fcc::pipelines;
MissionProfile p{};
p.stage_to_factory[(size_t)FccStage::PreLaunch] = make_standby_pipeline;
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::Boost2] = make_iter_guidance_pipeline;
p.stage_to_factory[(size_t)FccStage::TerminalDescent] = make_recovery_pipeline;
return p;
}// fcc/firmware/FccCompiler.h
namespace fcc::firmware {
struct CompiledFcc {
std::array<InnerPipeline, kFccStageCount> per_stage;
};
// 启动期一次调用;输入是 mission profile + heavy 环境
CompiledFcc compile_fcc(const MissionProfile& profile, const FccEnv& env);
}
// fcc/firmware/FccCompiler.cpp
fcc::firmware::CompiledFcc
fcc::firmware::compile_fcc(const MissionProfile& profile, const FccEnv& env) {
CompiledFcc out;
for (size_t i = 0; i < kFccStageCount; ++i) {
auto factory = profile.stage_to_factory[i];
out.per_stage[i] = factory ? factory(env) : make_standby_pipeline(env);
}
return out;
}特性:
- MissionProfile 是数据:可硬编码、可 YAML 加载;不是逻辑
- FccCompiler 是单一职责:调表 + 调工厂,毫无业务
- HIL 互换:换 mission profile 即可换映射,不动工厂、不动外壳
- layered 干净:
fcc/firmware/只依赖fcc/pipelines/+fcc/state/,不依赖 simulation/、不依赖 runtime/
5.3 段 ③:runtime/Assembler——通用装配触发
runtime::Assembler 在启动期调用一次 fcc::firmware::compile_fcc(...),把结果交给 simulation 层:
// runtime/Assembler.cpp(概念片段,详细见 Blueprint §3)
void Assembler::initialize() {
// ... 加载 environment / plant assets / fcc env ...
// 拿到 mission profile(来自硬编码 default_mission 或 YAML)
auto mission = fcc::firmware::default_mission();
// 触发编译——Assembler 不知道编译内部干啥
compiled_fcc_ = fcc::firmware::compile_fcc(mission, fcc_env_);
// 交给 simulation 层
fcc_tick_ = sim::pipeline::make_fcc_tick(compiled_fcc_, fcc_interpreter_);
}runtime/ 始终薄:它不知道 FccStage 是什么、不知道 InnerPipeline 是什么;只 wire 类型。
5.4 段 ④:simulation/pipeline/FccTick——每拍 tick
simulation/pipeline/FccTick 是唯一的跨域代码——它做 Bus↔FccFrame 帧转换,并驱动解释器:
// simulation/pipeline/FccTick.h
namespace sim::pipeline {
// 跨域:调用 bus、fcc/free_monad、fcc/firmware
struct FccTick {
const fcc::firmware::CompiledFcc& compiled;
fcc::FccInterpreter& interpreter;
bus::IBus& bus;
fcc::FccState& state_ref;
fcc::FccEnv& env_ref;
};
void run_one_tick(FccTick& ctx, Time t);
}
// simulation/pipeline/FccTick.cpp
void sim::pipeline::run_one_tick(FccTick& ctx, Time t) {
// 1. 跨域:Bus → FccInFrame
auto in_frame = bus::decode_to_fcc_in(ctx.bus, t);
// 2. 顶层策略:Free Monad 外壳(同一段代码跨所有 stage)
auto strategy = fcc::flight_control_loop(ctx.compiled); // 见 5.5
// 3. 解释执行
ctx.interpreter.run(strategy, ctx.env_ref, ctx.state_ref, in_frame, t);
// 4. 跨域:FccOutFrame → Bus
bus::encode_from_fcc_out(ctx.state_ref.pending_output, ctx.bus, t);
}simulation/ 是唯一知道 Bus 和 FccFrame 的层;FCC 内部代码看不见 Bus,simulation 内部代码看不见 FccStage 语义。
5.5 顶层 Strategy:外壳 + O(1) 派发
// fcc/free_monad/Strategies.h
// 跨所有 stage 共用同一段 monadic 代码——这就是 strategy 唯一化的关键
FccFree<std::monostate> flight_control_loop(const fcc::firmware::CompiledFcc& compiled) {
return
read_imu() >>= [&compiled](ImuMsg imu) {
return get_time() >>= [&compiled, imu](Time t) {
return get_state() >>= [&compiled, imu, t](FccState s) {
// O(1) 派发:array 索引,无 hash、无 map
const InnerPipeline& pipeline = compiled.per_stage[(size_t)s.current_stage];
Time dt = compute_dt(s.clock, t);
FccState next = pipeline(s, imu, t, dt);
// ↑↑↑ 该 stage 的所有 GNC 在此闭包内一次性算完 ↑↑↑
return write_telemetry(next.pending_telemetry)
>> update_state(next)
>> output_controls(next.pending_output);
}; }; };
}注意:
- 外壳不持有
FccEnv:Env 的引用被烘焙进闭包;外壳只通过compiled拿到 pipeline - GetEnv 算子退化为遗留:预编译之后外壳不再读 Env;蒸馏期可删除
- stage 切换零成本:
current_stage在pipeline(...)内被evolve_stage修改后,下拍外壳读到新值就自动派发新 pipeline;无需 strategy swap、无需 AST 重建
6. 为什么 GNC 算法不进 FccOp
| 把 RunNavigation 当算子的反模式 | 后果 |
|---|---|
解释器必须实现 operator()(RunNavigation) | 解释器膨胀;解释器从 HAL 变成"算法承载体" |
| 算法绑定到解释器 → 换 HAL 就要重写算法 | 违反"算法 100% 跨平台"承诺 |
| 单元测试 step_nav 必须拉起 Free Monad + interpreter | 测试地狱 |
| FccOp variant 列表无界增长(每加一个算法 +1 alternates) | C 蒸馏期 switch 表爆炸 |
正确做法(v1.0):
- DSL 只暴露与外界交互的算子(8 个,固定)
- GNC 算法是
(prev_state, inputs, env, dt) -> next_state的纯函数 - 用 DSL 把 IMU/State 取出,喂给纯函数,写回 State,发送 controls
7. Strategy 选择:默认 = 四段式 + Per-Stage 预编译
这是默认运行模式,不是优化选项。原因如 §5 开头所述:FccEnv heavy + 10ms 热点循环 = 动态调度禁区。
启动期一次性:
① fcc/pipelines/make_<algo>_pipeline(env) → InnerPipeline
└ 工厂层级无知:不知道自己将装在哪个 FccStage
↓
② fcc/firmware/compile_fcc(MissionProfile, env) → CompiledFcc
└ MissionProfile 持有 FccStage → factory 的表(firmware 知识)
└ 调表 + 调工厂,得到 per_stage[N] 闭包数组
↓
③ runtime::Assembler 调用一次 fcc::firmware::compile_fcc(...)
└ 把 const CompiledFcc& 交给 simulation
↓
④ simulation::pipeline::FccTick 持有 const CompiledFcc&
└ 每拍调用解释器,跨域处理 Bus↔Frame
运行期每拍(在 simulation::FccTick::run_one_tick 内部):
Bus → FccInFrame (simulation 跨域转换)
↓
解释器 run(flight_control_loop(compiled), env, state, frame, t)
↓
read_imu / get_time / get_state (Layer 1 I/O 算子)
↓
pipeline = compiled.per_stage[s.current_stage] (O(1) array 索引)
↓
pipeline(s, imu, t, dt) (一次 inline 调用,无 hash/无 map)
↓
内部按编译期固化的速率调用 step_nav / step_guidance / step_control
↓
内部检查 transitions[current_stage] → 应用 FccStageOp → 写回 current_stage
↓
update_state / output_controls
↓
FccOutFrame → Bus (simulation 跨域转换)7.1 各段的"知道什么 / 不知道什么"
| 段 | 知道 | 不知道 |
|---|---|---|
fcc/pipelines/ | 自己是哪套 GNC 算法、用什么速率 | 自己被装在哪个 FccStage、Bus、simulation |
fcc/firmware/ | FccStage → factory 映射;FCC mission profile | Bus、simulation、runtime |
runtime/Assembler | "启动期调一次 fcc::compile_fcc" | FCC 内部、FccStage 语义、Bus 协议 |
simulation/pipeline/FccTick | Bus 帧 ↔ FccFrame;持有 const CompiledFcc& | FccStage→factory 映射、GNC 算法、MissionProfile |
任何越界都是反模式(见 §9)。
7.2 stage 切换的代价
零额外代价。current_stage 被 evolve_stage 修改后,下拍 compiled.per_stage[new_stage] 自动取到新 pipeline。CompiledFcc 是 const 数组,所有 pipeline 同生命周期,无热加载、无 strategy swap、无 AST 重建。
7.3 与 dynamics 侧的对称
dynamics 侧采用同构四段式:
| 段 | FCC | Dynamics |
|---|---|---|
| ① 工厂 | fcc/pipelines/make_*_pipeline(intra-FCC,层级无知) | simulation/pipeline/factories/make_*_body_pipeline(跨域,已在 simulation/,详见 05_Dynamics_Core/Topology_Algebra.md §6.2) |
| ② 编译 | fcc/firmware/compile_fcc | simulation/pipeline/compile_dynamics(注 1) |
| ③ 触发 | runtime::Assembler 启动期调用 | runtime::Assembler 启动期调用 |
| ④ tick | simulation::FccTick | simulation::BodyTick + WorldTick |
| 预编译期产物 | CompiledFcc.per_stage[FccStage] | CompiledDynamics.per_stage[WorldStage] |
| 派发 | compiled.per_stage[current_stage] | compiled.per_stage[body.world_stage] |
| 触发重新派发 | FccStage 转换 | WorldStage 转换(分离 / 落级) |
> 注 1:dynamics 侧的"段 ②"放在 simulation/pipeline/,因为 BodyRWS pipeline 跨 plant/avionics/dynamics_core(Blueprint §2.6.4);FCC 侧 inner pipeline 是 intra-FCC,因此段 ② 放在 fcc/firmware/。这是 Blueprint 跨域判据的正确应用,二者不对称是合理的。
详见 Pipeline_Factory_and_Compilation.md 与 05_Dynamics_Core/Topology_Algebra.md §6。
7.4 与"DSL 仍是 I/O 包装"的关系
Layer 1 的 DSL 形态保持不变——仍然只暴露 8 个 I/O 算子,外壳代码看起来是函数式的 read_imu >>= ...。变化在闭包构造与 inner pipeline 调用:
- DSL 外壳:跨 stage 统一(同一段 monadic 代码,单一 strategy)
- Inner pipeline:每个 stage 独立预编译(不同闭包实例)
外壳的"纯描述性"与 inner 的"静态特化"互不冲突。这正是 Functional Core / Imperative Shell + Static Compilation 的合流形态。
8. FCC ↔ Bus 隔离
DSL 算子 OutputControls 写入的是 FccOutFrame,不是 Bus 消息。
output_controls(cmd: FccOutFrame)
│
▼
解释器写入 FccState.pending_output
│
▼
sim::body_tick 末尾:
BusManager::encode_from_fcc_out(pending_output) → [BusMessage]
bus->publish(...)Bus 装包责任在 simulation/,FCC 看不见也不依赖 bus::IBus。详见 Semantic_Bus_Pattern.md §9。
9. 反模式
| 反模式 | 为什么不行 |
|---|---|
加 RunNavigation / CalcControl 算子 | 违反 Layer 1/3 分层(§6) |
在 strategy 里直接 state.nav.q = compute(...) | 越过 DSL;破坏外壳的纯描述性 |
| Smart constructor 内部做计算 | constructor 必须只构造 liftF,不做业务 |
| 算子返回类型与 smart constructor 类型不一致 | std::any cast 会失败 |
在 strategy 里 try / catch | 错误应通过 std::expected 在纯函数层处理;DSL 不该有异常 |
output_controls 多次调用 | 一拍只发一帧 controls;多次写会覆盖前次 |
外壳每拍 tasks_for_stage(stage) + 字符串/map 查找 + 函数指针解引用 | FccEnv heavy,cache thrashing;用 CompiledFcc.per_stage[FccStage] O(1) 派发(§5/§7) |
让一个 compute_flight_logic 处理所有 stage 的 if-else 分发 | 失去编译期特化机会;编译器无法 inline;违反 §5 预编译原则 |
stage 切换时重建 FccFree AST | AST 应在初始化期构造一次;运行期只查表 |
把 FccStage→Pipeline 映射放在 simulation/ | 违反 Blueprint §2.6.4 跨域判据;这是 firmware 知识,应在 fcc/firmware/(§5.0) |
在 fcc/pipelines/make_*_pipeline 内引用 FccStage | 工厂应层级无知;如果它知道 FccStage,就失去复用性(HIL / ground test 用不了) |
runtime/Assembler 内写 FccStage→factory 映射 | runtime/ 应薄;映射是 firmware 知识,归 fcc/firmware/MissionProfile |
fcc/firmware/ 引用 Bus 或 simulation | firmware 是 intra-FCC;跨域转换是 simulation/pipeline/FccTick 的责任 |
simulation/pipeline/FccTick 内做 if (stage == Boost1) ... 分支 | simulation 不该知道 FccStage 语义;分支应通过 compiled.per_stage[] 自动选 |
10. C-Distillation 视角
C++ 阶段:
FccOp = std::variant<8 个 alternates>Free<F, A>用std::function+std::any持续传递- Smart constructor
inline,跨 TU 重用
C 蒸馏阶段:
FccOp退化为struct { uint8_t tag; union { ... } body; }Free树退化为 flatwhile (true)解释循环- Smart constructor 退化为 8 个
static inline FccOp make_*() - 返回类型由模板特化生成的多版本函数替换
std::any - 整套 DSL 在最热代码路径上等价于直接的 IO 调用,零开销
详见 Hardware_Decoupling.md §2 与 Static_Compilation_FSM.md。
11. Cross References
- 三层架构总览 → Blueprint §3.1
- 四段式职责分离详细图与禁忌 →
Pipeline_Factory_and_Compilation.md - 跨域判据(simulation/ 唯一定义)→ Blueprint §2.6.4
- 解释器实现 →
Interpreter_and_RWS.md - GNC 纯函数 step →
FCC_State_Machine.md§2.2 / §2.3 - Schedule + TaskCondition →
Data_Driven_Scheduling.md - Bus 装包责任 →
03_Avionics_and_Bus/Semantic_Bus_Pattern.md§9 - FCC 隔离约束 → Blueprint §3.3
- 当前代码 →
src/fcc/free_monad/{FccOp.h, FccDSL.h, FccInterpreter.h} - 架构目标位置 →
src/fcc/pipelines/、src/fcc/firmware/(待 Wave 1 落地)