# SO-101 公开录制：查询、裁剪与恢复

这个案例从一份公开机械臂录制中导出 `/joint_states` 的指定时间窗，再对主动截断的副本进行前缀恢复。所有 MCAP 处理由 MoonBit 实现；Python 驱动只负责下载、调用命令和生成展示报告。

## 运行

需要 Git、Python 3.12、Node.js 22.3+ 和 MoonBit（测试基准 moonc 0.10.17+d971670cd）。从仓库根目录执行：

```sh
moon update
python examples/so101/run.py
```

驱动只用 Python 标准库，无需安装参考读取器。首次运行从[固定公开清单](../validation/public-corpus.json)下载 2,592,733 字节原始录制，核对大小与 SHA-256 后才使用。数据缓存与生成文件保存在被 Git 忽略的 `_interop/` 内。终端打印本次输出目录，打开其中的 `report.html` 即可离线查看结果。

也可指定一个**尚不存在**的输出目录；父目录须已存在：

```sh
python examples/so101/run.py --output _interop/so101-result
```

已有目录会被拒绝，避免覆盖先前结果。下载失败、指纹不符、编译失败或结果不符均以非零状态退出；保留的 `build.log` 用于排查。修复问题后使用新的输出目录重跑。

## 预期结果与文件

| 操作 | 可核对的结果 |
| --- | --- |
| 完整校验 | 原录制 4,719 条消息；检查结构及有值的 CRC |
| 时间查询 | `/joint_states`，纳秒闭区间 `1791983428732915000`–`1791983428992984000`，14 条消息 |
| 索引对照 | 完整查询与 MIDX1 查询逐项一致；`source.midx` 绑定原始文件指纹 |
| 裁剪 | `clip.mcap` 使用 LZ4，包含上述 14 条消息，保留原始 payload、序号与时间；重新校验 |
| 截断副本 | 故意在 Chunk 内截断，完整文件校验必须失败；原件始终不变 |
| 前缀恢复 | `recovered.mcap` 使用 Zstd，包含连续有效前缀的 2,999 条消息；重新校验 |

`summary.json` 保存来源、参数和产物 SHA-256；`recovery.json` 保存停止偏移、原因和 `complete=false`；`report.html` 展示具体消息时间和本地文件链接。[固定展示预览](preview.html)展示相同预期结果，但不附带录制或可下载产物。它不能代替重新运行。

## 独立验证

安装参考读取器后，可以运行与展示驱动相同的用户路径，并独立逐条比较原件、裁剪结果与恢复结果的 payload、顺序和可用 CRC：

```sh
python -m pip install mcap==1.3.0 lz4==4.4.5 zstandard==0.25.0
python tools/so101_workflows.py
```

此检查还验证包含空格和中文的路径、拒绝覆盖现有目录、报告内的文件链接及固定预览一致性。不会改写仓库中的预览。

## 使用边界与来源

这是公开录制的离线文件流程，没有连接实体机械臂，不是设备控制或视觉分析演示。纳秒时间按 UInt64 处理，不能先转成 JavaScript Number。选择性查询不证明未读取区域完整，因此先执行完整校验。

截断故障由示例主动制作，不能描述成原录制的真实故障。恢复输出是有效前缀，不能推断全部消息已找回，也不估算丢失总量。零值 CRC 表示未提供校验值，不计作已验证校验和。

原始录制不随仓库、发布包或 GitHub Release 再分发。发布方链接、下载地址、来源说明与固定指纹见[公开清单](../validation/public-corpus.json)；项目的 MIT 许可证不改变数据来源许可。通用命令及更复杂输入的选择见 [CLI](../../docs/cli.md)与[入口选择](../../docs/entry-points.md)。
