ReplayReaderis an offline, simulator-independent API for observations, events, lifecycle records, camera media, and calibration.plugin://fastsim/replayis a managed FastSimcontrol_producerwhose defaultrendermode applies recorded visual state without control or physics; explicitresimulatemode submits portable authoredControlChunkrecords through a fresh control session.
The offline package does not import FastSim, UniRoboSim, Isaac Lab, MuJoCo, PyBullet, NumPy, or a rendering SDK.
Version 0.2.17 aligns the managed Replay extra with FastSim Core >=0.1.0a21,<0.2,
Record 0.3.10, and fastsim-recording-format[images]==0.3.3. It validates and reconstructs
bounded render.state trajectories during prepare and applies them on Core's paused
render-frame clock as fast as valid. It also validates and reconstructs
compact fluid.emissions batches into exact Core replay envelopes during prepare, then
requires fluid.replay and loads the complete schedule before Runtime starts. A present
empty fluid stream still loads (), disabling live emitters; an absent stream preserves
legacy behavior. It also bounds recorded-preemption
submissions, applies one absolute teardown deadline, retains failed cleanup handles for
retry, and rejects authored control spanning generation resets. The offline facade borrows the format package's typed reader instead
of maintaining a second decoder. Legacy rgb24-srgb, jpeg-rgb24-srgb, and
png-rgb24-srgb camera streams all decode transparently to the same immutable
RGBFrame value.
It reads FSR binary 1.0 strong inventories, binary 1.1 fast frame digests, and the
default binary 1.2 structural profile. Structural open validates the complete container
layout and metadata while seeking over payload bytes. ReplayReader.file_sha256 keeps
the same public return type but computes and caches the exact whole-file digest only when
explicitly accessed. A pinned expected_sha256 is an explicit digest request and triggers
that one lazy pass; an unpinned offline open does not.
Release 0.2.7 added preservation of the recorded terminal timeline. The offline
reader is verified against Record 0.3 camera streams, including RGB,
intrinsics, world translation, world quaternion, and their shared calibration digest. In
recorded_sim_time mode, Replay reads the formal terminal_semantic_cut from the
recorded run.lifecycle stream and preserves the complete source simulation timeline
after the final authored command. The final terminal policy therefore remains active
for the same recorded tail instead of ending as soon as the last Chunk reports
completion. Files created before terminal cuts were recorded retain the previous
behavior. The catalog keeps all manifests through 0.2.14 for existing locks, while
new compilation selects 0.2.17; all three supported binary minors remain under FSR v1.
Installation
Python 3.11 and 3.12 are supported.
python -m pip install \
"fastsim-recording-format[images]==0.3.3" \
"fastsim-plugin-replay==0.2.17"
This installs the SDK-free offline reader. For managed command Replay, install the
managed extra; it uses the reviewed compatible Core alpha range with the next minor
line as a review boundary:
python -m pip install "fastsim-plugin-replay[managed]==0.2.17"
For local source development:
python -m pip install ./packages/fastsim-recording-format
python -m pip install ./packages/fastsim-plugin-replay
The managed plugin is discovered through the fastsim.plugins and
fastsim.components entry-point groups. FastSim Core remains outside the base runtime
dependencies so an offline inspection environment stays SDK-free; the managed extra
adds the bounded Core dependency required for command Replay.
Offline ReplayReader
Opening a file validates its FSR header, canonical metadata, inventory projections, frame ranges, sequence and clock continuity, index, footer, truncation, and trailer before exposing any record. Digest checks depend on the file profile; structural files do not claim that payload bytes are tamper-evident.
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)
# Optional: this explicit request performs one lazy whole-file digest pass.
print(replay.file_sha256)
Seek is available by generation, simulation tick, or simulation time. Legacy raw
RGB24 plus Record 0.3.6 JPEG and PNG camera payloads are decoded only when their stream
definitions declare a known, self-describing codec. All three return the canonical
fastsim_recording_format.RGBFrame. Unknown codecs, inconsistent shape/dtype metadata,
non-finite numeric data, duplicate JSON keys, excessive JSON nesting, and oversized
values fail closed.
Path-based opening uses a non-following, nonblocking POSIX open, accepts only regular
files, pins the file descriptor, and optionally compares a compiled SHA-256 digest with
one lazily computed exact whole-file digest. FIFO, device, final-component
symlink, and post-resolution pathname-substitution inputs are rejected. Callers may
also construct ReplayReader from an already opened binary port and retain ownership
of that port. Managed preparation performs this work on one Replay-instance-owned
worker. Cancellation is checked before every bounded read and seek, then the worker is
joined and its pinned descriptor is closed before cancellation returns.
Managed render Replay
Managed Replay defaults to mode: render. It requires a source FSR containing the
optional Record render.state trajectory, starts Core paused, and loads frames through
the public render-state application boundary. Each load commits the source simulation
tick and sim_time_s, triggers normal observation and camera-media participants, and
does not open a control session, execute a controller, call the normal physics step, or
sleep for wall-clock pacing. Frames are applied sequentially as fast as the target can
settle them.
Run Replay and Record together to produce a new FSR:
plugins:
replay:
use: plugin://fastsim/replay
config:
mode: render
source:
uri: ./recordings/source.fsr
sha256: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
timing: {mode: as_fast_as_valid, speed: 1.0}
record:
use: plugin://fastsim/record
config:
output: {name: rendered.fsr}
streams:
observations: {mode: none, include: {}}
render_state: {enabled: true, rate_hz: 30}
cameras:
overview:
rgb: sensors.overview.camera.rgb
intrinsics: sensors.overview.camera.intrinsics
translation: sensors.overview.camera.extrinsics.translation
quaternion_xyzw: sensors.overview.camera.extrinsics.quaternion_xyzw
required: true
every_n_samples: 1
encoding: jpeg
quality: 92
See examples/render-replay/run.yaml for the
complete Run skeleton. render.max_frames, render.max_bytes, and
render.completion_timeout_s bound prepare-time allocation and execution. Record
authenticates Replay's prepared event and writes the source FSR SHA-256 and
mode: render into the new FSR footer. A legacy command-only FSR fails with an
actionable error; select mode: resimulate explicitly to use command Replay.
Managed command Replay (mode: resimulate)
The component identity is plugin://fastsim/replay. A normal plugin instance supplies
the recording and can select sequential or recorded-preemption execution:
plugins:
replay:
use: plugin://fastsim/replay
bindings:
robot: droid
access:
control:
robot: [arm, gripper]
config:
mode: resimulate
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
Compilation turns source.uri into the safe recording artifact selector. At
runtime, Replay performs this complete preflight before opening a control session:
- resolve and rehash
artifact://recording; - enforce the configured input byte limit;
- reopen a pinned regular-file descriptor and verify the compiled digest again;
- validate the complete FSR inventory and footer;
- decode every authored command, reject duplicate IDs, and require one source generation;
- compare the source and target simulation contracts; and
- validate every target resource, controller, command space, axis, unit, frame, interpolation mode, semantic requirement, and terminal policy.
Replay then opens a new scoped control session. Each command receives a fresh run ID, session ID, generation, world dependency, observation reference, target entity/group, and chunk ID. Original identity and digest fields remain in provenance, while accepted record order and relative simulation timing are retained.
execution.mode: sequential waits for the fresh terminal ChunkResult before
submitting the next chunk. recorded_preemption admits commands in source order and
retains their relative simulation-time starts, allowing a later command to preempt an
earlier one. Its rolling active window is bounded by execution.max_in_flight; a full
window waits for its oldest terminal result before admitting another command.
timing.mode: as_fast_as_valid removes source-time waiting while retaining admission order.
Cancellation, per-chunk cancellation tickets, heartbeat shutdown, and session close
share one absolute execution.chunk_timeout_s teardown deadline. A failed close or
completion request keeps its handle and success flag unset so an idempotent stop or
close retry can finish cleanup.
With timing.mode: recorded_sim_time, Replay also maps the source
run.lifecycle / terminal_semantic_cut timestamp onto the target clock. The scoped
control session and its heartbeat stay active until that mapped time, so hold, zero,
or controller-default terminal behavior continues through the recorded tail. This wait
is cancellable and is bounded by execution.chunk_timeout_s; if an operator pauses the
simulation, Replay waits without advancing the source clock and either resumes normally
or fails at that configured bound. An older valid FSR without the lifecycle cut skips
this step rather than being rejected.
Authored commands are not applied evidence
Command Replay accepts exactly this required stream contract:
| Field | Value |
|---|---|
| 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 records describe what a backend
actually received. They are evidence only and are never converted into Replay
commands. A recording containing applied frames without the authored stream is
rejected before control access.
Lifecycle behavior
Replay requests completion exactly once after all fresh terminal results are known:
succeeded / REPLAY_COMPLETEDafter all checks and results pass;failed / REPLAY_FAILEDafter compatibility, admission, execution, or result failure; orcancelled / REPLAY_CANCELLEDafter managed cancellation or Server replacement.
These are exact Core RunCompletionRequest values. The stable uppercase reason codes
are validated at the lifecycle boundary rather than translated by a local look-alike
type.
stop() and close() are idempotent. Active submitted chunks are cancelled and the
fresh control session is closed during teardown.
Verification status
Phase 03A includes package tests for offline seek and decoding, full integrity failure,
size limits, FIFO/device/symlink/path-substitution attacks, JSON bounds, applied-frame
rejection, target preflight, fresh authority rebinding, sequential execution,
recorded preemption, terminal-tail timing, paused-clock timeout, heartbeat continuity,
cancellation, exact Core completion requests,
exactly-once completion, Wheel contents, SDK-free isolated installation on Python 3.11
and 3.12, installed-Wheel managed discovery against the current Core Wheel, and
absolute-time preemption against the real Core ControlChunkExecutor.
The remaining integrated release gate is outside this package checkpoint:
- real DROID source recording plus Isaac Lab, MuJoCo, and PyBullet Replay/Record runs.
License and authorship
Author: Hofee lexhofee@gmail.com
The package currently uses LicenseRef-Pending until the repository license is
finalized.