Skip to content

Spec Reference Frame Requirements

Test User edited this page Jun 5, 2026 · 9 revisions

Reference Frame System — High-Level Software Requirements

Status: Draft — under initial review. 2026-06-05, grounded against the codebase at 0f78ef9. While in Draft, the document is edited freely; the ID-stability and issue-first change conventions on Specification bind once it is accepted.

Provenance: distilled from issue #659 (runtime-layer requirements), the June 2026 multi-agent design exploration that followed it (three independent designs, adversarial judging, and source-verified premise checks against both astrodyn and JEOD v5.4 C++), the JEOD invariant catalog, and the project's recorded incident history.

This page enumerates the fundamental needs of astrodyn's reference frame system — the drivers and their rationale. It deliberately stops short of prescribing implementation or design: no data structures, encodings, traits, or APIs. Designs will continue to change while astrodyn is young; these requirements are the yardstick each design iteration is measured against.

"The frame system" here means the whole capability, not one layer: the compile-time typed facade, the runtime frame tree, and any exchange/persistence surface are one system with one set of needs.


0. Definitions

  • Frame — a coordinate basis with an origin and orientation, possibly rotating, possibly moving: root inertial, planet-centered inertial, planet-fixed, topocentric, vehicle body/structure, orbit-relative (LVLH/NED), or producer-defined.
  • Frame state — the kinematic relationship of a frame to its parent: relative position, velocity, orientation, and angular velocity.
  • Relative state — the composed kinematic relationship between any two frames, derived from frame states along the connecting path.
  • Statically-defined frame — a frame the workspace knows at build time (today: the sealed compile-time frame set).
  • Runtime-defined frame — a frame introduced by a producer at runtime, unknown to the workspace at build time.
  • Producer / consumer — code that defines and updates frames / code that resolves and reads them. They may be the same process, or separated by a process boundary, persistence, or time (replay).
  • Host — the program driving the simulation (the Bevy adapter, the arena runner, or third-party code).
  • Integrated state — frame state produced by integration (vehicle body frames); authoritative, must be persisted to be recovered.
  • Derived state — frame state produced by evaluating a deterministic model at an epoch (ephemeris positions, planet-rotation models); re-derivable from model identity + epoch.
  • Injected state — frame state supplied directly by a caller or external source (retargeted source positions, fixed orientations); not integrated and not re-derivable.

1xx — Frame algebra and kinematics

RFS-101 Relative state between any two frames

The system shall compute the full relative state (relative position, velocity, orientation, and angular velocity) between any two frames that share a common root.

Rationale: This is the foundational query of orbital dynamics. Every consumer — gravity, integration, frame switching, sensors, visualization — reduces to it. JEOD's entire RefFrame model is organized around this computation. Verification: Unit tests against direct composition; Tier 3 cross-validation against JEOD reference data. Trace: JEOD ref_frame_compute_relative_state.cc; JEOD_INV RF.01, RF.02.

RFS-102 Full kinematics as first-class data

Frame state shall carry translation, rotation, and both their rates (velocity, angular velocity) as first-class data with documented meaning, such that a consumer can interpret a rotating frame without recomputing the model that produced it.

Rationale: Rotating frames (planet-fixed, body) are first-class in this domain, and velocity composition requires the angular-rate terms (ω×r). Conventions for these terms have already produced real bugs when guessed (planet-fixed angular velocity is expressed in planet-fixed coordinates, not rotated inertial — a 0.13 m/s ground-point velocity error was caught in Tier 3 Bucket B work). Verification: Composition tests with non-zero rates and non-trivial rotations; convention documentation review. Trace: #659 R4; JEOD_INV RF.06, RF.07; Common-Pitfalls.

RFS-103 Unique composition path

Every non-root frame shall have exactly one parent, and relative state shall be computed along the unique path through the common ancestor.

Rationale: A multi-path (graph) topology makes results path-dependent at the floating-point level — two routes between the same frames differ by ULPs. Determinism, replay, and cross-host parity all require one answer. Verification: Structural (no operation can introduce a second parent); cycle-rejection negative tests. Trace: JEOD tree model; incident #562 (result sensitivity to walk shape).

RFS-104 Composition determinism through degenerate hops

