Expand description
Frame-document schema: the self-describing serialized form of an astrodyn reference-frame tree.
This crate carries only the document types and their (de)serialization
— no physics. A read-only consumer (renderer, logger, analyzer) links this
crate (plus the identity vocabulary in astrodyn_quantities) and can
interpret a recorded run without the producer’s code (spec RFS-602,
Spec-Reference-Frame-Requirements).
§Document model
Two forms share one record vocabulary:
FrameDocument— a snapshot: header + internedFrameUidtable + oneFrameRecordper frame node.FrameSeries— replay v1: header + uid table + a sequence ofFrameSegments, each holding per-epoch record rows. A segment spans a constant topology; any topology change (a frame-switch reparent, an attach) closes the segment, so segment boundaries double as seek keyframes. Streaming topology events are replay v2 (issue #663 scope).
Every state record names the parent uid it is relative to — snapshot and series alike. Consumers check their folded topology against each record’s declared parent, so a missed or misordered topology change is a loud inconsistency, never silent misinterpretation.
Per record, state carries its origin (RFS-603): Integrated
(authoritative body store — the tree node is a per-step projection),
Derived (re-derivable from a model id + the record epoch), or
Injected (caller-supplied ground truth). Rotation is serialized as
CanonicalRotation — whichever representation was canonical at the
write site (the typed path is quaternion-canonical per JEOD_INV RF.04;
rotation-model writers like sync_pfix_rotation are matrix-canonical) —
and the other representation is re-derived on load, which is what makes
serialize → reload → continue bit-identical for both regimes (RFS-601).
§Encoding
Plain-decimal JSON. Shortest-round-trip float printing makes f64
round-trips bit-exact (proven by this crate’s full-entropy property test
and the cartesian_state_json_round_trips_bit_exact precedent in
astrodyn_quantities). Non-finite values are rejected loudly at
serialize time — a NaN in a frame document is upstream broken physics,
not data. A binary form is a later, additive decision.
The header carries the schema version and the numeric conventions
in-band (Conventions); FrameDocument::validate checks them
before any state is interpreted, so a consumer that never links astrodyn
cannot silently misread a changed convention.
§Per-record / streaming consumers
The record types — DocHeader, FrameRecord, EpochRow,
FrameUid and their components — are a supported, stable surface
for independent serialization, not internals behind the whole-document
JSON API. to_json_string / from_json_str are conveniences over the
same serde derives; a live feed may serialize records individually
(e.g. a binary serde format over a socket) under these rules:
- Handshake = header + uid table. Records reference identities
positionally (
FrameRecord::uid_index/parentindex into the internedVec<FrameUid>), so transmitDocHeaderand the uid table once, then per-epochEpochRows. Validate the handshake before interpreting any number viavalidate_headerandvalidate_uid_table, and each arriving row viavalidate_record— the loose-piece equivalents of the whole-documentvalidate()entry points. - Topology changes are segment boundaries. A reparent or attach
changes what
parentmeans; mirror replay v1’s rule on a live feed by re-sending header + uid table as a fresh keyframe. Per-recordparentkeeps every row self-checking: verify it against your folded topology and treat a mismatch as a loud inconsistency, never a reinterpretation. (Cross-record invariants — cycle-freedom, row completeness, constant topology within a segment — are the containervalidate()s’ job and become the consumer’s job on a stream.) - Format notes. Positional formats (postcard/bincode) bind to
field order, self-describing formats to field names; pin this
crate’s version on both ends and gate on
DocHeader::schema_versionat handshake. Binaryf64encodings are inherently bit-exact — the shortest-round-trip /float_roundtriprequirement above is JSON-specific. Non-finite values remain invalid in any encoding. - Stability. The wire schema evolves only through
SCHEMA_VERSION; these Rust types are the schema, so field changes imply a version bump and a semver-visible crate change. Evolution is additive where possible (enums such asOrigin/CanonicalRotationmay gain variants behind a version bump); existing fields are not silently re-shaped.
use astrodyn_frame_doc::{
validate_header, validate_record, validate_uid_table, CanonicalRotation, Conventions,
DocHeader, EpochRow, FrameRecord, FrameUid, Origin, TransRecord, SCHEMA_VERSION,
};
use astrodyn_quantities::frame::RootInertial;
// ── Producer side: handshake, then rows ──
let header = DocHeader {
schema_version: SCHEMA_VERSION,
conventions: Conventions::current(),
simtime: 0.0,
tai_tjt_at_epoch: 11544.499257592593,
};
let uids = vec![FrameUid::of::<RootInertial>()];
let row = EpochRow {
simtime: 0.0,
records: vec![FrameRecord {
name: "root".into(),
uid_index: 0,
parent: None,
epoch: Some(0.0),
trans: TransRecord {
position: [0.0; 3],
velocity: [0.0; 3],
},
rotation: CanonicalRotation::Quat([1.0, 0.0, 0.0, 0.0]),
ang_vel_this: [0.0; 3],
origin: Origin::Injected,
}],
};
// ── Consumer side: validate the handshake before any number, then
// each arriving row against the handshake's uid table. ──
validate_header(&header).expect("handshake header");
validate_uid_table(&uids).expect("handshake uid table");
for (pos, rec) in row.records.iter().enumerate() {
validate_record(rec, pos, uids.len()).expect("arriving row");
// ...then check rec.parent against your folded topology.
}Structs§
- Conventions
- Numeric conventions carried in-band and validated before any state
is interpreted (
FrameDocument::validate). Self-describing strings rather than flags so a code-free reader (RFS-602) sees the convention, not a boolean it must look up. - DocHeader
- Document header: schema version, conventions, and the producing simulation’s time anchor.
- Epoch
Row - All frame records at one simulation instant.
- Frame
Document - A frame-tree snapshot: header + interned uid table + one record per
node. Records appear in producer node order;
parentreferences are by uid-table index, never by arena index (RFS-601: identity is stable across the wire, storage indices are not). - Frame
Record - One frame node’s serialized state.
- Frame
Segment - A maximal run of epochs sharing one topology. Any topology change (a frame-switch reparent, an attach) closes the segment — boundaries double as seek keyframes for replay.
- Frame
Series - A recorded run: header + interned uid table + topology-stable segments (replay v1; streaming topology events are v2).
- Frame
Uid - Owned, comparable, hashable runtime frame identity.
- Namespace
- Identity-space discriminator for
FrameUids. - Series
Builder - Writer-side recorder: push one epoch’s rows per step; the builder auto-closes the open segment whenever the declared topology (each record’s parent uid) changes, so producers never hand-manage segment boundaries and an intra-segment topology change is unrepresentable.
- Trans
Record - Translational state relative to the parent frame (JEOD_INV RF.06).
Enums§
- Canonical
Rotation - Rotation state in whichever representation was canonical at the write site (RFS-601: serialize the canonical field; re-derive the cached form on load).
- DocError
- Validation / load errors for documents and series.
- Frame
Class - Coarse runtime taxonomy of frame kinds.
- Frame
Role - Orthogonal role within a
FrameClass— owned form carried byFrameUid. - Origin
- Where a record’s state comes from (RFS-603) — the three production write regimes, distinguishable by consumers.
- Tag
- Optional per-instance disambiguator: the planet/vehicle name for parameterized frames, or a producer-chosen key for external frames.
Constants§
- SCHEMA_
VERSION - Version of the wire schema this build writes and accepts.
Functions§
- validate_
header - Validate a
DocHeaderon its own: schema version, the in-band numericConventionsagainst this build’s, and finiteness of the time anchors — before any state number is interpreted. - validate_
record - Validate a single
FrameRecordagainst a uid table ofuid_table_lenentries: index bounds (uid_index,parent), self-parenting, and finiteness of every state field.record_posonly labels the error. - validate_
uid_ table - Validate an interned uid table on its own: every identity must be distinct — two table entries holding the same identity would let records alias one frame through “distinct” indices.