ReplayReader:离线读取观测、事件、生命周期、相机媒体和标定信息,不依赖仿真器。plugin://fastsim/replay:FastSim 托管的control_producer,通过全新的控制会话重放 可移植的原始ControlChunk。
离线 API 不会导入 FastSim、UniRoboSim、Isaac Lab、MuJoCo、PyBullet、NumPy 或渲染 SDK。
0.2.17 将托管 Replay extra 对齐到 FastSim Core >=0.1.0a21,<0.2、Record 0.3.10 和
fastsim-recording-format[images]==0.3.3。它在 prepare 阶段严格校验 fluid.emissions,
将批次重建为 Core 精确 replay envelope,并要求 fluid.replay 在 Runtime 启动前装载完整
schedule。存在但为空的流也会装载 () 以关闭 live emitter;流缺失时保持旧行为。它还限制 recorded-preemption 活动 submission,
让 teardown 共享一个绝对 deadline,保留失败清理句柄以便重试,并拒绝跨 generation reset 的 authored control。
离线 API 直接借用 format 包的 typed reader,不再维护第二套 decoder。既有
rgb24-srgb 以及新的 jpeg-rgb24-srgb、png-rgb24-srgb 相机流都会透明解码为同一种
不可变 RGBFrame。它同时读取 FSR binary 1.0 strong inventory、binary 1.1 fast
frame digest 和默认 binary 1.2 structural profile。structural open 会完整验证容器布局和
metadata,但通过 seek 跳过 payload。ReplayReader.file_sha256 的公开返回类型保持不变,
只有显式访问时才计算并缓存精确整文件摘要。传入 expected_sha256 就是显式摘要请求,会
触发一次惰性扫描;未 pin 的离线 open 不会触发。
0.2.7 新增了录制终止
时间轴保留能力。离线 Reader 已针对
Record 0.3 的 RGB、内参、世界系平移、世界系四元数和共享标定摘要完成验证。在
recorded_sim_time 模式下,Replay 会从正式
run.lifecycle 流读取 terminal_semantic_cut,并保留最后一条 authored command 之后的完整
源仿真时间轴。因此最后的 terminal policy 会在录制尾段持续生效,而不是最后一个 Chunk 一完成
就结束。尚未记录 terminal cut 的旧文件继续采用原行为。组件目录保留截至 0.2.7 的全部
Manifest 供既有 Lock 使用;目录还保留 0.2.15,新编译默认选择 0.2.17,三种
binary minor 都属于 FSR v1。
安装
支持 Python 3.11 和 3.12。
python -m pip install \
"fastsim-recording-format[images]==0.3.3" \
"fastsim-plugin-replay==0.2.17"
上述命令只安装无 SDK 的离线 Reader。如需在 FastSim 中执行托管命令回放,安装
managed extra;它使用已经复审的 Core alpha 兼容范围,并把下一条 minor 版本线作为复审边界:
python -m pip install "fastsim-plugin-replay[managed]==0.2.17"
从源码安装:
python -m pip install ./packages/fastsim-recording-format
python -m pip install ./packages/fastsim-plugin-replay
托管插件通过 fastsim.plugins 和 fastsim.components entry point 被发现。FastSim Core
仍不属于 base 运行依赖,因此离线检查环境可保持无 SDK;managed extra 会添加托管命令回放
需要的有界 Core 依赖。
离线 ReplayReader
文件打开成功前,会验证 FSR 文件头、规范化 metadata、清单投影、帧范围、序号与时钟连续性、 索引、footer、截断和 trailer。摘要校验由文件 profile 决定;structural 文件不会宣称 payload 具备防篡改能力。任何记录都不会在结构验证完成前暴露。
from fastsim_plugin_replay import ReplayReader
with ReplayReader.open("run.fsr", max_bytes=64 * 1024**3) as replay:
print(replay.footer.primary_integrity)
sample = replay.seek(
"observations.robot.joints",
generation=1,
tick=120,
mode="at_or_after",
)
if sample is not None:
print(sample.metadata, sample.decoded)
for frame in replay.iter_media():
print(frame.ref.sim_time_s, frame.decoded)
# 可选:显式请求时才执行一次惰性的整文件摘要扫描。
print(replay.file_sha256)
读取器支持按 generation、仿真 tick 或仿真时间定位。旧版原始 RGB24 与 Record 0.3.6 的
JPEG/PNG 相机数据只有在流定义声明了已知、自描述 codec 时才会解码,三者均返回规范的
fastsim_recording_format.RGBFrame。未知 codec、不一致的 shape/dtype、非有限数值、重复
JSON 键、过深 JSON 和超限数据都会显式失败。
路径入口使用不跟随符号链接、非阻塞的 POSIX open,只接受普通文件,并固定文件描述符。 如果提供编译期 SHA-256,读取器会将其与惰性计算的精确整文件摘要直接比较。 FIFO、设备、末级符号链接以及 artifact resolve 后的路径替换都会被拒绝。高级调用方也可以传入 已经打开的二进制端口,并继续持有该端口的所有权。托管 prepare 使用 Replay 实例独占的加载 线程;每次有限长度的 read 和 seek 前都会检查取消令牌,取消返回前会回收线程并关闭固定的文件 描述符。
托管命令回放
组件 ID 为 plugin://fastsim/replay。插件实例可以选择顺序执行或按录制时序抢占:
plugins:
replay:
use: plugin://fastsim/replay
bindings:
robot: droid
access:
control:
robot: [arm, gripper]
config:
source:
uri: ./recordings/droid-source.fsr
sha256: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
timing:
mode: recorded_sim_time
speed: 1.0
execution:
mode: sequential
chunk_timeout_s: 120
max_in_flight: 32
input:
max_bytes: 1099511627776
配置编译会把 source.uri 转换成安全的 recording artifact selector。打开控制会话之前,
Replay 必须依次完成:
- resolve 并重新校验
artifact://recording; - 检查输入字节上限;
- 用固定的普通文件描述符再次校验编译期摘要;
- 验证完整 FSR 清单和 footer;
- 解码全部原始命令、拒绝重复 ID,并要求命令只属于一个 source generation;
- 比较源仿真与目标仿真的 simulation contract;
- 检查每个目标资源、controller、command space、axis、unit、frame、插值、语义要求和终止策略。
预检通过后,Replay 才会打开新的 scoped control session。每条命令都会换成新的 run ID、 session ID、generation、world dependency、observation reference、目标 entity/group 和 chunk ID; 原始 ID 与摘要保留在 provenance 中,录制时的接受顺序和相对仿真时序仍然保留。
execution.mode: sequential 会等待当前命令得到新的终态 ChunkResult 后再发送下一条命令。
recorded_preemption 按源顺序接受命令,并保留相对仿真开始时间,因此后续命令可以抢占前一条。
其滚动活动窗口受 execution.max_in_flight 限制;窗口满时会先等待最旧命令终态。
timing.mode: as_fast_as_valid 不等待源时间,但仍保留接受顺序。
取消请求、逐 chunk cancel ticket、心跳停止和 session close 共享同一个
execution.chunk_timeout_s 绝对 teardown deadline。close 或 completion 失败时不会提前置成功 flag,
对应句柄会保留,使幂等 stop/close 重试可以完成清理。
使用 timing.mode: recorded_sim_time 时,Replay 还会把源文件
run.lifecycle / terminal_semantic_cut 的时间映射到目标仿真时钟。scoped control session 和
心跳会保持到该时刻,因此 hold、zero 或 controller-default 会覆盖完整录制尾段。该等待可取消,
并受 execution.chunk_timeout_s 的墙钟上限约束;操作员暂停仿真时不会推进源时钟,恢复后可继续,
超过上限则明确失败。缺少 lifecycle cut 的旧版合法 FSR 会跳过尾段等待,不会被拒绝。
原始命令不等于实际施加记录
命令回放只接受以下必需流:
| 字段 | 固定值 |
|---|---|
| stream | control.authored |
| schema | fastsim-control/1 |
| media type | application/vnd.fastsim.control+json |
| payload codec | fastsim-control-json-v1 |
attributes.kind |
control.authored |
control.applied / fastsim-applied-control-frame/1 描述后端实际接收到的控制量,只能作为证据,
绝不会被转换成回放命令。只有 applied frame、没有 authored stream 的录制会在控制访问前被拒绝。
生命周期行为
Replay 只会在所有新的终态结果明确之后请求一次结束:
- 全部检查和结果通过:
succeeded / REPLAY_COMPLETED; - 兼容、接受、执行或结果失败:
failed / REPLAY_FAILED; - 托管取消或 Server 替换:
cancelled / REPLAY_CANCELLED。
以上均为精确的 Core RunCompletionRequest 值。稳定的大写原因码会直接在生命周期边界校验,
而不是由插件自定义的相似类型转换。
stop() 与 close() 均可重复调用。清理阶段会取消正在执行的 chunk,并关闭新建的控制会话。
验证状态
Phase 03A 已覆盖:离线 seek 与解码、完整性失败、大小限制、FIFO/设备/符号链接/路径替换攻击、
JSON 边界、applied frame 禁入、目标预检、新 authority 重绑定、顺序执行、录制时序抢占、终态结果、
终止尾段时序、暂停时钟超时、心跳连续性、取消、精确 Core 结束请求、只请求一次结束、
Python 3.11/3.12 Wheel 内容与无仿真 SDK 的隔离安装、
针对当前 Core Wheel 的已安装 Wheel 托管发现,以及真实 Core ControlChunkExecutor 的绝对时刻抢占。
剩余的集成发布门禁不属于本包当前阶段:
- DROID 在 Isaac Lab 中录制源数据,并分别在 Isaac Lab、MuJoCo、PyBullet 中 Replay + Record。
许可证与作者
作者:Hofee lexhofee@gmail.com
仓库许可证确定前,本包暂用 LicenseRef-Pending。