-
Notifications
You must be signed in to change notification settings - Fork 0
Spec Reference Frame Requirements
Status: Active. Last updated 2026-06-05 (initial issue), grounded against the
codebase at 0f78ef9.
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.
- 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).
- Environmental frame — a frame whose per-step state is the evaluation of a deterministic model at an epoch (ephemeris positions, RNP/IAU planet-fixed rotations).
- Dynamic frame — a frame whose state is the product of integration (vehicle body 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.
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.
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).
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.
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.
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".
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.
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.
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.
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.
The system shall detect references to frames that are missing or unresolved. Speculative lookups shall report recoverably; load-bearing resolution failures shall fail loudly, naming the unresolved reference.
Rationale: JEOD's model: lookups return null and callers choose severity —
but load-bearing sites hard-fail (DynBody::set_integ_frame aborts on an
unresolvable integration frame). Silent mishandling of a dangling reference is
the failure mode #659 R8 forbids.
Verification: Tests for both tiers.
Trace: #659 R8; JEOD RefFrameManager::find_ref_frame,
dyn_body_integration.cc.
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.
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.
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.
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.
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.
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.
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.
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.
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.
A serialized form shall identify its schema version and the conventions under which its numbers are to be interpreted, sufficiently for a consumer to validate compatibility before interpreting any state.
Rationale: The only structural defense against producer/consumer convention drift — the cross-process form of the JEOD Convention Rule. Without it, a convention change upstream is silently misread downstream, the exact failure mode RFS-301 forbids in-process. Verification: A document with mismatched conventions/version is rejected loudly, never partially interpreted. Trace: June 2026 design exploration; CCSDS/SANA registry practice.
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.
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").
Exchanged or persisted frame state shall be distinguishable as to its origin: derived from a deterministic model evaluation (re-derivable from model identity + epoch), authoritative dynamic state (integration product), or externally supplied override.
Rationale: Verified June 2026: the frame tree conflates model-evaluation caches (environmental frames), projections of authoritative body state (dynamic frames), and caller-supplied overrides (retargeted sources, fixed orientation matrices). A serialized snapshot that cannot distinguish these double-books state and leaves replay semantics ambiguous about which values are ground truth. JEOD's checkpoint philosophy (persist state + registry, regenerate derived structure) reflects the same need. Verification: Round-trip preserves provenance; replay reconstruction honors it. Trace: June 2026 tree-write inventory (per-site classification of every production frame-tree write); JEOD checkpoint/restart model.
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").
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).
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.
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.
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.
Frame algebra shall reproduce JEOD v5.4 reference behavior, validated against JEOD reference data within established Tier 3 tolerances. Missing behavior is ported from JEOD source, never approximated; JEOD outputs are never inputs to production computation.
Rationale: Cross-validation against JEOD is the project's definition of done; computational independence is a non-negotiable. Verification: Tier 3 suite; parity-superset invariant. Trace: CLAUDE.md non-negotiables; JEOD-Capability-Matrix.
The frame system shall support the staged, snapshot-per-step update model that JEOD parity requires — including JEOD's deliberate within-step staleness semantics (step-start snapshots consumed by integration sub-stages; scheduled-cadence model refreshes; one-tick-stale kinematic propagation) — and shall not impose an evaluation model that changes these observable semantics while JEOD parity remains the definition of done.
Rationale: Verified June 2026: the integrator deliberately consumes step-frozen planet-fixed rotations and interpolated source positions across RK4 sub-stages, matching JEOD; the RNP cadence cache reuses precession/nutation from an earlier refresh epoch (PF.06). A query-at-actual-epoch (lazy/provider) evaluation model — however elegant in systems without a parity obligation (SPICE, Orekit, ANISE) — would change accelerations beyond Tier 3 tolerances (~µm-level). This requirement records why that design family is currently out of bounds, so the conclusion is not re-litigated from scratch each redesign. If the parity obligation is ever relaxed, this requirement is the one to revisit first. Verification: Tier 3 suite (any violation surfaces as cross-validation drift). Trace: June 2026 provider-model verification; JEOD_INV PF.06, DM.13.
Every JEOD invariant the frame system enforces (or knowingly diverges from) shall be cataloged and source-traced under the established JEOD_INV discipline.
Rationale: The invariant catalog is how fidelity knowledge survives
contributor turnover and redesigns — precisely this spec's purpose, applied at
the behavioral level.
Verification: tests/invariant_coverage.rs bidirectional consistency check.
Trace: docs/JEOD_invariants.md;
JEOD-Invariant-Workflow.
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.
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.
All frame state and frame algebra shall use f64 throughout; no narrowing conversion shall appear in any frame-state path.
Rationale: Orbital dynamics at f32 loses meters at LEO radii; the workspace
denies the relevant lossy casts for this reason.
Verification: Workspace lints (cast_precision_loss, etc.); review.
Trace: CLAUDE.md "f64 everywhere".
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.
The frame system shall contain no unsafe code.
Rationale: Workspace-wide #![forbid(unsafe_code)] discipline.
Verification: Compiler-enforced.
Trace: CLAUDE.md lints.
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.
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.
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/602 require of the result.
- API shapes, type-system mechanics, trait design, crate placement.
- Eager vs lazy evaluation strategy — within the observable-semantics bound of RFS-802.
- 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.
Non-normative orientation for readers; the requirements above are the durable
content. As of 0f78ef9: groups 1xx/2xx/8xx/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.
- 2026-06-05 — Initial issue. RFS-101 … RFS-907. Sources: issue #659, June 2026 design exploration and verification, JEOD v5.4 source audit, project incident history.