orbit_com_path — the CoM inspection orbit: build it, track it, roll it forward

Concept

The system’s center of mass follows a pole-to-pole spherical helix that sweeps the target for full
coverage. Three files own three stages of that one trajectory — build the geometry (utils/orbit.py),
track it per step as a desired (p_c, v_c, a_c) (GNC/com_guidance.py), and roll it forward
dynamics-free as a coverage oracle (guidance_rollout.py). No single file holds the whole pipeline, so
the per-file pages document each stage and this documents how they chain.

What this spans

  • utils/orbit.py (orbit) — generates the spherical helix and exposes it two ways: an analytic
    closed-form sampler and a uniform-arclength polyline with tangents + curvature.
  • GNC/com_guidance.py (com_guidance) — turns the static orbit into a live per-step CoM reference:
    monotonic progress tracking, a lookahead velocity field, a startup speed ramp, optional smoothing.
  • GNC/guidance/guidance_rollout.py (guidance_rollout) — walks the same orbit on a pure arclength
    clock without the controller/dynamics, to study selection + coverage in isolation.
  • Thesis: the inspection orbit is a single geometric object (a spherical helix) that is generated once,
    tracked dynamically by the controller path, and swept open-loop by the oracle path — and the second
    and third readers must agree on the first’s geometry (curvature, tangents, arclength) or the coverage
    number is meaningless.

Constituent pages

Down-links to the per-file pages that hold each part’s contract:

  • orbitOrbitGenerator: builds the helix, the uniform-arclength polyline, and the analytic sampler;
    the sole owner of the geometry.
  • com_guidanceCOMGuidance: the root of the guidance tower; the live per-step CoM desired with
    progress tracking + startup ramp.
  • guidance_rolloutbuild_guidance_only_rollout: the dynamics-free coverage/selection oracle that
    walks the orbit on an arclength clock.

Mechanism (where it lives in code)

The end-to-end path from helix coefficients to a tracked (or swept) CoM trajectory:

Stage 1 — build the geometry (once, cached):

  • Sample the analytic spherical helix on the time grid; cache its (R, θ_rate, φ0, φ_rate) coefficients — utils/orbit.py:75
  • Re-interpolate to N points uniform in arclength (the addressable polyline) — utils/orbit.py:97
  • Build the cached path + the progress→u parameter table for analytic sampling — utils/orbit.py:27
  • Polyline tangents + Frenet curvature vectors (open-path differences, run at construction) — utils/orbit.py:150
  • Analytic curvature magnitude at axial coordinate s (closed form, no FD ripple) — utils/orbit.py:119
  • Runtime closed-form sampler: position / unit-tangent / curvature vector at a continuous progress index — utils/orbit.py:46

Stage 2 — track it per step (the live controller path):

  • Startup speed ramp exp_scale(t), gated on controller.com.startup.enableGNC/com_guidance.py:39
  • Live per-step entry: ramped speed → desired → optional smoothing — GNC/com_guidance.py:89
  • The velocity-field core: monotonic progress (local window) → lookahead tangent → (p_c, v_c, a_c)GNC/com_guidance.py:170
  • t=0 seed that caches a_c(0) for the analytic-feedforward warm-start — GNC/com_guidance.py:111
  • Curvature-limited arc-time oracle (v ≤ √(a_ff_max/κ)) used to size run duration — GNC/com_guidance.py:134

Stage 3 — roll it forward dynamics-free (the oracle path):

  • The loop: resolve windows → arclength clock s = t·desired_speed → sample CoM/base/EE goal → mark FOV every stride → log — GNC/guidance/guidance_rollout.py:90
  • Sample the nominal CoM from arclength progress: v_c = speed·t̂, a_c = speed²·κGNC/guidance/guidance_rollout.py:45
  • Fake plant: desired base motion carrying the previous desired EE command (no dynamics solve) — GNC/guidance/guidance_rollout.py:60
  • FOV-marking stride (cost lever, not resolution) — GNC/guidance/guidance_rollout.py:13

