定点数物理引擎:确定性数值、XPBD 与碰撞检测源码复盘
Fate 不是一个“把 float 全部替换成整数”的简单物理库。源码展示的是三层可组合系统:Fate Math 提供跨平台一致的定点数和软件浮点;Fate Physics 以 XPBD 组织粒子、刚体、约束、接触与场景查询;Fate Sync 提供帧数据驱动的锁步通信。确定性来自数值、时间、执行顺序、输入和版本共同受控,而不是只来自定点数。
本文依据本地 fate_math、fate_physics、fate_sync 源码及 Git 历史复盘设计与实现。项目整体能力和个人提交边界分开陈述,避免把团队架构全部包装成个人设计。
一、先给结论
这个引擎最值得讲的有五点:
- 它是混合确定性数值系统。 坐标、长度、速度、方向和角度采用不同 Q 格式;质量和惯量使用自行实现 IEEE-754 语义的软件
Float32,不是所有量都强行定点化。 - 它以物理量类型承载数值预算。
Position、Displacement、Direction、Compliance不是同一个标量的别名堆砌,而是分别选择位宽和小数位,防止在接口层误用。 - 动力学采用位置级约束投影。 一帧拆成若干子步,每个子步依次积分、投影约束、窄相碰撞、重建速度、处理接触位置、约束速度和接触速度。
- 溢出策略进入了算法设计。 宽位中间量、尺度缩减、checked/saturated 运算、速度上限、修正量上限和退化分支散布在几何与求解路径中。
- 个人贡献集中在碰撞检测的困难部分。 包括 Box-Capsule/Box-Cylinder SAT、Sphere/Capsule-HeightField、Segment-Triangle 最近点、定点精度与溢出修复,以及 Cluster 查询加速和 Shape 独立属性接口。
面试时最准确的一句话是:
我参与的是一套 Rust 实现的确定性物理引擎,个人工作重点在窄相碰撞、HeightField 几何、定点数退化与溢出处理,以及后续场景查询优化;Fate Math、XPBD World 和锁步框架是团队整体架构,我能从源码解释其设计,但不说成由我独立设计。
二、三层架构:确定性模拟不等于锁步网络
flowchart LR
A[玩家 Action] --> B[Fate Sync 帧排序与确认]
B --> C[固定 frame id / 输入序列]
C --> D[Fate Physics World::step]
D --> E[确定性世界状态]
E --> F[序列化快照 / 回放 / 状态接入]
M[Fate Math] --> D
M --> G[CollisionWorld]
G --> D| 层 | 源码职责 | 不负责什么 |
|---|---|---|
| Fate Math | Fixed32/64、软件 Float32、SIMD、向量、四元数、矩阵、确定性随机数 | 不决定物理时间步和对象更新顺序 |
| Fate Physics | World、CollisionWorld、实体、约束、接触、查询、睡眠、岛和序列化 | 不负责网络帧何时到达 |
| Fate Sync | Action 上报、Frame 组装、连续帧缓存、ACK、重传、重连与世界状态入口 | 不保证业务模拟本身确定 |
三个项目可以组合,但源码并没有把 Fate Sync 硬编码进 World::step。这点很重要:物理引擎提供“相同初始状态和输入得到相同结果”的计算基础;锁步层负责让各端获得相同顺序的输入。两者缺一不可,但不是同一个模块。
Fate Sync 的客户端按 frame_id 缓存乱序帧,只向上层交付严格递增的连续序列;Action 有独立 seq,客户端维护未确认 Action、ACK 列表和重传。重连时还可以请求全量帧数据或接收 world_state。这些机制解决输入一致性与恢复,不替代物理状态的数值确定性。
三、数值系统:不是一种 Q 格式打天下
3.1 物理量决定位宽和小数位
units.rs 对核心量做了明确预算:
| 物理量 | 实现 | 分辨率/范围含义 | 设计意图 |
|---|---|---|---|
Timestep | Fixed32<30> | 分辨率约 2^-30,正值范围小于 2 秒 | 时间步很小,优先精度 |
Length / Speed | Fixed32<20> | 分辨率约 9.54e-7,范围约 [-2048, 2048) | 局部距离和速度兼顾精度与范围 |
Coord / Position | Fixed64<20> | 与 Length 同分辨率,整数范围扩到约 2^43 | 世界坐标需要大范围,局部计算不必都用 64 位 |
SNorm | Fixed32<30> | 高精度单位方向/四元数分量 | 法线、方向和旋转误差会被反复放大 |
Angle | Fixed32<29> | 范围约 [-4, 4) 弧度,可容纳 pi | 单次旋转角追求精度 |
FullAngle | Fixed64<29> | 保留相同角度精度并扩大范围 | 中间计算避免窄位溢出 |
Compliance | Fixed32<30> | 高精度柔度参数 | XPBD 中小参数仍需可表达 |
Mass / InvMass / Inertia | 软件 Float32 | IEEE-754 风格动态范围 | 质量与惯量跨度大,纯 Q 格式不经济 |
这里有一个很成熟的分层:世界位置使用 64 位,两个位置相减后得到 32 位局部位移,碰撞公式尽量在局部坐标完成。这样既能容纳大世界,又避免所有窄相和 SIMD 路径都承担 64 位成本。
3.2 Fixed32<F> 把精度选择放进类型
Fixed32<F> 的底层是 i32 raw,值为 raw * 2^-F;Fixed64<F> 同理使用 i64。小数位是 const generic,因此不同 Q 格式在类型系统中可区分,乘除还可以显式指定结果小数位。
源码提供三类运算语义:
| 语义 | 行为 | 适用位置 |
|---|---|---|
| 普通运算 | 溢出或除零时 panic | 已由前置条件证明安全的内部路径 |
checked | 返回 Option | 几何退化、平行、零长度等合法失败分支 |
saturated | 钳制到 MIN/MAX | 速度、冲量、转换等必须继续运行的边界 |
乘除统一采用 round-half-up。字面量宏在编译期完成转换并检查溢出;文档明确把运行时 f32 与定点数互转标为 non-deterministic。这说明确定性边界不是口号,而是进入了 API 文档。
3.3 为什么还需要软件 Float32
Float32 以 u32 保存符号、指数和尾数,并自行实现分类、规格化、舍入、加减乘除等路径。例如除法先把尾数扩到 u64,执行整数除法后再通过统一 normalize 处理 normal、subnormal、infinity 和 rounding。
这不是直接调用硬件 f32。它保留浮点动态范围,同时把结果从 CPU、编译器和 FPU 模式差异中隔离出来。质量与惯量选择 Float32,可以避免为极轻与极重对象选一个很难兼顾的固定 Q 格式。
这也修正了“定点物理引擎所有数都是定点数”的说法。更准确的是:
Fate 用可审计的软件数值类型替代不受控的硬件浮点,并按物理量选择定点或软件浮点。
四、数据组织:句柄、SoA 与可序列化世界
World 由 Entity、Constraint、CollisionWorld、外力队列、接触缓存、岛和睡眠数据组成。外部通过 Entity、Constraint、Collider、Shape 等整数句柄引用对象,不直接持有内部数组地址。
粒子和刚体采用 Struct-of-Arrays:
ParticleVec
position[]
velocity[]
inv_mass[]
prev_position[]
RigidBodyVec
ParticleVec
orientation[]
angular_velocity[]
inv_inertia[]
prev_orientation[]这种设计有四个直接后果:
- 同一 kernel 连续读取同一字段,缓存和 SIMD 更友好;
Fixed32x4/Fixed64x4/Float32x4可以一次处理四个对象;- 句柄与内部索引解耦,排序、交换和合并 World 后仍可重映射;
- 主状态通过 Serde 序列化,临时 broad-phase/contact scratch buffer 用
serde(skip)排除,快照不携带可重建缓存。
README 将句柄 + SoA 明确关联到高效序列化和网络状态复制。这里的设计不是只为 CPU 性能,它还让状态边界更清楚。
五、World::step 的真实执行顺序
flowchart TD
A[固定 fps 与 substep] --> B[更新句柄到数组索引]
B --> C[更新 Collider Transform/Velocity]
C --> D[扩张 AABB + Broad Phase]
D --> E[可选构建 Island]
E --> F{每个 Substep}
F --> G[外力/力矩预积分 + 实体积分]
G --> H[位置约束投影]
H --> I[更新 Collider Transform]
I --> J[Narrow Phase + Contact 分类]
J --> K[由修正后 Pose 重建速度]
K --> L[接触位置修正 + 静摩擦]
L --> M[约束速度求解]
M --> N[接触速度求解: 动摩擦/恢复]
N --> F
F --> O[睡眠判定]
O --> P[清空本帧外力与力矩]5.1 时间不是浮点秒数
调用方传入整数 fps 和 substep。源码计算:
full_dt = 1 / fps
inv_dt = fps * substep
dt = 1 / inv_dt因此所有端必须使用相同 fps/substep,并在相同帧应用相同输入。现实时间抖动只影响“什么时候赶帧”,不能直接改变物理 dt。
5.2 为什么 broad phase 在子步外,narrow phase 在子步内
Broad phase 先更新碰撞体 Transform,并用速度扩张 AABB。源码额外乘以启发式安全系数 2,用于覆盖时间步内可能出现的加速度。候选对在整帧复用,降低子步成本。
但约束投影会在每个子步改变对象位置,所以每个子步都会重新更新 Collider Transform 并执行 narrow phase。若只在帧首做一次窄相,后续约束修正产生的新接触就会漏掉。
5.3 积分和速度重建
粒子积分先保存 prev_position,施加重力、阻尼与速度上限,再执行:
position = prev_position + velocity * dt刚体旋转没有直接使用容易溢出且需要重新归一化的 q += q * omega * dt / 2。源码先分解角速度方向与大小,再通过 axis-angle 指数形式更新四元数,减少归一化和溢出压力。
位置约束完成后,线速度由 (position - prev_position) * inv_dt 重建;角速度由 prev_orientation^-1 * orientation 的四元数差重建,并再次应用速度上限。这是位置级求解器把投影结果反馈给速度状态的关键步骤。
六、XPBD 求解器怎样落到代码
6.1 广义逆质量
对接触点方向 n,平动和转动共同决定对象有多容易被修正。源码先把 n 旋转到刚体局部,再计算:
w = inv_mass + inv_inertia dot (r cross n)^2实现没有直接用容易丢精度的窄位点积,而是让 r 使用 full_mul 产生更宽中间结果,再与软件 Float32 惯量相乘。
6.2 Compliance 进入时间步缩放
位置约束的核心代码等价于:
alpha = compliance * inv_dt^2
lambda = -C / (w1 + w2 + alpha)
delta = n * lambdaCompliance=0 对应刚性约束;非零时,柔度经过 inv_dt^2 缩放,减轻约束软硬程度对帧率的依赖。这是 XPBD 相对普通 PBD 的关键价值。
实现还设置 CORRECT_LENGTH_LIMIT=256,并在冲量、位移和 acting force 上使用 saturated 运算。它们是数值保险丝,但不是“物理上完全无副作用”:一旦触发钳制,结果虽仍确定,能量与真实修正量已经被截断,所以还需要诊断和合理输入范围。
6.3 接触位置与速度分开处理
接触首先按法线消除穿透。静摩擦通过当前接触点相对 prev_position/prev_orientation 的切向位移估算;若所需切向力小于 mu_static * normal_force,就接受位置级切向修正。
随后速度级求解处理两件事:
- 动摩擦按
F_t = mu_dynamic * F_n计算速度变化,并限制不超过当前切向速度; - 恢复系数只对超过重力相关阈值的法向速度生效,低速碰撞将 restitution 置零,避免静止堆叠持续抖动。
这说明接触并不是“一次 impulse 算完”。位置稳定、静摩擦、动摩擦和弹性恢复分别在不同阶段承担职责。
七、碰撞系统:可独立使用的 CollisionWorld
CollisionWorld 不依赖动力学求解器也能单独完成碰撞检测与场景查询。它包含 Collider、Material 和多种 Shape,并把流程拆为:
Shape/Collider 更新
↓
Broad Phase: Brute Force / Sweep-and-Prune / KD-tree
↓ 候选 Shape Pair
Narrow Phase: 专用 SAT / 最近点 / GJK / EPA / HeightField
↓
Contact: point + normal + separation + materialSweep-and-Prune 会按 AABB 中心近似离散度最大的轴选择排序轴,对 min 端点执行稳定排序;查询时用 partition_point 缩小候选范围。稳定排序不仅影响性能,也有助于固定候选遍历顺序。
窄相不是只依赖一种通用算法。球、盒、胶囊、圆柱、锥、凸包、三角网格、HeightField、Cluster 和 SDFVoxel 之间既有专用 SAT/解析路径,也有 GJK/EPA 路径。专用算法承担更稳定的接触点与更低成本,通用算法扩大形状覆盖面。
八、个人贡献一:Box-Capsule 与 Box-Cylinder
8.1 Box-Capsule 的问题不只是“有没有重叠”
提交 2479e2834 修复了 SAT 分离轴选择错误。完整候选轴至少包括:
- Box 三个面法线;
- Box 边方向与 Capsule 轴的叉积;
- Capsule 两个球帽到 Box 最近点形成的方向。
旧路径只区分 Face 和 Edge-Edge,长 Capsule 或球帽贴近 Box 角点时可能选错最小穿透轴。修复把 Edge-Edge 拆成圆柱侧面与球帽两类,并为球帽保留接触点来源。
接触生成也不是简单取中心点:面接触先投影到 Box 局部并 clamp;侧面接触求 Capsule 轴与 Box 平面的参数区间;区间中点使用 s_min/2 + s_max/2,避免直接相加溢出;落到球帽外时再沿球面恢复接触点。除法使用 checked 分支处理平行轴。
8.2 Box-Cylinder 为什么不能照抄 Capsule
Capsule 沿任意横向轴的投影半径恒为 r,Cylinder 的圆盘投影却取决于分离轴与圆柱轴夹角。Box-Cylinder 的 test_axis 根据夹角计算半径投影,并额外测试圆柱顶/底面轴。
这类实现最容易出现两个错误:
- 把 Capsule 的支撑半径直接套给 Cylinder,导致斜轴上过度或不足扩张;
- SAT 判定正确,但接触点落在圆盘之外或 Box 内部错误位置。
因此提交不仅补齐候选轴,还分别处理面接触和边边接触,并对 Box 局部接触点再次约束。
九、个人贡献二:HeightField 与 Segment-Triangle
9.1 Sphere-HeightField
Sphere-HeightField 先由 HeightField 的层次结构筛出候选三角形,再把三角形平移到球心局部空间,计算原点到三角形的最近点。若距离小于“球半径 + tolerance”,就生成法线、接触点和 separation。
源码使用 translate_checked,因为大世界 Position 转成局部 Displacement 时仍可能超出 32 位 Length 范围。候选三角形还携带索引,用于去重接触。这里体现了大坐标与局部碰撞计算之间的明确边界。
9.2 Capsule-HeightField 的核心是线段到三角形最近点
Capsule 可以化成“中心线段膨胀半径”。真正困难的不是半径,而是求线段与三角形在所有退化情况下的最近点:
- 线段退化为点;
- 线段延长线与三角形平面相交,且交点位于三角形内部;
- 交点在三角形外;
- 线段与三角形平行;
- 最近点落在线段端点、三角形内部或三条边之一;
- 两个最近点重合,无法由差向量归一化得到法线。
Segment::closest_parameter_to_triangle 的实现先把坐标平移到局部,再根据最大坐标值选择 reduction shift,把 Segment 和 Triangle 缩到 SNormVector3 的安全区间。点积和距离比较用 Fixed64 全精度平方值,最后才 narrow_sqrt_saturated 回到 Length。
非平行情形先尝试线与平面的交点;若交点不满足线段参数或三角形重心坐标,就退回统一候选集:线段对三条三角形边的最近点,以及线段端点对三角形的最近点。平行情形直接走同一候选集。
9.3 零距离为什么是独立算法分支
若线段与三角形最近距离非零,法线就是最近点差向量归一化。但距离恰为零时,这个向量没有方向,直接 normalize 会除零。提交 82c589a3f 将其改成显式分支:先取三角形 face normal,再根据 Capsule 轴、球帽/侧面相对位置构造可用的接触方向;叉积退化时又有备用路径。
这类问题说明定点几何不能依赖“浮点误差通常不会恰好为零”。定点数反而更经常精确命中零,因此退化拓扑必须是一等分支。
十、个人贡献三:场景查询加速与 API 完整性
提交 1d1d1bcfe 将 Cluster 的 Sphere/Box overlap 从遍历全部粒子改为:
查询形状 World AABB
↓
Cluster acceleration_structure.aabb_overlap
↓ 候选 particle index
精确 Sphere-Sphere 或 Sphere-OBB 测试这不是拿 AABB 结果直接当碰撞,而是用加速结构做保守候选过滤,再保留原精确判断和实体映射,因此优化不改变查询语义。对粒子数较大的 Cluster,复杂度从每次全量扫描转向与空间局部密度相关。
提交 4cd11ba19 则补充 Box 的 set_local_center、set_local_orientation、set_half_extent 独立接口,并通过内部 set_box_properties 复用句柄解码和错误检查。它解决的不是算法问题,而是避免调用方为了改一个字段必须读回并重写整组 Shape 状态。
十一、定点数下最关键的四种防护
| 风险 | 源码策略 | 仍需注意 |
|---|---|---|
| 中间乘积溢出 | full_mul/full_dot 扩到 Fixed64 | 最终窄化仍需 checked 或 saturated |
| 坐标过大 | World Position 用 Fixed64,窄相转局部 Displacement | 两对象距离超过 Length 范围时必须拒绝或提前裁剪 |
| 除零与退化 | div_checked、normalized_approx_checked、显式平行/零距离分支 | 不能把 None 一律解释成“无碰撞” |
| 状态失控 | 速度上限、单子步修正上限、saturation、sleeping | 钳制保证继续运行,不保证能量真实 |
尺度缩减尤其关键。若向量分量很大,直接算 x*x + y*y + z*z 即使最终距离很小也可能先溢出;如果分量很小,过度右移又会把方向量化为零。代码根据最大分量计算 shift,在“保留有效位”和“给乘加留头部空间”之间折中。
十二、确定性边界:定点数只是必要条件之一
要让两端长时间锁步,至少要同时固定:
- 初始 World、资源版本和所有配置;
fps、substep和每帧 Action 顺序;- 数值运算、舍入、溢出和三角函数实现;
- Entity、Constraint、候选碰撞对和 Contact 的遍历顺序;
- 随机数算法与种子;
- 并行任务的归并顺序;
- 序列化格式和版本迁移规则。
Fate Math 声明 SIMD 与非 SIMD、x86-64 与 aarch64 结果一致;Fate Physics 也提供可选并行 feature。但“单个算子一致”不自动证明“并行 World 全链路一致”,还必须验证任务划分与归并次序。因此面试中应说“源码为跨平台确定性提供数值基础,并用回归测试验证结果”,不要无证据扩大成“所有并行配置都已形式化证明一致”。
十三、性能不是定点数天然更快
定点数的首要目标是可重复,不是一定比硬件浮点快。源码中的性能来自另一组工程手段:
- SoA 连续数组与 x4 SIMD kernel;
- broad-phase scratch、ContactCollection 等缓冲复用,避免每帧分配;
- Sweep-and-Prune、KD-tree、HeightField 层次结构和 Cluster 空间加速;
- sleeping 跳过稳定对象,可选 island 分组;
- 专用 narrow phase 避免所有形状都走 GJK/EPA;
- benchmark 分别覆盖积分、narrow phase、SDF 和 scene query。
代价也很明确:宽位中间量、软件浮点、checked 分支、三角函数近似和跨平台 SIMD 一致性都会增加实现复杂度。性能结论必须来自具体 workload 的 benchmark,不能从“用了整数”直接推出。
十四、验证体系:如何证明改动没有悄悄改变结果
源码中能看到四层验证:
| 层 | 证据 | 能发现什么 |
|---|---|---|
| 数值单测 | Fixed32/64、Float32、向量、四元数的大量边界与随机输入测试 | 舍入、溢出、转换、归一化错误 |
| 几何单测 | Segment、Triangle、Collider、Constraint 测试 | 退化和局部公式错误 |
| JSON 回归 | collision/scenequery 及多个示例保存确定结果并逐 JSON 比较 | 算法改动造成的可见结果漂移 |
| World 回放 | World 可序列化,Replay 可从快照继续模拟 | 崩溃前状态复现和跨版本排查 |
示例框架的 regression 不是只看“有没有 crash”,而是重新模拟后把旧 JSON 与新 JSON 做精确相等比较。对于确定性引擎,这比只设浮点 epsilon 更能暴露执行路径变化;如果算法确实需要更新结果,则必须显式重新生成基线并审查差异。
十五、个人边界与源码证据
本地提交历史记录了两个阶段的作者身份:早期 zhengrjiang,后期 davidsjiang。可用于面试的代表性证据如下;提交数量不作为产出指标。
| 主题 | 代表提交 | 能证明什么 |
|---|---|---|
| Box-Capsule SAT 修复 | 2479e2834 | 能定位漏失分离轴,并区分侧面与球帽接触生成 |
| Box-Cylinder narrow phase | 53c48183e | 能从投影几何实现新形状对,而不是只调用现成 API |
| Fixed64 溢出语义 | 0d04840a、eb0238cf | 理解 checked/saturated 与 raw integer overflow |
| Sphere-HeightField | 02797a656 及后续系列 | 参与 HeightField 候选三角形与最近点接触实现 |
| Capsule-HeightField | 56ef52e36、82c589a3f 等 | 处理 Segment-Triangle、零距离、精度和溢出问题 |
| 几何原语抽取 | 1c32d0703 | 将重复最近点逻辑沉淀到 Segment/Triangle,并补测试与文档 |
| Cluster overlap 加速 | 1d1d1bcfe | 用已有加速结构过滤候选,同时保持精确查询语义 |
| Shape 独立 Setter | 4cd11ba19 | 补齐可维护 API,并复用统一句柄校验路径 |
| 示例渲染恢复 | dc48faaad | 处理 swap-chain texture Outdated 等运行环境故障 |
可以直接说:参与 Fate Physics 和 Fate Math,重点做过窄相碰撞、HeightField、基础几何、定点边界修复和场景查询优化。
不能直接说:独立设计整套 Fate Math、XPBD 求解器、所有形状碰撞矩阵或 Fate Sync;这些属于团队整体架构。若面试官追问整体设计,可以回答源码理解,再回到自己的模块和代表提交。
十六、面试回答模板
30 秒版本
我参与过一套 Rust 确定性物理引擎 Fate,个人重点在碰撞检测和定点数边界。它不是所有量用同一种定点数:世界坐标是
Fixed64<20>,局部长度和速度是Fixed32<20>,方向和四元数是高精度Fixed32<30>,质量与惯量用软件实现的Float32。动力学采用 XPBD,固定帧和子步内依次做积分、位置约束、窄相、速度重建和接触求解。我具体做过 Box-Capsule/Box-Cylinder、Sphere/Capsule-HeightField、Segment-Triangle 最近点和 Cluster 查询加速,最难的是在定点数下同时处理溢出、精度和几何退化。
90 秒版本
我会把它分成数值、模拟和同步三层。Fate Math 用 const generic Q 格式和软件 Float32 保证运算语义可控,还提供 checked、saturated、宽位中间量和跨平台 SIMD;Fate Physics 用句柄加 SoA 保存可序列化 World,XPBD 每帧按固定 substep 执行积分、约束投影、碰撞和速度求解;Fate Sync 只负责把 Action 组织成连续 Frame、ACK 和重传,它不替代物理确定性。我的贡献主要在窄相碰撞。比如 Capsule-HeightField 不能只算一条线到三角形距离:还要处理平行、端点、三角形边、退化线段和距离为零时没有法线的问题。实现先把局部坐标按最大分量缩放到安全 Q 区间,用 Fixed64 保存平方距离,再按拓扑分支生成接触。后续我还把 Cluster overlap 从全量粒子扫描改为 AABB 加速结构候选过滤,再保留精确测试。这个项目让我真正理解,确定性不是“用了整数”,而是数值、时间步、遍历顺序、输入和版本共同形成协议。
高频追问:为什么不用普通浮点
锁步会把极小数值差异经过分支、碰撞和积分持续放大。普通硬件浮点还受指令集、融合运算、编译选项和 FPU 模式影响。Fate 选择自己定义舍入与溢出语义,并对需要动态范围的质量和惯量使用软件 Float32。代价是实现和性能优化更复杂,所以它适合强确定性需求,不是所有游戏物理的默认答案。
高频追问:定点数为什么仍会出 Bug
定点数消除了部分平台差异,但增加了范围、量化和退化问题。两个大坐标相乘可能先溢出,极小法线可能量化为零,平行判断和最近点拓扑也会改变。我做 HeightField 和 Segment-Triangle 时,核心工作就是把计算移到局部坐标、按尺度缩减、用宽位平方距离,并把零距离和平行情况变成显式分支。
十七、源码索引
| 结论 | 主要源码 |
|---|---|
| 数值类型与运算语义 | fate_math/src/fixed32.rs、fixed64.rs、float32.rs、ops/ |
| 物理量精度预算 | fate_physics/src/units.rs |
| World 状态与 step 顺序 | fate_physics/src/world.rs |
| 粒子/刚体积分与速度重建 | src/entity/particle/integrator.rs、particle.rs、rigidbody/integrator.rs、rigidbody.rs |
| XPBD 约束投影 | src/constraint/utility.rs 与各 Constraint kernel |
| 接触、摩擦与恢复 | src/contact/*.rs |
| broad/narrow phase | src/collision/broadphase/、src/collision/narrowphase/ |
| Segment/Triangle 与 HeightField | src/collision/segment.rs、triangle.rs、shape/heightfield/ |
| 回归与回放 | examples/framework/src/lib.rs、examples/collision/src/test.rs、examples/replay/ |
| 锁步帧与恢复 | fate_sync_lockstep/src/client_api/lockstep_client.rs 与 fate_sync_wpl/src/sync/ |
相关材料:工程效能工具、源码仓库与个人贡献地图、简历主张与指标台账、最终面试包。