Composing through identity (degenerate) intermediate frames shall not perturb results at the f64 bit level: equivalent tree shapes yield bit-identical relative states.

Rationale: Incident #562 — ~30 ULP body-position drift between a 1-hop and a 2-hop tree shape for the same physics. Hosts legitimately differ in tree shape; parity between them must not depend on hop count. Verification: Bit-comparison tests across equivalent tree shapes. Trace: Issue #562; JEOD_INV RF.13.

RFS-105 State-preserving restructure

Moving a frame to a new parent (integration-frame switch, attach/detach) shall preserve the frame's absolute state.

Rationale: A frame switch is a relabeling of bookkeeping, not a physical event. JEOD models this explicitly (RefFrame::transplant_node, dyn_body_frame_switch.cc). Verification: Reparent tests comparing absolute state before/after. Trace: JEOD transplant/frame-switch behavior; Tier 3 frame-switch RUNs.


2xx — Conventions, canonical representations, time

RFS-201 One pinned convention set

The system shall define exactly one convention for interpreting frame state — which entity is relative to which, expressed in whose coordinates, with which orientation convention and direction — consistent with JEOD's, and shall document it at every surface where state crosses a boundary.

Rationale: Convention ambiguity is the silent-physics generator. The project's "JEOD Convention Rule" exists because one guessed sign convention produced an 11,668 km error against NASA flight data while passing trivial tests. Verification: Documented conventions; cross-validation against JEOD; the read-the-JEOD-source discipline for any ambiguity. Trace: JEOD_INV RF.06 (parent-coordinates), RF.07 (orientation convention); CLAUDE.md "JEOD Convention Rule".

RFS-202 Canonical representation, consistent derivations

Where redundant representations of the same rotation coexist (e.g. quaternion and matrix), the system shall define which representation is canonical for a given state and shall guarantee that no consumer ever observes mutually inconsistent representations of one rotation.

Rationale: Stale-cache divergence between redundant forms is undetectable downstream. Note the need is per-state, not global: production legitimately contains both quaternion-canonical states (typed path) and matrix-canonical states (rotation-model kernels whose matrix is the source of truth), and a correct design must honor both without letting them drift. Verification: Consistency checks at boundaries; round-trip tests covering both canonicity regimes. Trace: JEOD_INV RF.04; sync_pfix_rotation (matrix-canonical writer); June 2026 verification of the dual-regime reality.

RFS-203 Epoch association and staleness detectability

A time validity (epoch) shall be associable with frame state, and a consumer shall be able to detect mixed-epoch or stale frame data when frames are exchanged, persisted, or composed from multiple sources.

Rationale: JEOD stamps every frame with update_time. Inside one process the scheduler guarantees coherence; across a process boundary or a recording, the data must carry its own time meaning or staleness becomes silently wrong physics. Verification: Exchange tests where a deliberately stale node is detected and surfaced. Trace: JEOD RefFrame::update_time; #659 R4/R5 motivation.


3xx — Failure behavior and integrity

RFS-301 Earliest-possible misuse detection; never silent

Frame misuse — combining quantities expressed in different frames, applying a transform to a quantity in the wrong frame, or interpreting state under a wrong frame label — shall be prevented at build time where the representation permits, and otherwise detected at the operation boundary with a loud failure. In no case shall misuse pass silently.

