The writer appends canonical records to any seekable binary port. Finalization writes a canonical structural index, a completion footer, and a fixed-size trailer. The reader validates the container structure before exposing records and supports offline lookup by stream, generation, tick, or simulation time.

Distribution version 0.3.3 writes FSR binary 1.2 with structural-validation by default. The hot write path does not hash payloads. Opening a recording validates frame magic and lengths, exact offsets and bounds, canonical metadata, index projection, global and per-stream sequence continuity, generation and clock ordering, footer counts, and truncation while seeking over payload bytes. The index and footer remain compactly checksummed; stream inventory never contains a fake or placeholder payload digest.

Payload bytes are read only by read_payload, read_record, or typed decoding. FSRReader.file_sha256 keeps its public str result but computes and caches the full file digest only when that property is explicitly requested. For digest-bearing files, select integrity="fast" to emit binary 1.1 or integrity="strong" to emit binary 1.0. The reader remains compatible with both released layouts.

python
from io import BytesIO

from fastsim_recording_format import FSRReader, FSRWriter

port = BytesIO()
writer = FSRWriter(port)
writer.write_manifest({"run_id": "example"})
writer.define_schema("joint-state/1", {"type": "object"})
writer.define_stream("robot.joints", "joint-state/1", required=True)
writer.write_record(
    "robot.joints",
    record_sequence=1,
    source_sequence=1,
    generation=0,
    tick=0,
    sim_time_s=0.0,
    attributes={"dtype": "float32"},
    payload=b"binary sample",
)
writer.finalize(completion={"status": "succeeded"}, last_record_sequence=1)

reader = FSRReader(port)
record = reader.seek("robot.joints", generation=0, tick=0, mode="exact")
assert record is not None
assert reader.read_payload(record) == b"binary sample"
# Optional: requesting this property performs one lazy full-file SHA-256 pass.
print(reader.file_sha256)

Immutable embedded assets

Version 0.3.3 adds optional asset frames for non-temporal data such as a scene geometry index and chunked mesh buffers. write_asset is valid only before the first temporal record; asset IDs are unique, bounded, indexed, and available through FSRReader.assets and FSRReader.read_asset. Ordinary recordings that write no assets retain the previous frame layout. Assets do not consume record sequence numbers and therefore cannot disturb control, observation, event, or camera ordering.

Typed offline reads

FSRTypedReader adds simulator-free decoding for the portable JSON, raw RGB24, and little-endian float64 codecs emitted by the official recorder. With the optional images extra it also validates and decodes JPEG/PNG RGB payloads to the same RGBFrame type. It borrows a fully validated FSRReader: construction does not read the file again, create a second reader, or take ownership of the caller's binary port.

python
from fastsim_recording_format import FSRReader, FSRTypedReader

reader = FSRReader(port)  # structural validation does not scan record payloads
typed = FSRTypedReader.from_validated_reader(reader)

record = typed.seek("robot.joints", generation=0, tick=0, mode="exact")
assert record is not None and record.decoded == b"binary sample"

# A validated reference can be decoded directly, which is useful for bounded caches.
reference = reader.records("robot.joints")[0]
decoded = typed.read(reference)

The public typed surface also includes DecodedRecord, StreamKind, classify_stream, decode_payload, and FSRCodecError. JSON decoding rejects duplicate keys and non-finite numbers and enforces node/depth limits. RGB24 and float64 payloads must match their declared shape, dtype, layout, color space, and finite-value requirements. Unknown codecs fail closed; opaque raw payloads remain bytes unless the stream claims to be RGB or calibration data.

The package never extracts files, follows embedded paths, or inventories directories. Artifact selection, path policy, and output transactions belong to FastSim Core and the Record/Replay plugins.

Canonical metadata profile

FSR v1 fixes its metadata profile as fastsim-fsr-cjson/1. This profile is separate from FastSim Core's simulation-contract-digest canonicalizer. It uses UTF-8, no byte order mark or whitespace, mapping keys in Unicode code-point order, source array order, unescaped non-ASCII text, JSON's mandatory string escapes, decimal integer tokens, and finite floating-point tokens in Python 3.11/3.12 json shortest-round-trip form. Consequently the authoring distinction is retained for 1.0 and -0.0, and exponent padding such as 1e-07 is normative in FSR v1. Non-Python implementations must reproduce the package's published golden vectors byte for byte. A different numeric profile requires a new FSR format version; it cannot silently change v1 bytes.

Installation and verification

Python 3.11 and 3.12 are supported. The base package has no runtime dependency on FastSim, UniRoboSim, a simulator SDK, NumPy, or an image library. Install the image extra when decoding compressed RGB streams:

bash
python -m pip install "fastsim-recording-format[images]==0.3.3"

Without that extra, container validation and raw/JSON/numeric access remain available; attempting typed JPEG/PNG decode fails with an actionable FSRCodecError.

From this repository:

bash
python -m pytest packages/fastsim-recording-format/tests -q
python -m ruff check \
  packages/fastsim-recording-format/src \
  packages/fastsim-recording-format/tests
cd packages/fastsim-recording-format
python -m mypy --config-file pyproject.toml src/fastsim_recording_format
python -m build

License and authorship

Author: Hofee lexhofee@gmail.com

The package currently uses LicenseRef-Pending until the repository license is finalized.