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, with all four carried as first-class data with documented meaning, such that a consumer can interpret a rotating frame without recomputing the model that produced it.

Rationale: The foundational query of orbital dynamics — every consumer (gravity, integration, frame switching, sensors, visualization) reduces to it — and full kinematics is part of the need, not an extra: velocity composition requires the angular-rate terms (ω×r), and conventions for them have produced real bugs when guessed (a 0.13 m/s ground-point velocity error from angular-velocity coordinates, caught in Tier 3 Bucket B work). Verification: Unit tests against direct composition, including non-zero rates and non-trivial rotations; Tier 3 cross-validation against JEOD reference data. Trace: JEOD ref_frame_compute_relative_state.cc; #659 R4; JEOD_INV RF.01, RF.02, RF.06, RF.07; Common-Pitfalls.

RFS-102 Deterministic composition

Every non-root frame shall have exactly one parent; relative state shall be computed along the unique path through the common ancestor; and equivalent decompositions of the same query — including composition through identity (degenerate) intermediate frames — shall yield bit-identical results.

Rationale: Multiple paths or shape-dependent arithmetic make results path-dependent at the floating-point level. Determinism, replay, and cross-host parity require one answer: incident #562 — ~30 ULP body-position drift between a 1-hop and a 2-hop tree shape for the same physics — is the recorded cost of violating this, and hosts legitimately differ in tree shape. Verification: Structural (no operation can introduce a second parent); cycle-rejection negative tests; bit-comparison across equivalent tree shapes. Trace: JEOD tree model; issue #562; JEOD_INV RF.13.

RFS-103 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

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".


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 particular, any operation that ascribes a specific frame interpretation to stored or exchanged frame data shall validate that claim against recorded identity rather than rely on caller assertion. 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; the known runtime gap (#659 R6) is recovery-by-caller-assertion at the typed↔runtime boundary, where one wrong type parameter silently mislabels physics. Verification: compile-fail test suite; #[should_panic] negative tests at runtime boundaries — a mismatched interpretation claim fails loudly, naming both the claimed and the recorded identity. Trace: CLAUDE.md "Fail Loudly" and "JEOD Convention Rule"; JEOD_INV TS.01; #659 R6; incidents above.

RFS-302 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-303 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; structure that violates frame semantics — only non-rotating (inertial-class) frames are eligible to serve as a tree root or as an integration frame; and classification claims that contradict the frame's state (e.g. an inertial classification with non-zero angular rate).

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. Classification is producer-supplied metadata: where it implies mechanically checkable kinematic properties, an unchecked contradiction silently defeats every consumer that reasons from it (RFS-501). Verification: Negative construction tests for each rejected condition, including deliberately inconsistent classification/state. Trace: JEOD ephem_ref_frame constraints; JEOD_INV RF.01/RF.02; #659 R3, R8.


4xx — Identity and resolution

RFS-401 Stable frame identity

Every frame shall carry a stable, comparable identity — independent of its storage position and of any compile-time representation — that consumers can hold and later resolve, including across process boundaries and persistence, without sharing the producer's code.

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 — and 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: Identity survives storage reorganization; cross-process / persisted resolution round-trips. Trace: #659 R1, R5; June 2026 identity-mechanism inventory.

RFS-402 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.


5xx — Classification

RFS-501 Semantically sufficient, extensible 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 — shall at least distinguish the categories JEOD's runtime model distinguishes, and shall admit new categories without breaking existing consumers.

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. And astrodyn is young: foreseeable additions must not invalidate deployed consumers (SPICE added frame classes over decades under the same constraint). Verification: Coverage review against the JEOD taxonomy; compatibility tests across classification versions. Trace: #659 R3; JEOD EphemerisRefFrame flavors, BodyRefFrame roles; 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, including as an ordered sequence of states across epochs (a recorded run) with frame identity unambiguous across the whole sequence. Canonical numeric fields shall round-trip bit-exactly; derived/cached forms shall be reconstructed consistently with the canonical fields.

Rationale: Replay and cross-validation lose their meaning at ULP drift (the #562 lesson, applied to persistence) — "approximately restored" is not restoration — and identity stability over time is what makes a recorded sequence a replayable run rather than a pile of snapshots. 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; record→replay equivalence tests. Trace: #659 R5 and motivation ("persistence and replay"); incident #562.

RFS-602 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-603 Boundary-crossing state metadata: epoch and origin

Frame state that is exchanged, persisted, or composed from multiple sources shall carry interpretable metadata: an associable time validity (epoch — when), and its originintegrated, derived, or injected (see Definitions) — with mixed-epoch or stale data detectable by consumers and origins distinguishable.

Rationale: Inside one process the scheduler guarantees time coherence; across a boundary the data must carry its own time meaning or staleness becomes silently wrong physics (JEOD stamps every frame with update_time). On origin, 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) — and 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: Exchange tests where stale/mixed-epoch data is detected and surfaced; round-trip preserves origin; replay reconstruction honors it. Trace: JEOD RefFrame::update_time; #659 R4, R5; June 2026 tree-write inventory; JEOD checkpoint/restart model.


7xx — Multi-source composition and runtime definition

RFS-701 Multi-source composition is sound

Frames contributed by independent producers shall be composable into one resolvable structure. 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. 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: #659's motivating consumer: several producers contributing frames into one tree. The canonical field failures of runtime frame systems live exactly here — ROS tf2's multiple-publisher tree corruption (silent aliasing; topology inferred from independently published data) and the id hijacking SPICE prevents by resolving built-in frames before any kernel-defined frame. 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: Merge tests across independently produced frame sets; merge-collision and impersonation negative tests; undeclared cross-source query → loud, diagnostic failure. Trace: #659 motivation ("multi-source composition") and R1; prior-art audit (tf2, SPICE); June 2026 judging (named the implicit-graft hazard).

RFS-702 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-703 Lossless, guarantee-preserving 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, validated per RFS-301. 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: #659 R6: the gap currently bridged by hand — erasure must be total and lossless so that recovery can be checked at all. And 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 makes the "membrane" between the two regimes a permanent property of the system rather than a feature of one design. Verification: Publish→recover round-trip tests; checked-recovery 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.



8xx — Usability

RFS-801 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.


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).
  • Engine independence and the three-layer architecture — physics defined once in engine-agnostic crates, hosts drive it, no engine types below the adapter: CLAUDE.md non-negotiables, enforced by scripts/check_no_bypass_deps.sh and the cross-host bevy_parity_* suite. The frame-specific consequences (one composition algorithm across storage backends; no ECS leakage into frame interfaces) follow from these; these are natural rows for a future astrodyn-level architecture spec.
  • Diagnostic quality — failures name the broken invariant, the offending input, and what to change, in domain language: CLAUDE.md Fail-Loudly message patterns; the compile-time diagnostics culture is documented in Type-System.

(Requirements were renumbered contiguously during draft review; the wiki commit history records the old→new mapping.)

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-602 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-601) is the recorded-sequence form of the requirement.
  • Where correctness checks live relative to the hot path. Design guidance: the per-step relative-state walk is the inner loop of every simulation; concentrate identity/classification/integrity checking at construction, exchange, and reinterpretation boundaries so guarantees stay affordable — a correctness layer that taxes the inner loop gets bypassed under pressure, which is worse than not having it. Benchmark before/after any frame-layer change (perf-microbench CI lane).

Clone this wiki locally