Date: 2026-06-08. Status: approved design (conversational brainstorm), pre-implementation. Goal: drive p_e p99 toward 0.15 by killing the EE velocity-LAG at its root. (Not git-committed — no repo.)
p_e is a velocity lag in the RELATIVE-motion
feedforward, not the orbit. The CC controller treats
v_c (COM/orbit velocity) as a DISTURBANCE it rejects –
orbit motion is not a reference driver. The laggy quantity is
self.nu_e.local.des (the desired EE twist in the
local/relative frame), currently
desired_nu_e_feedforward_consistent finite-diffing the
FILTERED desired pose. Finite-diff of the filtered pose lags; of the raw
pose is jerky (reselect steps). Pointing-target smoothing (TASK_11
blend, TASK_13 glide) improves z_e but does NOT touch this lag (and
slightly worsens p_e). The fix is to feed nu_e.local.des an
analytic, smooth twist (world or relative – A/B) from a
planned reference.
surface_target(s)
(precompute)For orbit progress s (the COM helix is a known smooth
function of s), project the COM orbit position p_c(s) onto
the target mesh – ray-cast inward (toward the body) / nearest-surface –
to get the surface patch “under” the platform at phase s. Precompute
over the orbit samples, then smooth / resample to a C1 function
s -> x_surf(s) (the raw projection can jump across
facets/concavities; smoothing removes that). This is feasible BY
CONSTRUCTION: x_surf(s) is reachable/visible from the orbit at phase s
(that’s how it was generated), so following it never demands an
unreachable point (unlike center-pointing). Coverage is guaranteed as s
sweeps pole-to-pole.
Online, s(t) = t * desired_speed (arclength), so the
commanded pointing target is x_surf(s(t)), smooth; the
desired EE pose p_e_des(s) points at it from the standoff.
The analytic, smooth world EE velocity is
v_world = d(p_e_des)/ds * sdot (no finite-diff -> NO
LAG, NO JERK – the jerk fix). v1 tests BOTH routes on the full helix
(config flag; per the breve math both are derivable from the same smooth
path): - Route A – analytic tracking FF
(nu_e.local.des): replace the lagged finite-diff
with nu_e.local.des = R_te.T @ v_world, then through the
existing nu_e_local_to_oplus(.., v_c, ω_b) conversion
(which ALREADY folds v_c in -> consistent with eq 22b; do NOT
pre-subtract v_c or it double-counts). Smooth, un-lagged desired EE
twist. - Route B – C_c v_c disturbance feedforward
(into w_e^⊕): per eq (22b), v_c enters the
attitude+EE block ONLY as the known forcing term C_c v_c.
Feed +C_c v_c (analytic, from the smooth orbit reference)
directly into the EE torque/RHS to cancel the disturbance at the source,
rather than via tracking. (C_c is available in
self.motion/self.dyn; the RHS already has a
COM_coupling term – check whether it already includes this or needs the
explicit FF.) Routes A and B are independent and composable; v1 measures
each alone + together. finite-diff stays as the fallback when no path is
active. (RELATIVE-mode pre-subtraction deferred – math says it
double-counts; revisit only if A+B underperform.)
The geometric path is the smooth backbone; the scorer corrects it
toward high-value/unseen surface. Concretely:
x_cmd = (1-w)*x_surf(s) + w*x_scorer, w a small config
weight (e.g. 0.2) so the command stays smooth while biasing toward the
scorer’s pick; x_scorer is the existing finder’s chosen
surface point, clamped to a neighborhood of x_surf to
preserve reachability + smoothness. The analytic FF still derives from
x_surf(s) (the smooth backbone), so the correction does not
re-introduce jerk. Scorer/reselect demotes from “primary driver” to
“corrector” – consistent with TASK_5/7 (selection is not the coverage
lever; the orbit is). w=0 -> pure geometric path; sweep
w.
GNC/guidance/ee_guidance.py (or a small new
orbit_path helper it owns): builds x_surf(s)
at init (precompute from target_finder.mesh +
traj.orbit), evaluates x_surf(s) +
x_surf'(s) online, applies the reactive correction.GNC/BreveController.desired_nu_e_feedforward_consistent:
consume the analytic twist (world or relative, per ff_mode) from the
path (via the Desired record or the guidance handle) instead of
finite-differencing.camera_guidance.trajectory.orbit_path block
(enable, smoothing, correction gain,
ff_mode: world|relative to select the feedforward
formulation for the A/B).x_surf: define the desired
EE pose fresh from x_surf(s) + standoff (do NOT reuse the
reactive selection.pose), then CLAMP the motion
(rate-limit). Accept com.v_max may need to come back down
as a consequence.goal.pos.rate_max=0.04 (= 0.0012 m/step on the OFFSET) and
goal.step_max=0.05 throttle exactly the relative motion
that lags – likely too tight for the analytic FF; raise as needed.
nu_des.max=0.1 is DEAD cruft (only the inactive
use_fd_pe=False branch reads it; live FF uses desired_twist.v_max=0.9) –
remove it.x_surf(s).
Validate C1 + reachable BEFORE wiring the FF (it’s the #1 failure
point).max |Δ nu_e_des| (peak per-step
jump in the desired twist = the reselect-impulse magnitude) on BOTH
nu_e_des and the actual nu_e; drive it from its current reselect-spike
level to the smooth-orbit level. COMPLEMENT: FFT HF-energy fraction of
nu_e_des (cutoff ~1 Hz, picked off a baseline spectrum) – shows energy
migrating from HF impulses to the LF orbit. SANITY: RMS of 2nd-diff of
p_e_des (desired-accel-roughness; rename the mislabeled refine_gate
ref_jerk). The analytic smooth FF is precisely a
jerk-killer.orbit_path.enable=off must reproduce baseline (cov
0.998, p_e 0.404, z_e 0.027) – no-op sanity.Controller gain/structure changes (TASK_12 showed they destabilize); pointing-only smoothing (failed 3x). This is purely the reference + feedforward.