Investigation report (report-only; no code modified). Generated
2026-06-16, ~15:45 GMT-4. Companion to
docs/week2/section5_singularities_7dof.md; this note
answers one narrow question for the 6-DOF arm only,
verified against the live code.
As the smallest singular value falls, the controller degrades gracefully through a fixed four-stage cascade, each stage triggered at a different \(\sigma\) threshold and each with a distinct cost:
np.linalg.inv and enters damped least-squares,
bounding the inverse gain.Two signals drive this. The derate and the \(\Gamma\)-solve key on \(s_{\text{min\_G}}=\sigma_{\min}(\Gamma)\);
the \(J^{\oplus}\)-inverse tiers key on
\(s_{\text{min\_J}}=\sigma_{\min}(J^{\oplus})\)
directly (utils/robot.py:184). Because \(\Gamma\) is singular iff \(J^{\oplus}\) is (F4, comment
GNC/breve_controller.py:311-312), as \(s_{\text{min\_J}}\) falls \(s_{\text{min\_G}}\) falls ~1:1, so the
question “what happens as \(s_{\text{min\_J}}\) decreases” has one
clean answer for both.
if self.n > 6:
(utils/robot.py:313); for 6-DOF \(z_a\) is None
(:312), so the augmented null-space solve never fires
(GNC/breve_controller.py:545) and
null_space.enable is irrelevant.section5_singularities_7dof.md.
Divergences from it are in Findings.flowchart TD
H["HEALTHY s_min >= 0.05<br/>exact inverse np.linalg.inv<br/>gamma = 1.0, Gamma-solve undamped"]
A["1. s_min ~ 0.049 — Gamma Tikhonov ON<br/>lambda = max of 1e-4 and 0.05^2 - s_minG^2<br/>damps velocity-reconstruction DIRECTION<br/>cost: demand-direction corruption"]
B["2. s_min = 0.025 — derate gamma leaves 1.0<br/>scales arm gains D,K and base wrench tau_b<br/>vehicle slows as 2nd-order impedance effect<br/>cost: time / schedule lag"]
C["3. s_min = 0.02 — J-plus SOFT floor<br/>exact inverse to damped least-squares<br/>lambda = max of 1e-4 and 0.02 - s_min<br/>cost: bounded but inexact gain"]
D["4. s_min = 0.005 — BOTH floors hit<br/>gamma pinned 0.25 AND J-plus HARD floor<br/>hold last_J_plus_inv, norm guard > 1000 reverts<br/>cost: stale-but-valid map, not exact kinematics"]
E["s_min < 0.005<br/>gamma = 0.25 saturated, J-plus map frozen"]
X["EXCLUDED 6-DOF: no kernel freeze / null-space<br/>z_a is None, guarded by if n > 6"]
H --> A --> B --> C --> D --> E
B -.->|"7-DOF only: kernel freeze also engages here"| X
classDef external fill:#fff3cd,stroke:#d39e00,color:#3d2b00;
class X external;
| \(\sigma_{\min}\) band | Derate \(\gamma\) | \(\Gamma\)-solve | \(J^{\oplus}\) inverse |
|---|---|---|---|
| \(\ge 0.05\) | 1.0 | undamped (\(\lambda=10^{-4}\)) | exact inv |
| \([0.025,\ 0.05)\) | 1.0 | Tikhonov damped | exact inv |
| \([0.02,\ 0.025)\) | ramping \(<1\) | Tikhonov damped | exact inv |
| \([0.005,\ 0.02)\) | ramping \(\to 0.25\) | Tikhonov damped | damped least-squares |
| \(< 0.005\) | 0.25 (floor) | Tikhonov damped + clip | hold last-healthy |
The \([0.025,\ 0.05)\) row is the ordering inversion (Finding F2): direction is already damped while speed is still full.
reconstruct_generalized_velocity
(GNC/breve_controller.py:541-559). Damping \(\lambda=\max(10^{-4},\
\varepsilon_\Gamma^2-s_{\text{min\_G}}^2)\) with \(\varepsilon_\Gamma=0.05\)
(parameters.yaml:84); solves \((\Gamma^{\mathsf T}\Gamma+\lambda
I)^{-1}\Gamma^{\mathsf T}y\), then a constant
clip \(|v|\le v_{\max}=50\)
(:554-556, parameters.yaml:82).conditioning_scale
(GNC/breve_controller.py:313-316) →
scale_by_svd (utils/sampling.py:70-78): floor
\(0.25\), knees \([0.005,\ 0.025]\)
(parameters.yaml:86-91). Applied to the arm
gains via arm_gain_scale (:319-322)
and the base wrench \(\tau_b\) in
compute_tau_b_oplus (:333).damped_inverse +
regularized_svd (utils/robot.py:173-210): soft
floor \(0.02\), hard floor \(0.005\), DLS \(\lambda=\max(10^{-4},\ 0.02-\sigma)\),
square-\(6\times6\) exact
np.linalg.inv path (:189-190), norm guard
inv_norm_max=1000 (:196-199).all_dynamics_terms
(utils/robot.py:243, 255); both are logged
(GNC/breve_controller.py:564-583), \(s_{\text{min\_G}}\) is the live derate
driver.section5_singularities_7dof.md)F1 — The derate scales impedance, not velocity.
Evidence: \(\gamma\)
multiplies the arm gain blocks
(breve_controller.py:319-322) and the base wrench \(\tau_b\) (:333); no site
multiplies \(v_c\), \(\omega_b\), or \(\nu_e^{\oplus}\) by \(\gamma\). The only hard velocity bound is
the constant \(v_{\max}=50\) clip in the \(\Gamma\)-solve (:554-556),
which is not \(\gamma\)-scaled.
Impact: section5 Delta-1 reads as if \(\gamma\) scales the velocities directly; in
the live code the vehicle slows as a second-order
consequence of softened impedance. The descriptive claim should
be corrected, the numbers are unaffected. Next step: reword
section5 Delta-1 (“scales the impedance gains and base wrench” not “the
velocities”).
F2 — Ordering inversion in \([0.025,\ 0.05)\) is live.
Evidence: \(\Gamma\)-Tikhonov
engages at \(\sigma\approx0.049\)
(:551) but the derate does not leave \(\gamma=1\) until \(\sigma=0.025\)
(parameters.yaml:90). Impact: in that band the
controller corrupts the demand direction (the more
lossy tool) while speed is still full — the opposite of the published
cost hierarchy (speed-scaling before damping). Next step: the
candidate fix (section5 §6) re-harmonizes \(\varepsilon_\Gamma\) from \(0.05\) to \(0.025\) so the two onsets coincide;
investigate-only, no change made here.
F3 — The integral-freeze gate is dormant in the nominal
config. Evidence: update_x_e_integral
freezes the EE integral when \(\gamma<\text{scale\_gate}=0.03\)
(breve_controller.py:243-245), but
arm.integral.enable=false by default
(parameters.yaml:111). Impact: a real but inert
code path for 6-DOF as shipped; a footnote, not a cascade stage.
Next step: none unless the integral is enabled.
(Line numbers in section5 have drifted —
e.g. conditioning_scale 302-305 → 313-316,
reconstruct_generalized_velocity 507-516 → 541-559 — but
every threshold value in section5 still matches the
live code: floor 0.25, knees 0.005/0.025, \(\varepsilon_\Gamma\) 0.05, soft 0.02, hard
0.005.)
reconstruct_generalized_velocity,
threshold_derivation.md §6. Validation:
matched A/B at \(\varepsilon_\Gamma\in\{0.05,\
0.025\}\), compare \(p_e\) in
\([0.025,0.05)\).section5_singularities_7dof.md (user-owned).
Validation: re-read against
breve_controller.py:319-333.