Rationale: This bug class compiles, runs, passes trivial tests, and produces wrong answers at scale. Recorded incidents: 11,668 km (guessed periapsis convention), 54.8° attitude error (caught only by full-state cross-validation, #618), 0.13 m/s NED velocity (wrong angular-velocity coordinates). The compile-time checking that exists today is a load-bearing guardrail (Audit-2026-05) and shall not regress in any redesign. Verification: compile-fail test suite; #[should_panic] negative tests for runtime boundaries. Trace: CLAUDE.md "Fail Loudly" and "JEOD Convention Rule"; JEOD_INV TS.01; incidents above; #659 R6.

RFS-302 Checked reinterpretation of stored data

Any operation that ascribes a specific frame interpretation to stored or exchanged frame data shall validate that claim against recorded identity, rather than relying on caller assertion.

Rationale: The known gap in the current system (#659 baseline): recovery of a typed view from untyped storage is a caller assertion with no runtime check, so one wrong type parameter silently mislabels physics. Fail-Loudly demands the claim be witnessed, not trusted. Verification: Negative tests — a mismatched interpretation claim fails loudly, naming both the claimed and the recorded identity. Trace: #659 R6; current from_untyped_unchecked / get_state_typed caller-asserts baseline.

RFS-303 Unresolved references detectable, never silently resolved

A reference to a frame that is missing, not yet present, or stale shall be detectable by the consumer as an observable outcome, and shall never be silently treated as resolved — in particular, never silently resolve to a different frame (e.g. through storage-slot reuse) or to a substituted default.

Rationale: Companion to RFS-401: if identity can be held and resolved later — across documents, processes, and time — a resolution miss is a normal data condition that consumers must be able to surface (#659 R8), while silent mis-resolution is wrong physics with no symptom (the current arena handle is a bare index with no guard against slot aliasing). How loud a given call site is on absence — recoverable result vs immediate failure — is severity policy, left to design and to the Fail-Loudly rule for misconfigurations. Verification: Negative tests: dangling/stale reference reported naming the reference; no path silently substitutes another frame. Trace: #659 R8; RFS-401; arena FrameId aliasing hazard (June 2026 audit). JEOD's null-lookup vs hard-fail split is design precedent, not a requirement.

RFS-304 Structural and semantic integrity by construction

The system shall reject, at the point of introduction and with named diagnostics: cycles, duplicate frame identities, references that would span disconnected structures without declaration, and semantically invalid structure — in particular, only non-rotating (inertial-class) frames are eligible to serve as a tree root or as an integration frame.

Rationale: The root/integration constraint is JEOD's own (only planet-centered-inertial and barycenter-inertial frames may root the tree or host integration); integrating in a rotating frame without the corresponding fictitious-force treatment is silently wrong physics. Verification: Negative construction tests for each rejected condition. Trace: JEOD ephem_ref_frame constraints; JEOD_INV RF.01/RF.02; #659 R8.

RFS-305 Classification–state consistency checkable

Where a frame's classification implies kinematic properties (e.g. an inertial classification implies zero angular rate), the system shall be able to check the claim against the frame's state and fail loudly on contradiction.

Rationale: Classification is producer-supplied metadata. An external producer mislabeling a rotating frame as inertial would silently defeat every consumer that reasons from classification (RFS-501). The claim is mechanically checkable; an unchecked checkable claim is a silent failure waiting. Verification: Validation tests with deliberately inconsistent classification/state. Trace: June 2026 design exploration; #659 R3 discussion.


4xx — Identity and resolution

RFS-401 Stable frame identity

Every frame shall carry a stable, comparable identity that is independent of its storage position and of any compile-time representation, and that consumers can hold and later resolve.

Rationale: Positional handles (arena indices) and conventional labels (name strings) are not identity. The codebase has organically grown at least seven partial identity mechanisms (arena ids, dotted names, host-generic source ids, builder handle newtypes, branded indices, …) — evidence that the primitive is missing and being locally re-invented. Verification: Identity survives storage reorganization; resolution round-trips. Trace: #659 R1; June 2026 identity-mechanism inventory.

RFS-402 Identity agreement across boundaries

Frame identity shall be meaningful across process boundaries and through persistence: two parties exchanging frame data shall agree on which frame is which without sharing code.

Rationale: The motivating consumers of #659 — separate producer/consumer processes, persistence and replay, downstream tooling — all reduce to identity that survives leaving the producer's address space. Verification: Cross-process / persisted round-trip tests. Trace: #659 R1, R5.

RFS-403 Recoverable relationships

The from/to relationship of any transform or relative state shall be expressible and recoverable at runtime, as data.

Rationale: Compile-time relationship encoding is erased at storage and unavailable across boundaries; exchange and replay need the relationship itself to travel. Verification: Round-trip of relationship records with endpoints intact. Trace: #659 R2.

RFS-404 No silent aliasing or impersonation

Identities minted by independent producers shall not silently alias one another, and an externally supplied frame shall not be able to assume the identity of a locally defined frame.

Rationale: The two canonical field failures of runtime frame systems: ROS tf2's multiple-publisher tree corruption (silent aliasing), and the id hijacking that SPICE prevents by resolving built-in frames before any kernel-defined frame. Both produce silent wrong physics; both are structural properties a design must guarantee, not document. Verification: Merge-collision tests (loud rejection); impersonation negative tests. Trace: #659 R1 (multi-source); prior-art audit (tf2, SPICE), June 2026.


5xx — Classification

RFS-501 Semantically sufficient classification

Runtime classification shall be expressive enough for a consumer to reason about the frame semantics it depends on — at minimum: rotating vs non-rotating, root/integration eligibility, planet-anchored vs vehicle-anchored, and body-frame role — and shall at least distinguish the categories JEOD's runtime model distinguishes.

Rationale: #659 R3 names the current three-variant kind a floor, not a ceiling. JEOD's runtime taxonomy distinguishes planet-centered inertial, barycenter inertial, planet-fixed, alt-inertial/alt-pfix, and body core/composite/structure/point roles — consumers of a runtime layer need at least that resolution to interpret frames without compile-time types. Verification: Coverage review of classification against the JEOD taxonomy. Trace: #659 R3; JEOD EphemerisRefFrame flavors, BodyRefFrame roles.

RFS-502 Extensible classification

It shall be possible to add new classification categories without breaking existing consumers.

Rationale: astrodyn is young; foreseeable additions (barycenter frames, switch frames, new orbit-relative frames) must not invalidate deployed consumers. SPICE added frame classes over decades under the same constraint. Verification: Compatibility tests across classification versions. Trace: #659 R3 ("floor, not a ceiling"); prior-art audit.


6xx — Exchange, persistence, replay

RFS-601 Lossless round-trip

The runtime representation — topology, identity, classification, per-frame state, and epochs — shall round-trip losslessly through a serialized form. Canonical numeric fields shall round-trip bit-exactly; derived/cached forms shall be reconstructed consistently with the canonical fields (RFS-202).

Rationale: Replay and cross-validation lose their meaning at ULP drift (the #562 lesson, applied to persistence). "Approximately restored" is not restoration; the precision of this guarantee must be stated in terms of which fields are canonical. Verification: Full-entropy round-trip property tests asserting bit equality on canonical fields. Trace: #659 R5; incident #562.

RFS-603 Consumer independence

A read-only consumer (renderer, logger, analyzer) shall be able to interpret the serialized form without the producer's code and without the frame system's full implementation.

Rationale: #659's motivating consumer is an external visualization tool that cannot enumerate the producer's frame types at compile time. Engine independence extended to data: the document, not the codebase, is the contract. Verification: A minimal independent reader can be (and is) written against the documented form alone. Trace: #659 motivation; engine-independence non-negotiable.

RFS-604 Replay representability

A recorded run — frame states across a sequence of epochs — shall be representable and reconstructible in order, with frame identity unambiguous across the whole sequence.

Rationale: Persistence and replay are first-class consumers in #659. Identity stability over time is what makes a series a series rather than a pile of snapshots. Verification: Record→replay equivalence tests. Trace: #659 motivation ("persistence and replay").

RFS-605 State origin distinguishability

Exchanged or persisted frame state shall be distinguishable by origin: integrated, derived, or injected (see Definitions).

Rationale: Verified June 2026: production writes all three into one tree — body-frame writebacks (integrated), ephemeris and rotation-model evaluations (derived), caller-supplied retargets and fixed orientations (injected). Origin is a property of the state, not the frame: a model-driven node can also carry an injected override. A serialized form that cannot tell origins apart double-books integrated state (a tree node is a projection of body state owned elsewhere) and leaves replay ambiguous about which values are ground truth and which are reconstructible. JEOD's checkpoint philosophy reflects the same split: persist state, regenerate the derivable. Verification: Round-trip preserves origin; replay reconstruction honors it. Trace: June 2026 tree-write inventory (per-site classification of every production frame-tree write); JEOD checkpoint/restart model.


7xx — Multi-source composition and runtime definition

RFS-701 Multi-source composability

Frames contributed by independent producers shall be composable into one resolvable structure.

Rationale: #659's motivating consumer: several producers contributing frames into one tree (e.g. a visualization host merging feeds). Verification: Merge tests across independently produced frame sets. Trace: #659 motivation ("multi-source composition").

RFS-702 Cross-source relationships are explicit

A kinematic relationship across a producer boundary shall exist only by explicit declaration. A query spanning sources with no declared relationship shall fail loudly rather than return a silently disconnected, aliased, or inferred result.

Rationale: tf2's silent-topology-corruption failure mode arises exactly from inferring topology from independently published data. Equivalence between "their Earth-fixed" and "our Earth-fixed" is a physical claim only the host can make; the system must demand the claim, not guess it. Verification: Negative tests: undeclared cross-source query → loud, diagnostic failure. Trace: prior-art audit (tf2); June 2026 judging (named the implicit-graft hazard).

RFS-703 Runtime-definable frames

Frames unknown at build time shall be fully representable — identity, classification, state, topology participation, and exchange — on par with statically-defined frames.

Rationale: The statically-known frame set cannot name producer-defined frames (#659 R7), and the motivating consumers produce them (sensor frames, external vehicles, site frames defined in config or over the wire). Verification: End-to-end tests where a runtime-defined frame participates in relative-state queries and exchange round-trips. Trace: #659 R7.

RFS-704 Lossless static↔runtime correspondence

Producers computing with statically-defined frames shall be able to publish into the runtime representation without losing identity or relationship information, and — where the target is a statically-defined frame — recover a statically-checked view, subject to RFS-302 validation.

Rationale: #659 R6: the gap currently bridged by hand. Erasure must be total and lossless so that recovery can be checked at all. Verification: Publish→recover round-trip tests; checked-recovery negative tests (RFS-302). Trace: #659 R6.

RFS-705 Guarantee preservation under extension

Admitting runtime-defined frames shall not weaken the guarantees of statically-defined ones: no runtime-defined frame shall be granted the checked interpretation of a statically-defined frame, and code paths operating purely on statically-defined frames shall retain their build-time guarantees unchanged.

Rationale: Extension must be sound, not merely expressive. If runtime data could masquerade as a statically-checked quantity, every compile-time guarantee in the system would become advisory. This requirement makes the "membrane" between the two regimes a permanent property of the system rather than a feature of one design. Verification: Negative tests (runtime-defined frame refused static interpretation); the compile-fail suite holds unchanged across the feature's introduction. Trace: #659 R6/R7 jointly; June 2026 design discussion (typed↔runtime membrane); JEOD_INV TS.01 discipline.



9xx — Architecture, performance, usability

RFS-901 Engine independence

The frame system shall be usable by any host. No engine-specific types (ECS components, scheduling primitives, engine math types) shall appear in its interfaces. Hosts include the Bevy adapter, the arena runner, and third-party code, as peers.

Rationale: Engine independence is the project's founding design goal — the coupling JEOD carried to Trick is the mistake astrodyn exists to avoid. Verification: Dependency enforcement (check_no_bypass_deps.sh); a non-Bevy host (the runner) exercises the full capability. Trace: CLAUDE.md non-negotiables; Strategy.

RFS-902 Storage-agnostic algebra

The relative-state composition algorithm shall be definable once, independently of how a host stores frame data, so that multiple storage backends cannot diverge numerically.

Rationale: Two backends exist today (arena tree, ECS hierarchy) and are verified to share one walk. A forked algorithm would diverge in exactly the ULP-sensitive ways incident #562 punished, and the divergence would be invisible until a parity test catches it. Verification: Cross-backend parity suite (bevy_parity_*). Trace: FrameStorage seam (existence proof, not prescription); #562.

RFS-904 Hot-path cost discipline

Identity, classification, and integrity checking shall be structured so that steady-state per-step frame computation is not measurably regressed. Checks may concentrate at construction, exchange, and reinterpretation boundaries.

Rationale: Relative-state walks are the inner loop of every simulation step. A correctness layer that taxes the inner loop will be bypassed under pressure, which is worse than not having it — guarantees must be affordable to be real. Verification: Performance microbenchmarks (perf-microbench CI lane) before/after any frame-layer change. Trace: CI benchmark lane; June 2026 design risk notes.

RFS-906 Common-case simplicity

Mission code composing standard scenarios (defining vehicles, reading states) shall not be required to interact with frame machinery beyond naming the frames it cares about. Advanced capabilities — exchange, multi-source composition, runtime frame definition — shall be opt-in and invisible otherwise.

Rationale: Verified June 2026: mission code today never touches the frame tree (it builds vehicles and reads states through high-level surfaces). That property arose by design care, and this requirement preserves it on purpose rather than by accident through future redesigns. Verification: The worked mission examples remain representative and free of frame-machinery ceremony; review against them. Trace: June 2026 ergonomics analysis; typed_mission.rs / multi_body_scenario.rs examples.

RFS-907 Domain-language diagnostics

Frame errors — at build time or runtime — shall be expressed in physics language: naming the frames involved, the convention violated, and the expected-vs-actual relationship.

Rationale: A frame error that names memory layout or generic bounds instead of frames does not get fixed correctly. The project already invests in physics-language compile diagnostics; runtime failures owe the same standard. Verification: Review of compile-fail expectations and panic-message assertions in negative tests. Trace: Type-System diagnostics culture; CLAUDE.md Fail-Loudly message patterns.


Not restated here

Rules that bind the frame system but are owned and enforced elsewhere are deliberately not duplicated as requirements in this spec:

  • JEOD fidelity — parity with JEOD v5.4 as definition of done, computational independence, staged-update semantics, and invariant traceability: CLAUDE.md non-negotiables, the Tier 3 CI suite, and docs/JEOD_invariants.md (consistency-checked by tests/invariant_coverage.rs).
  • Workspace precision and safety — f64-everywhere and #![forbid(unsafe_code)]: CLAUDE.md plus workspace lints (compiler-enforced).

(ID gaps in this draft — group 8xx, RFS-903, RFS-905 — correspond to early draft rows covering these rules, removed during review to avoid duplication; gaps left unallocated rather than renumbering mid-review.)

Out of scope — design freedoms

These are deliberately not constrained by this spec. Any design satisfying the requirements above may choose freely:

  • Identity representation (integers, strings, structured values) and any namespace/allocation scheme.
  • Storage layout (arena, ECS, registry, hybrid) and indexing.
  • Serialization format and encoding (text/binary), beyond what RFS-601 requires of the result. A self-describing form — schema version and conventions carried in-band, CCSDS-style, validated before any state is interpreted — is a recommended design device for honoring RFS-201/RFS-301/ RFS-603 across process boundaries; recorded here as design guidance, not a requirement.
  • API shapes, type-system mechanics, trait design, crate placement.
  • Eager vs lazy evaluation strategy. Unconstrained by this spec — but note (June 2026 verification, recorded here so it is not re-litigated from scratch each redesign): while JEOD parity remains the project's definition of done, query-at-actual-epoch (lazy/provider) evaluation changes observable within-step staleness semantics — step-start snapshots consumed across integration sub-stages, scheduled-cadence model refreshes (JEOD_INV PF.06, DM.13) — beyond Tier 3 tolerances. That bound comes from the parity obligation itself, not from a requirement in this spec; if the parity obligation is ever relaxed, this design family reopens.
  • A subscription/activation model (JEOD has one; astrodyn currently updates all frames every step). Not a present need; revisit if frame-count scaling demands it.
  • Time-buffered, interpolating transform queries (tf2-style). Not a present need; replay (RFS-604) is the recorded-sequence form of the requirement.

Current gaps (snapshot, 2026-06)

Non-normative orientation for readers; the requirements above are the durable content. As of 0f78ef9: groups 1xx/2xx/9xx are largely satisfied by the existing typed facade + frame tree (epoch association RFS-203 excepted); RFS-302 (checked reinterpretation), 4xx (identity), RFS-501 (classification breadth), 6xx (exchange — no serialization exists), and 7xx (multi-source, runtime definition) are the open ground motivating issue #659 and its follow-on design work.

Change log

  • 2026-06-05 — Initial draft, under review. Sources: issue #659, June 2026 design exploration and verification, JEOD v5.4 source audit, project incident history. During review: rules already owned and enforced elsewhere (JEOD fidelity, workspace precision/safety lints) removed as requirements and consolidated under "Not restated here"; RFS-303 rewritten to outcomes (two-tier severity was design leakage); RFS-602 (self-describing exchange) removed as mechanism rather than need — its substance is covered by RFS-201/301/603, the device preserved as design guidance under design freedoms.

Clone this wiki locally