Generated: 2026-06-23 (EDT) · Run:
logs/logs_Jun23_26/full_control_floor_an/run/npz/run_PM0523/full_control_floor_an.npz
(analytic FF on, log_cruise_lag_floor on) Evidence
script: validation/nu_e_orthogonality.py ·
Window: non-derated operational (_op,
n=9066 steps) Question: Is the large logged EE-twist
error nu_e (actual vs desired) a coordinate/sign artifact
(⊕ frame vs local), or a real direction-tracking deficiency? Which DOF —
linear or angular?
nu_e pair is
both EE-local: actual
motion.nu_e = twist("cam","local")
(utils/robot.py:162) and desired
des.nu_e = R_te.T·(world velocity) in the
same actual EE frame R_te
(GNC/guidance/ee_guidance.py:241,242,249). The controller’s
⊕-frame error (nu_e_oplus_tilde) is a different
quantity and is not what the nu_e metric
logs.v_x (0.716 vs 0.629) and less v_y
(0.440 vs 0.591). v_z is negligible.w_z is a structural zero, not a
defect: desired w_z ≡ 0 because
axis_only_attitude zeroes it
(ee_guidance.py:252), while the plant rolls the wrist at
~0.053 — a small, expected, fully-explained contribution.desired.nu_e is pure feedforward
(reference velocity); actual.nu_e is the
closed-loop EE velocity. A heading offset is therefore
partly the expected response to the standing p_e
lag, not proof of an independent controller fault. The deciding
experiment is in Proposed Next Tasks.| DOF | |actual| | |desired| | |error| |
|---|---|---|---|
| v_x | 0.7162 | 0.6286 | 0.3938 |
| v_y | 0.4401 | 0.5905 | 0.4260 |
| v_z | 0.0514 | 0.0296 | 0.0213 |
| w_x | 0.0973 | 0.1265 | 0.0910 |
| w_y | 0.1579 | 0.1345 | 0.0868 |
| w_z | 0.0526 | 0.0000 | 0.0526 |
| block | ‖actual‖ | ‖desired‖ | ‖error‖ | angle (deg) |
|---|---|---|---|---|
| full (6) | 0.8698 | 0.8826 | 0.7915 | 53.67 |
| linear (0:3) | 0.8475 | 0.8630 | 0.7718 | 53.60 |
| angular (3:6) | 0.1941 | 0.1848 | 0.1767 | 54.59 |
Self-consistency: law of cosines on the linear block,
cos θ = (0.847² + 0.863² − 0.772²)/(2·0.847·0.863) = 0.59 → 53.7°,
matches the directly-computed 53.60°.
GNC/breve_controller.py,
GNC/guidance/ee_guidance.py, utils/robot.py,
utils/data_classes.py,
analysis/data_analyzer.py,
YAMLs_by_domain/metrics.yaml,
validation/signal_measurement.py.velocity_ff (falsified-inert Jun 12;
standing instruction not to touch it).actual_desired_values / metric_signal),
windowed by the floor report’s _op mask — no hand-rolled
metric.The nu_e metric is a paired (actual, desired) tracking
error. The two channels are produced on different code paths but
expressed in the same EE-local frame, then logged
together and reduced by the catalog as a Euclidean
ERROR.
flowchart TD
subgraph ACTUAL [actual channel — plant]
A1["robot.py:162 nu_e = twist('cam','local')"] --> A2["data_classes.py:237 KinematicsCache.nu_e = motion.nu_e"]
end
subgraph DESIRED [desired channel — guidance feedforward]
D1["ee_guidance.py:238 v_des_world = (p_now - p_prev)/dt"] --> D2["ee_guidance.py:242/249 nu_des = R_te.T @ world (same R_te)"]
D2 --> D3["ee_guidance.py:252 axis_only -> nu_des[w_z]=0"]
end
A2 --> P["data_classes.py:280 pair: nu_e=(actual.nu_e, desired.nu_e)"]
D3 --> P
P --> C["metrics.yaml:96-99 variable: nu_e (err-default, log_desired)"]
C --> R["data_analyzer.py:1041/1048 ERROR = actual - desired"]
R --> M["signal_measurement.series('nu_e') -> ||error|| per step"]
OPLUS["breve_controller.py:580 nu_e_oplus_tilde (⊕-frame)\nNOT logged as nu_e; metrics.yaml:281-284 include:false"] -.separate quantity.-> P
Inconsistency the diagram exposes: the dashed
⊕-frame error is the one the damping law actually drives toward zero,
yet it is not the nu_e the metric reports. So “the
controller tracks ⊕-twist” and “logged local nu_e error is
large” are not contradictory — they are two different numbers.
Robot motion build —
utils/robot.py:162
nu_e = self.twist("cam","local"). The actual EE twist in
the EE-local frame R_te (line 160–161 uses the
lwa twist only to get the world-frame point velocity
p_e_dot; nu_e itself stays local).EEGuidance.desired_nu_e_feedforward_consistent
— ee_guidance.py:225. FD reference velocity:
v_des_world=(p_now-p_prev)/dt (238) →
nu_des[:3]=R_te.T@v_des_world (242),
nu_des[3:]=R_te.T@ω_des_world (249), with R_te
= actual EE frame (241). axis_only zeroes
w_z (252). Stores des.nu_e (260).KinematicsCache.update —
data_classes.py:237 copies motion.nu_e into
the actual cache that feeds logging.StepLog.pair —
data_classes.py:266,280 groups
nu_e=(actual.nu_e, desired.nu_e) for the tracking-error
log.metrics.yaml:96-99
(variable: nu_e, inherits err-default at
25–36: log_desired: true,
pointing: false).SignalAnalysis.actual_desired_values /
paired_metric_signal —
data_analyzer.py:1041,1048. Pairs the logged tuple and
forms ERROR = actual − desired (non-pointing).BreveController.nu_e_oplus_tilde —
breve_controller.py:580, via
nu_e_local_to_oplus (555). The ⊕-frame velocity error used
by the damping RHS; logged only as nu_e_oplus_des
(metrics.yaml:281-284, include:false),
not as nu_e.F1 — Frame/sign-artifact hypothesis is falsified
(structural). Evidence: actual and desired
nu_e are both R_te.T·(world) in the same
actual EE frame (robot.py:162;
ee_guidance.py:241,242,249); the pairing is
(actual.nu_e, desired.nu_e)
(data_classes.py:280); the ⊕-frame error is a distinct,
non-nu_e quantity (breve_controller.py:580;
metrics.yaml:281-284). Impact: the large
nu_e error is a genuine physical residual, not a coordinate
clash. Next step: none for the frame question — closed.
F2 — The residual is a ~54° direction offset with matched
magnitude, linear-dominated. Evidence:
validation/nu_e_orthogonality.py on run_PM0523 (n_op=9066):
linear ‖actual‖ 0.847 ≈ ‖desired‖ 0.863, angle 53.6°; angular block 5×
smaller. Law-of-cosines cross-check matches. Impact: the fix is
not a speed/gain on the desired direction (that’s the magnitude axis,
already matched) — it is the heading of the EE
velocity. Confirms velocity_ff (magnitude-only, inert) is
not the lever. Next step: identify what rotates the achieved EE
velocity off the feedforward heading (F4).
F3 — “Nearly orthogonal” was an over-characterization;
correct it in the record. Evidence: direct cosine =
53–55°; the prior CLAIM’s own norms (0.79/0.88/0.89) imply 64°, not 90°.
Impact: prevents a downstream chase of a non-existent
orthogonality. Next step: CLAIMS.md +
GNC/INSIGHTS.md:62 corrected (done with this report).
F4 — Open question: is the offset feedback (symptom) or
feedforward infidelity (cause)? (inference) Evidence:
desired.nu_e is reference-only feedforward
(ee_guidance.py:238); actual.nu_e is
closed-loop. A heading offset is the expected closed-loop response to
the standing p_e≈0.107 m lag. Impact: decides the
lever. If the offset is the feedback correction toward the lagged
target, the upstream cause is the position lag itself (and the
cruise-lag floor premise v̆=v̆_des is violated
because feedback is active) — not a twist-direction defect to
“fix.” Next step: the distinguishing experiment in Proposed
Next Tasks.
BODY: Form
Δ = actual.nu_e − desired.nu_e (linear block) per step over
_op, and the EE-frame position-error direction
R_te.T·(p_e_actual − p_e_des). Compute the per-step angle
between Δ and that position-error direction (and
correlate/lead_lag of ‖Δ‖ vs
p_e). If Δ points along the position error
(small angle, positive correlation), the ~54° offset is the feedback
closing the lag → nu_e error is a symptom of
p_e, not an independent cause.
Modify: none. Inspect-not-modify:
breve_controller.py (EE damping RHS),
ee_guidance.py. Create: extend
validation/nu_e_orthogonality.py (add the
Δ-vs-p_e-direction panel).
Validation: angle distribution + Pearson/Cliff’s-delta
via the signal_measurement facade; no sim re-run.
BODY: Establish whether the EE has explicit
position-proportional feedback or tracks position only through the
velocity (⊕-twist) loop + pose schedule. The F2 signature (right speed,
wrong heading) is the fingerprint of a velocity-loop-only architecture
with a standing along-track/cross-track lag. Map the lever candidates
(EE position stiffness vs feedforward heading fidelity) to code,
excluding velocity_ff.
Modify: none (report-only).
Inspect-not-modify: breve_controller.py
(all_control_terms, RHS),
GNC/equations/current_sota.md §3–5.
Create: a short follow-up section here or a new dated
report. Validation: trace + cite; propose the eventual
A/B as a separate run-spec.
validation/nu_e_orthogonality.py (canonical
actual_desired_values/metric_signal,
_op window reused from
validation/analytic_ff_floor_report.py).utils/robot.py:162,
GNC/guidance/ee_guidance.py:225-261,
utils/data_classes.py:237,266-290,
analysis/data_analyzer.py:1041-1052,
YAMLs_by_domain/metrics.yaml:25-36,96-99,281-284,
GNC/breve_controller.py:555,580.GNC/INSIGHTS.md:62,119; CLAIMS Jun 23
(cruise-lag floor 2.46× above eq 4.14). ```