com_controller — ControlLoop (generic control loop, root)
Purpose
The generic per-step control loop and the root of the control chain: state caches, force assembly
(Γᵀmap), generalized-velocity reconstruction, integration, joint-limit clip, and the uncertainty hook.
Ownsrun_all/run_control_loop; the task-space wrenches are placeholders here, filled in by subclasses.
The CoM force law itself is no longer in this class — it is composed asself.com, aCOMController
living in com_guidance (has-a, not is-a, since Jun25e14ee16). Renamed fromCC_Controller.
Role in the system
- Root of the control chain
ControlLoop → [[breve_core_controller]] → {Base, Breve}: the shared
base-attitude/breve template breve_core_controller (BreveCoreController) extends this class, and
breve_controller (BreveController, live workhorse) + base_controller (BaseController,
base-only diagnostic) extend that (reparented 2026-06-22; the chain was flat before). - Composes the CoM force law:
self.com = COMController(robot, motion_cache, uncertainty, traj, cfg)
built in__init__(GNC/com_controller.py:59), sharing the loop’s caches by reference. The loop
callsself.com.compute_f_c_terms/self.com._update_com_integral_state/self.com.clamp_com_integral;
the manipulator RHS readsself.com.x_c_tilde_dot. See com_guidance for the law itself. - Selected by
controller.name == "com"; the loop body (run_control_loop) is shared by every subclass. - Reference trajectory comes from com_guidance (
self.traj); subclasses swap in the deeper
base_guidance / ee_guidance tower. - Dynamics (
Γ,M̆,C̆,s_min_G,z_a) come fromutils/robot.pyasself.dyn.*/self.motion.*. - The injected-noise / belief-covariance owner is uncertainty_model (
self.uncertainty, no-op when OFF).
Inputs / Outputs
- In:
State(q, v),Desired(CoM goalp_c,v_c,a_c), config gains,s_min_G, the guidance trajectory. - Out: CoM force
f_c; viaΓᵀthe generalized forces(f_b, τ_b, τ); the per-step log payload; the integratedState.
Key methods
run_all— reset guidance, build the initial state, run the loop (optional time cap) —GNC/com_controller.py:345run_control_loop— the per-step closed loop: desired → control → log → integrate → clip → resync —:296all_control_terms— assemble[f_c; τ_b⊕; ω_e⊕]and map throughΓᵀinto(f_b, τ_b, τ)—:141reconstruct_generalized_velocity—(v_c, ω_b, ν_e⊕)→ fullvvia the (Stage C augmentable)Γlstsq —:178(the exact hierarchical override lives in breve_controller:504)compute_tau_b_oplus/compute_omega_e_oplus— task-space wrench placeholders (subclasses override) —:131/:135- CoM force law (composed, in
COMController):compute_f_c_terms(GNC/com_guidance.py:328),feedforward_accel(:318),f_c_fb(:312),x_c_tilde/x_c_tilde_dot(:295/:303),clamp_com_integral(:287) — reached viaself.com.*. See com_guidance.
Footguns
Joint-limit clip kills outward velocity only — and the XML limits are task artifacts
_enforce_arm_joint_limitsclips the joint and zeroes velocity only at an active wall in the outward
direction, so the arm slides back inward freely. Thebox_limitedXML ranges are task artifacts that
nominal operation exceeds constantly (wrist_1 ~75% OOR on the full helix); this clip is what breaks the
elbow lock-in. (GNC/INSIGHTS.md[footgun], CHAIN_5)
Belief side vs truth side of the uncertainty hook
x_c_tilde/x_c_tilde_dotfeed back the ESTIMATED CoM (belief: truth +p_c_offset), while
compute_f_c_termsinjects force noise post-saturation (truth: the actuator realizes a noisy command).
Both are no-ops whenuncertaintyis OFF — keep the two sides distinct. These methods now live in
COMController(com_guidance), reached asself.com.*. (GNC/INSIGHTS.md)
CoM integral is OFF by default
The integral accumulates only when
|x_c_tilde| ≤ com.integral.disable_errorand is anti-wound by
back-off when|f_unsat| > F_max. It is off in the adopted default — the operational error is a
velocity lag, not a steady-state position offset. (GNC/INSIGHTS.md[control])
Stage C null-space suppression
[derate]When
controller.arm.null_space.enable,_augment_with_null_rowappends a unit null-rowẑtoΓ,
forcing the lstsq reconstruction onto thev_n = 0fiber (thed→∞limit ofu_n = −d·v_n). This
suppresses ~150× the ghost null-space injection that plain min-norm lstsq would inject. (GNC/INSIGHTS.md[derate])
Pseudocode (one control step)
des = build_desired_for_step(t) # CoM goal from the guidance trajectory
x_c_int = update_com_integral_state(des) # accumulate only inside disable_error (usually OFF)
all_control_terms(st, des, x_c_int, t):
f_c = ff + fb + i (saturate F_max; + force noise if active)
G = [f_c; τ_b⊕; ω_e⊕] # task wrenches 0 here, filled by subclass
F = Γᵀ G → (f_b, τ_b, τ)
log payload → apply_step_state_update # integrate v_c (and subclass rates)
v = reconstruct_generalized_velocity(st) # Γ lstsq, +v_n=0 fiber row if Stage C
q = pin.integrate(q, v·dt) ; clip arm joints # outward-only velocity kill at a wall
Equations & references
Key equations mirrored from current_sota — the math source of truth; see there for derivations.
Decoupled CoM Newton equation — §3, eq (3.3):
CoM force law (PD on the centroid) — §4.2, eq (4.4):
Closed-loop CoM error (homogeneous damped oscillator) — §4.3, eq (4.9):
References:
- CoM force law (feedforward + PD + integral) and the
Γᵀforce map (§4.x): current_sota > 4. - Singularity / derate stack — Stage C
v_n = 0fiber (§6): current_sota > 6 · eq↔code ingenerated_reports/GNC/cross_check.md.
Related
breve_controller · base_controller · com_guidance · uncertainty_model · terminology · current_sota · com_vs_base · circumcentroidal_control