Skip to main content

Crate astrodyn_frame_doc

Crate astrodyn_frame_doc 

Source
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 + interned FrameUid table + one FrameRecord per frame node.
  • FrameSeriesreplay v1: header + uid table + a sequence of FrameSegments, 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 / parent index into the interned Vec<FrameUid>), so transmit DocHeader and the uid table once, then per-epoch EpochRows. Validate the handshake before interpreting any number via validate_header and validate_uid_table, and each arriving row via validate_record — the loose-piece equivalents of the whole-document validate() entry points.
  • Topology changes are segment boundaries. A reparent or attach changes what parent means; mirror replay v1’s rule on a live feed by re-sending header + uid table as a fresh keyframe. Per-record parent keeps 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 container validate()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_version at handshake. Binary f64 encodings are inherently bit-exact — the shortest-round-trip / float_roundtrip requirement 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 as Origin / CanonicalRotation may 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.
EpochRow
All frame records at one simulation instant.
FrameDocument
A frame-tree snapshot: header + interned uid table + one record per node. Records appear in producer node order; parent references are by uid-table index, never by arena index (RFS-601: identity is stable across the wire, storage indices are not).
FrameRecord
One frame node’s serialized state.
FrameSegment
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.
FrameSeries
A recorded run: header + interned uid table + topology-stable segments (replay v1; streaming topology events are v2).
FrameUid
Owned, comparable, hashable runtime frame identity.
Namespace
Identity-space discriminator for FrameUids.
SeriesBuilder
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.
TransRecord
Translational state relative to the parent frame (JEOD_INV RF.06).

Enums§

CanonicalRotation
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.
FrameClass
Coarse runtime taxonomy of frame kinds.
FrameRole
Orthogonal role within a FrameClass — owned form carried by FrameUid.
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 DocHeader on its own: schema version, the in-band numeric Conventions against this build’s, and finiteness of the time anchors — before any state number is interpreted.
validate_record
Validate a single FrameRecord against a uid table of uid_table_len entries: index bounds (uid_index, parent), self-parenting, and finiteness of every state field. record_pos only 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.