analytic_feedforward — closed-form EE desired-twist feedforward

Purpose

Pure trajectory kinematics: from the CoM-to-surface vector and its time derivatives, build the
desired EE twist and its derivative in closed form (its_tex.md §3.4 / current_sota > 5),
replacing the finite-difference feedforward. Every function is a function of the reference
alone
, never of the measured state.

Role in the system

  • A stateless helper module (module-level functions, no class) supporting ee_guidance — the leaf
    of the deep guidance chain com_guidancebase_guidanceee_guidance.
  • The adopted default since 2026-06-21 (controller.arm.desired_twist.analytic_ff = true,
    parameters.yaml): the FF closed form replaced the FD path mission-wide (pe_median −28%, pe_p99 −11%,
    coverage up) at a bounded conditioning cost (s_min_G median 0.032→0.027). The 4 EE baselines + gold were
    re-pinned. (logs/logs_Jun21_26/CLAIMS.md ~16:00)
  • reduced_desired_twist is called by ee_guidance (analytic_desired_nu_e_feedforward) and folds the
    world twist into the circumcentroidal (⊕) frame the controller consumes.
  • cruise_lag_floor is the same math the breve_controller runs as x_ss_floor — the controller’s own
    prediction of the standing pose error the stiffness must hold once the feedforward has cancelled the
    velocity/inertial demand.
  • Siblings supporting ee_guidance: target_finder, guidance_classes.

Inputs / Outputs

  • In: the standoff reference r, r_dot, r_ddot (CoM→surface vector + derivatives), CoM velocity/accel
    v_cd, a_cd, standoff distance r_cam, reference frames R_ed, R_bd, reference positions p_cd, and base
    spin omega_bd. For the floor: the reduced matrices (J_x, K_breve, C_breve, C_c, D_breve, G_vc_breve) and
    reference rates v_breve_des, x_c_dot_err.
  • Out: the reduced (⊕) EE twist nu_oplus and its derivative nu_oplus_dot (the feedforward the RHS adds);
    and the quasi-steady reduced pose error x_ss plus its two forcing components (Coriolis, CoM-coupling).

Key methods

  • reduced_desired_twist — closed-form reduced (⊕) EE twist + derivative from the reference — GNC/guidance/analytic_feedforward.py:110
  • unit_vector_derivatives — direction-only derivatives of u = r/|r| via the projector P = I − uuᵀ:7
  • desired_ee_twist_world — world-frame desired twist; roll-free omega_ed = u × u_dot:27
  • world_twist_to_local — rotate the world twist into the desired EE frame (carries one Coriolis term) — :39
  • g_vc_desired / g_omega_b_desired — the COM→EE and base-spin→EE ⊕ maps on the REFERENCE frames — :60 / :76
  • cruise_lag_floor — quasi-steady reduced pose error x_ss from static stiffness balance — :144

Footguns

Pure reference, POSE mode only

The closed form assumes v_des = const, s_ddot = 0, and omega_bd_dot ≈ 0 (base carries a slowly-varying
inward-pointing reference). It is POSE-mode only — in ANCHOR the scorer picks a different aim point, so the
closed form wouldn’t match the selected target and ee_guidance falls back to FD. (GNC/INSIGHTS.md [math])

axis_only zeroes roll BEFORE the ⊕ fold

With axis_only, the local roll rate (component 5) and its derivative are zeroed before the circumcentroidal
fold, mirroring the FD path’s nu_des[5] = 0. Folding first would leak roll into the reduced twist.
(GNC/INSIGHTS.md [math])

The lone Coriolis term in the linear block

Differentiating a body-frame quantity carries d/dt(R_ed^T w) = R_ed^T(w_dot − omega_ed × w). The angular block
is clean (omega_ed × omega_ed = 0); the linear block keeps one Coriolis term −omega_ed × p_ed_dot. Dropping
it is a frame-convention sign footgun. (GNC/INSIGHTS.md [frame])

cruise_lag_floor uses lstsq — filter derated steps downstream

The static balance (J_xᵀ K_breve) x_ss = −forcing is solved with lstsq, so a near-singular J_xᵀ K_breve
yields the minimum-norm balance rather than blowing up. Such derated steps must be filtered out of the
cruise comparison
downstream, or the floor is meaningless there. (GNC/INSIGHTS.md [footgun] [derate])

Pseudocode (reduced_desired_twist)

u, u_dot, u_ddot = unit_vector_derivatives(r, r_dot, r_ddot)   # direction-only, via P = I − uuᵀ
p_ed_dot, p_ed_ddot, omega_ed, omega_ed_dot = desired_ee_twist_world(...)   # z_ed = u ; omega_ed = u × u_dot
nu_local, nu_local_dot = world_twist_to_local(R_ed, ...)        # rotate in; linear block keeps −omega_ed × p_ed_dot
if axis_only: nu_local[5] = nu_local_dot[5] = 0.0               # zero roll BEFORE the fold
G_vc, G_wb (+ dots) on REFERENCE frames (R_ed, R_bd, p_cd, p_ed)
nu_oplus     = nu_local     − G_vc · v_cd       + G_wb · omega_bd          # circumcentroidal fold
nu_oplus_dot = nu_local_dot − (G_vc_dot·v_cd + G_vc·a_cd) + G_wb_dot·omega_bd

Equations & references

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

Aim-direction rate (radial part projected out) — §5.5, eqs (5.8–5.9):

One pair feeds the full 6-DOF twist§5.5, eq (5.14):

Acceleration feedforward into the RHS ( frame) — §5.6, eq (5.18):

References:

  • Analytic desired twist (translational + rotational FF): current_sota > 5 §5.5.
  • Conversion to the circumcentroidal (⊕) frame: current_sota > 5 §5.6.
  • Cruise-lag (steady-state) error floor: current_sota > 4 §4.5 · eq↔code in generated_reports/GNC/cross_check.md.

ee_guidance · target_finder · guidance_classes · com_guidance · base_guidance · breve_controller · current_sota · terminology · ee_feedforward