Evidence

Purely structural — the chain is established by who owns the geometry and who reads it, not by a single
run. OrbitGenerator is instantiated by exactly one owner, COMGuidance (GNC/com_guidance.py:34), and
the rollout reads the same cached fields (traj.orbit.path / .tangents / .curvature,
GNC/guidance/guidance_rollout.py:45) so both stages sweep one geometry. The two readers do agree on the
acceleration form (a_c = ‖v‖²·κ): the centripetal feedforward in desired_at_window
(GNC/com_guidance.py:170) and a_c = speed²·κ in _sample_nominal_com (GNC/guidance/guidance_rollout.py:45)
are the same §5.3 term. The durable rationale (closed-form vs polyline curvature, open-path differences,
coverage-as-traversal) is recorded in utils/INSIGHTS.md (orbit [math]) and GNC/INSIGHTS.md
([guidance], guidance_rollout, Coverage marking); numbers live in the dated CLAIMS.md, never here.

Footguns

Two curvature paths, two sample shapes — never cross-wire them

orbit exposes curvature twice: sample_analytic (utils/orbit.py:46) returns the closed-form
curvature vector to dodge the polyline finite-difference ripple at segment crossings, while the cached
self.curvature field (utils/orbit.py:150) is the polyline Frenet form for windowed lookups. The live
tracker reaches for the analytic sampler; the rollout reads the cached polyline window. Feeding one where
the other is expected silently changes the reference signal. (utils/INSIGHTS.md orbit [math])

Progress is strictly monotonic — the tracker never walks backward

COMGuidance takes progress = max(progress_prev, progress_geom) and re-centers a cheap local search
window each step (GNC/com_guidance.py:170); the full-orbit polyline scan runs once per run. The rollout
path has no such tracker — it drives an open-loop arclength clock s = t·desired_speed
(GNC/guidance/guidance_rollout.py:90). Don’t expect the rollout’s progress to behave like the tracker’s;
they sweep the same curve but advance it differently. (GNC/INSIGHTS.md [guidance])

The rollout's motion state is faked — it is NOT a controller validation

guidance_fake_motion_state (GNC/guidance/guidance_rollout.py:60) copies the previous desired EE command
instead of integrating dynamics. This path measures coverage and selection along the orbit, and says
nothing about tracking error, the derate stack, or whether the controller can actually fly the helix.
Coverage here is a traversal artifact: a short run only sweeps the southern cap (~0.47); the full
helix reaches 1.0, so size runs by progress-to-completion, not step count.
(GNC/INSIGHTS.md guidance_rollout, Coverage marking)

Open path — never wrap terminal onto initial

The helix is an open pole-to-pole curve. Tangents and dt/ds use one-sided differences at the endpoints
(utils/orbit.py:150); wrapping the last sample onto the first would inject a spurious jump into both the
reference and the curvature. (utils/INSIGHTS.md orbit [math])

Equations & references

Key equations mirrored from current_sota — the math source of truth; see there for derivations.

Spherical-helix position (analytic, , ) — §5.2, eq (5.4):

Startup speed ramp§5.1, eq (5.1):

Centripetal acceleration feedforward§5.2, eq (5.3):

References:

  • CoM orbit reference + startup ramp (§5.1, eq 5.1): current_sota > 5 · GNC/com_guidance.py:89.
  • Analytic spherical-helix tangent & curvature (§5.2): current_sota > 5 · utils/orbit.py:46.
  • Centripetal acceleration feedforward a_cd = ‖v_cd‖²κ (§5.2–5.3): current_sota > 5 · GNC/com_guidance.py:170 / GNC/guidance/guidance_rollout.py:45.
  • eq↔code cross-check: generated_reports/GNC/cross_check.md.

com_vs_base · target_finding_coverage · guidance_modes · orbit · com_guidance · guidance_rollout · base_guidance · ee_guidance · terminology