Skip to content

WorldEnv Assembly & Probe Descent

> Status: NEW · 已对齐 PCR Master Blueprint v1.0 > 范畴: simulation/env/, simulation/probe/ > 依赖: environment/{Atmosphere, GravityField, WindField}, plant/model/*Spec, contracts/*Id, frames/ > 被依赖: simulation/pipeline/, runtime/Assembler


1. 问题陈述

仿真需要两类只读上下文:

  • WorldEnv(全局):装配后冻结,整个仿真生命周期共享——atmosphere、gravity、wind、所有 *Spec 表、frame config。
  • BodyEnv(局部):每个 tick 为每个 body 现场降维——TrajCtx + AeroCtx + MassPropsCtx + 资产指针。

这两类的设计核心都是一句话:"配方与流水线分离"

  • 算子(thrust、aero、gravity 计算)不直接读 WorldEnv 大表。
  • 算子只读 BodyEnv 的扁平字段(如 static_pressure: double)。
  • 换大气模型只动 probe_aero 函数,pipeline 一行不改。

本节描述:装配(一次性)+ 降维(每 tick)+ 不变量。


2. WorldEnv:全局只读资产库

2.1 类型布局

cpp
// simulation/env/WorldEnv.h
namespace sim {

struct WorldEnv {
    // ── 全局常量与坐标系(来自 frames/ + types/) ──
    frame::FrameConfig             frame;          // ECF / LIC 锚点、地球自转
    Constants                      constants;      // GM, J2, R_earth, etc.

    // ── 普适物理场(来自 environment/) ──
    env::Atmosphere                atmosphere;     // pressure_at(h), density_at(h), ...
    env::GravityField              gravity_field;  // g(pos_ecf)
    env::WindField                 wind_field;     // wind(pos_ecf, t)

    // ── 对象本构资产(来自 plant/model/,按 type 索引) ──
    std::vector<plant::model::BodyAsset>   body_assets;
    std::vector<plant::model::EngineSpec>  engine_specs;
    std::vector<plant::model::ServoSpec>   servo_specs;
    std::vector<plant::model::FinSpec>     fin_specs;
    std::vector<plant::model::ImuSpec>     imu_specs;
    std::vector<plant::model::GpsSpec>     gps_specs;
    std::vector<plant::model::IcuSpec>     icu_specs;
    std::vector<plant::model::AeroSpec>    aero_specs;

    // 查找接口
    const plant::model::BodyAsset& get_asset(contracts::BodyId id) const;
};

} // namespace sim

> WorldEnv 严格只读。它只持有 spec 数据(配方),不持有运行时实例。所有 device instance 属于 RocketBody(详见 RocketBody_Composite.md)。

2.2 强制属性

属性强制理由
装配后不可修改spec 指针稳定性约束(详见 §6);多线程读安全
不持有 RocketBody / WorldState单源;State 与 Env 严格分离(RWS Reader 不可变)
不持有 IBus / FCC instancebus 在 runtime/Runner;FCC 在 RocketBody.fcc
不持有 path_index 等装配期临时表path_index 在 Assembler 局部 RAII,装配结束析构(Blueprint §2.6.2 注)
跨域可包含 environment + plant 头文件Blueprint §2.6 判据:simulation/ 是唯一合法跨域聚合层

2.3 与旧 dynamics::WorldEnv 的区别

维度v0(runtime::WorldEnvv1(sim::WorldEnv
归属runtime/simulation/env/
持有 atmospherestd::function<double(double)> 句柄env::Atmosphere 强类型对象
持有 specstd::map<string, BodyAsset>std::vector<...Spec> + 按 type_id 索引
持有 bus是(含 busbus_cycle(bus 在 runtime)
持有 path_index(装配期局部)
LogConfig移走到 runtimesimulation/log/

详见 dev_doc/Refactoring/PCR_Master_Blueprint.md §7.16 迁移条目。


3. BodyEnv:单 body 局部 Reader

3.1 类型布局

cpp
// simulation/env/BodyEnv.h
namespace sim {

struct BodyEnv {
    // ── 当前 Body 的局部上下文切片(probe 产物) ──
    TrajCtx       traj;          // 经纬高、ECF 位置、速度方位角
    AeroCtx       aero;          // static_pressure, density, mach, dyn_pressure, wind_b
    MassPropsCtx  mass_props;    // 当前 mass / centroid / inertia tensor

    // ── 该 Body 引用的资产(非拥有指针,指向 WorldEnv) ──
    const plant::model::BodyAsset* asset;
    const plant::model::AeroSpec*  aero_spec;

    // ── 全局只读引用(指向 WorldEnv 字段) ──
    const env::Atmosphere*         atmosphere;
    const env::GravityField*       gravity_field;
    const env::WindField*          wind_field;

    // ── 上下文 ──
    Time                           current_time;
};

} // namespace sim

3.2 设计原则

原则解释
算子只读 BodyEnv 扁平字段compute_thrustenv.aero.static_pressureenv.atmosphere->pressure_at(h)
probe 是唯一的"模型耦合点"换大气模型只动 probe_aero;pipeline 不动
指针稳定所有指针指向 WorldEnv 字段,WorldEnv 生命周期 ≥ BodyEnv
栈对象BodyEnv 是 BodyTick 入口的栈对象,tick 结束自动析构
不持有可变状态BodyEnv 不含任何 mutable 字段;所有可变态在 RocketBody(BodyRWS 的 State)

4. probe:双层 RWS 之间的降维算子

4.1 函数签名

cpp
// simulation/probe/Probe.h
namespace sim::probe {

TrajCtx       probe_traj      (const WorldEnv&, const RocketBody&, Time t);
AeroCtx       probe_aero      (const WorldEnv&, const RocketBody&, Time t);
MassPropsCtx  probe_mass_props(const WorldEnv&, const RocketBody&, Time t);

// 一次性装配整个 BodyEnv
BodyEnv assemble_body_env(const WorldEnv&, const RocketBody&, Time t);

} // namespace sim::probe

这四个函数定义了双层 RWS 的"降维接口":World 层只读全局 → Body 层只读局部。

4.2 probe_traj:弹道上下文

cpp
TrajCtx probe_traj(const WorldEnv& we, const RocketBody& body, Time t) {
    Vec3_T<frame::LIC> pos_lic = body.spatial.pos_lic;
    Vec3_T<frame::ECF> pos_ecf = we.frame.lic_to_ecf(pos_lic, t);

    auto [lat, lon, h_msl] = we.frame.ecf_to_lla(pos_ecf);
    Vec3_T<frame::ECF> vel_ecf = we.frame.lic_to_ecf_vel(body.spatial.vel_lic, pos_lic, t);

    return TrajCtx {
        .pos_ecf  = pos_ecf,
        .vel_ecf  = vel_ecf,
        .lat      = lat,
        .lon      = lon,
        .h_msl    = h_msl,
        .speed    = vel_ecf.norm(),
        .azimuth  = compute_azimuth(vel_ecf, lat, lon),
    };
}

4.3 probe_aero:气动上下文

cpp
AeroCtx probe_aero(const WorldEnv& we, const RocketBody& body, Time t) {
    double h = body.spatial.h_msl();    // 经纬高
    double pressure = we.atmosphere.pressure_at(h);
    double density  = we.atmosphere.density_at(h);

    Vec3_T<frame::ECF> wind_ecf = we.wind_field.wind_at(body.spatial.pos_ecf, t);
    Vec3_T<frame::BODY> wind_b  = we.frame.ecf_to_body(wind_ecf, body.spatial.q_lic_body);

    Vec3_T<frame::BODY> vel_b   = we.frame.lic_to_body(body.spatial.vel_lic, body.spatial.q_lic_body);
    Vec3_T<frame::BODY> v_rel_b = vel_b - wind_b;
    double v_squared = v_rel_b.dot(v_rel_b);

    double speed_of_sound = std::sqrt(1.4 * pressure / density);  // 理想气体近似
    double mach           = v_rel_b.norm() / speed_of_sound;

    return AeroCtx {
        .static_pressure = pressure,
        .density         = density,
        .mach            = mach,
        .dyn_pressure    = 0.5 * density * v_squared,
        .v_rel_b         = v_rel_b,
        .wind_b          = wind_b,
    };
}

> 核心:这里集中了所有跨域查表逻辑。plant::physics::compute_thrust(BodyEnv, RocketBody) 只看 env.aero.static_pressure不知道 we.atmosphereenv::Atmosphere 的什么形态——指数模型、US Standard 1976、CFD 表格都一样。

4.4 probe_mass_props:质量上下文

cpp
MassPropsCtx probe_mass_props(const WorldEnv& we, const RocketBody& body, Time t) {
    double total_mass    = body.inertial.mass;     // 当前积分质量
    Vec3   centroid_b    = body.inertial.centroid;
    Matrix inertia_b     = body.inertial.inertia;

    return MassPropsCtx {
        .total_mass = total_mass,
        .centroid_b = centroid_b,
        .inertia_b  = inertia_b,
    };
}

> 这个 probe 看起来只是搬运,但它的存在是接口契约——未来若引入 plumes、燃料晃动、外挂物分离,MassPropsCtx 字段会扩展,pipeline 算子签名稳定。

4.5 assemble_body_env:一次性装配

cpp
BodyEnv probe::assemble_body_env(const WorldEnv& we, const RocketBody& body, Time t) {
    return BodyEnv {
        .traj          = probe_traj(we, body, t),
        .aero          = probe_aero(we, body, t),
        .mass_props    = probe_mass_props(we, body, t),
        .asset         = &we.get_asset(body.body_id),
        .aero_spec     = &we.aero_specs[body_type_to_aero_spec_idx(body)],
        .atmosphere    = &we.atmosphere,
        .gravity_field = &we.gravity_field,
        .wind_field    = &we.wind_field,
        .current_time  = t,
    };
}

在 BodyTick 入口调用一次,整个 BodyTick 后续阶段共享这个 BodyEnv 栈对象。


5. WorldEnv 装配流程

WorldEnv 由 runtime::Assembler::assemble() 在仿真启动时一次性装配。流程:

data/input/run_xxx.yaml
   ├── frame:        <FrameConfig>
   ├── environment:  atmosphere_model: "us_standard_1976"
   │                 gravity_model:    "j2"
   │                 wind_model:       "hwm14"
   ├── rocket:       <rocket_yaml_path>
   ├── mission:      <mission_yaml_path>
   └── deployment:   <PlantScope>


runtime::Assembler::assemble()

    ├── load_environment_(run_yaml)
    │     → we.frame, we.constants, we.atmosphere, we.gravity_field, we.wind_field

    ├── load_plant_assets_(rocket_yaml)
    │     │  对每个 device 类型:
    │     │
    │     ├── parse devices.yaml
    │     ├── for each spec_path in devices.yaml.engines:
    │     │     EngineSpec spec = load_engine_spec_yaml(spec_path);
    │     │     spec.tables     = load_csv_tables(spec.csv_paths);
    │     │     we.engine_specs.push_back(std::move(spec));
    │     │
    │     └── 类似处理 servo_specs / fin_specs / imu_specs / gps_specs / icu_specs / aero_specs

    └── (此处 we 已完成,进入 instantiate_world_state_,详见 RocketBody_Composite.md §7)

5.1 装配期局部状态

cpp
class Assembler {
private:
    std::unordered_map<std::string, uint32_t> path_index_;
    //              ^^^^ 路径字符串 → spec 数组下标,仅装配期使用
    //                              RAII:Assembler 析构时此表消失
};

> 旧设计把 path_index 塞进 WorldEnv 是错的——它是装配过程的临时索引,不是运行时数据。Blueprint §2.6.2 明确:装配结束 RAII 析构。

详见 07_Runtime/Assembler.md(待写)。


6. 指针稳定性约束

RocketBody.engines[i].specBodyEnv.atmosphere 等都是非拥有指针,必须保证:

约束实现
WorldEnv 装配后 vector 不 realloc装配阶段 reserve(N);装配结束后禁止 push_back
WorldEnv 生命周期 ≥ WorldState 生命周期runtime::SimulationInstance 同时持有,WorldEnv 先构造、后析构
WorldEnv 装配后 冻结(移动到 const&)Assembler::assemble() 返回 SimulationInstanceWorldEnv 仅以 const& 暴露
跨线程读 safe所有字段 const 后多线程共享

检查点

  • 编译期:WorldEnv 不暴露 mutable getter。
  • 装配期:Assembler::finalize() 把所有 vector 转 const(或调用 shrink_to_fit)。
  • 运行时:debug build 在 BodyTick 入口断言 body.engines[i].spec != nullptr

> 反例:在仿真运行中 we.engine_specs.push_back(...) —— 旧指针全部失效,是 use-after-realloc UB。Wave 0 必须删除任何运行时修改 WorldEnv 的代码路径。


7. probe 设计准则

7.1 probe 是纯函数

cpp
AeroCtx probe_aero(const WorldEnv&, const RocketBody&, Time t);
//      ^^^^^^^^^   const&            const&             value
//      所有输入只读,无副作用,无状态。

强约束

  • 不可在 probe 中修改 RocketBody 或 WorldEnv。
  • 不可在 probe 中分配堆内存(probe 在热循环 → cache 友好 + 无 GC pressure)。
  • 不可在 probe 中调用 IO(log/println/读文件)。
  • 可以读 const& 字段、查表、做数学。

7.2 probe 字段必须扁平

cpp
// ✗ 错:把 std::function 塞进 BodyEnv
struct AeroCtx_BAD {
    std::function<double(double)> pressure_func;   // 接口耦合
};

// ✓ 对:probe 在降维时**调用一次** atmosphere->pressure_at(h),得到 double
struct AeroCtx {
    double static_pressure;
};

理由:算子拿到的应该是数值(pressure: 100kPa),不是接口对象(pressure_func)。这是"配方与流水线分离"的本质。

7.3 字段扩展指南

引入新物理量时:

  1. 决定字段归属:弹道(TrajCtx)/ 气动(AeroCtx)/ 质量(MassPropsCtx)/ 还是新建 ctx?
  2. 在 ctx 中加字段,类型必须是扁平 POD
  3. probe_* 中填充计算。
  4. 算子按需读取。不需要的算子不感知新字段

> 不要 创建跨 ctx 字段(如"既属于 TrajCtx 又属于 AeroCtx")。如果一个字段被多个算子需要但语义跨域,把它放到最贴近物理意义的 ctx,或新建一个 ctx(如 RelativeFlowCtx)。

详见 02_Physical_World/Environment_Fields.md(atmosphere/gravity/wind 字段定义)。


8. 不变量与契约

契约强度检查点
WorldEnv 装配后只读必须API 上 const&;debug 断言 size 不变
BodyEnv 是栈对象,BodyTick 出口析构必须不允许 BodyEnv 跨 tick 持久化
probe 是纯函数必须函数签名 + code review
spec 指针非空必须BodyTick 入口断言
AeroCtx 字段全部扁平 POD必须type check
时间一致:probe(t) → BodyTick 内部所有计算都用同一个 t必须probe 把 t 写进 BodyEnv.current_time
同 tick 内 probe 只调用一次设计BodyTick 入口 once

9. 与三种部署模式的适配

部署WorldEnv 装配BodyEnv probe
avionics_dry(FCC 模飞)完整装配物理冻结,但 probe 正常跑(返回固定值)
sil_monolithic(默认 SIL)完整装配每 tick 实时 probe
hil_dyn(物理本机 + FCC 远程)完整装配同 SIL
hil_fcc(FCC RTOS + Devices 仿真)Plant 端装配 simulation Env;FCC 端不装配 WorldEnvFCC 端无 BodyEnv(FCC 只看 FccInFrame)

> 关键:HIL 部署时,FCC 在另一台机器上根本不知道 WorldEnv / BodyEnv 的存在。FCC 只看 FccInFrame(IMU/GPS payload)。这正是双层 RWS + simulation/ 跨域归属设计的目的:FCC 严格隔离

详见 04_FCC/Free_Monad_DSL.md §6(FCC 不依赖 simulation/)。


10. 反模式

反模式后果正确做法
std::function / 闭包塞进 WorldEnv.atmosphere不可序列化;调试困难;接口耦合用强类型 env::Atmosphere + 字段
装配后修改 WorldEnv(push_back spec)指针失效 UB装配后冻结
把 path_index 持久化进 WorldEnv运行时累赘;语义混淆装配期局部 RAII
在 BodyEnv 持有 mutable 字段Reader 不可变假设破产BodyEnv 全 const POD
算子直接调 we.atmosphere.pressure_at(h)pipeline 跨域;换模型要改算子算子读 env.aero.static_pressure,probe 负责调
probe 在 BodyTick 中段调用(而非入口)不同算子看到的环境时间不一致probe 在 BodyTick 入口 once
在 WorldEnv 持有 bus::IBus*越层;bus 是 runtime 设施bus 在 runtime::Runner
在 WorldEnv 持有运行时 RocketBody 实例Env / State 不分RocketBody 在 WorldState

11. C-Distillation 路径

C++ 抽象C 蜕化
sim::WorldEnv struct一个静态全局 const struct,编译期初始化
std::vector<EngineSpec>静态数组 + 编译期上限
env::Atmosphere(虚函数)C 函数指针表 + plain data
assemble_body_env 栈对象C 函数填充栈 struct
probe_aero等价 C 函数(已经基本是纯函数)
Constants map#define GM 3.986e14 等编译期常量

详见 09_Cross_Cutting/C_Distillation.md


12. 测试策略

12.1 单元层

  • probe_aero(known we, known body, t) → 验证字段数值(手算或参考实现对比)。
  • probe_traj 验证 lat/lon/h 与 ECF/LIC 双向转换。
  • 极端高度(h=0, h=100km, h<0)边界。

12.2 组件层

  • Assembler 装配测试:YAML → WorldEnv,验证 engine_specs.size()、字段值。
  • 指针稳定性:装配后查询 we.engine_specs[0] 地址,再装配第二轮,地址应为新对象。
  • assemble_body_env 字段填充完整性。

12.3 集成层

  • 整循环 SIL 1s,验证 RocketBody 演化 → 验证 probe 在不同高度返回正确大气值。

详见 08_Verification/Test_Strategy.md


13. 引用

  • Blueprint §2.6.2(WorldEnv 环境装配)、§2.6.3(Probe)、§4.3(probe_aero 与算子解耦实例)
  • Dual_Layer_RWS.md §4(probe 作为降维算子的位置)
  • RocketBody_Composite.md §4(spec 指针所有权契约)
  • Body_World_Tick.md §2(BodyTick 入口调用 probe)
  • 02_Physical_World/Environment_Fields.md(Atmosphere/GravityField/WindField 字段)
  • 02_Physical_World/Plant_Model_Assets.md(*Spec 来源与 YAML 格式)
  • 07_Runtime/Assembler.md(装配流程实现,待写)