feat(cam-lower): tangent-aligned orientation law (inert) #117

Merged
patman merged 4 commits from patman/orient-law into patman/angular-units 2026-08-24 02:23:22 +00:00 AGit
Owner

SE(3) S-b. src/orient.rs: OrientationLaw (Fixed/TangentAligned), HeadingTable with curvature-predicted branch unwrapping (detector-not-corrector: |step|<pi enforced, curvature estimate can only refuse, never rewrite) + arc-length-driven sampling, TangentRates/AngularRates typed state, omega=kv alpha=k'v^2+kv. zeta=k\v^3+3k'vv.+kv.. per design 2.1 with time-domain FD validation. Inert: nothing calls it; existing outputs unchanged. Opus review PASS after C1 aliasing fix + R1/R2.

SE(3) S-b. src/orient.rs: OrientationLaw (Fixed/TangentAligned), HeadingTable with curvature-predicted branch unwrapping (detector-not-corrector: |step|<pi enforced, curvature estimate can only refuse, never rewrite) + arc-length-driven sampling, TangentRates/AngularRates typed state, omega=kv alpha=k'v^2+kv. zeta=k\\v^3+3k'vv.+kv.. per design 2.1 with time-domain FD validation. Inert: nothing calls it; existing outputs unchanged. Opus review PASS after C1 aliasing fix + R1/R2.
feat(cam-lower): tangent-aligned orientation law (inert)
Some checks failed
CI / build-test (pull_request) Failing after 4s
2d3de3074c
New src/orient.rs, the data model for the SE(3) angular half. Nothing in
lower.rs calls it yet, so the emitted schedule is unchanged and every
existing test is untouched.

- OrientationLaw { Fixed(Quaternion), TangentAligned { offset } }.
  R(s) = Rz(phi(s) + phi0); Fixed(IDENTITY) is today's behaviour spelled
  out in the new vocabulary.
- angular_state -> AngularRates: omega = kv, alpha = k'v^2 + kv_dot,
  zeta = k''v^3 + 3k'v*v_dot + k*v_ddot, computed in the uom
  dimension-checked currency.
- HeadingTable: unwrapped heading phi(s) over a whole pass, accumulated
  and never wrapped (a closed loop sweeps 2pi). Tabulates the branch
  only; heading_at lifts the caller's exact tangent onto it, so the
  returned angle is exact rather than interpolated.

The unwrap is curvature-checked. Tangents alone cannot distinguish a turn
of 3pi/2 from one of -pi/2, so each step is compared against the
trapezoid estimate (k[i-1] + k[i])/2 * ds. A step is accepted only when
it is under pi in magnitude AND within UNWRAP_TOLERANCE (pi/2) of that
estimate; anything else, including a non-finite sample, is refused via
LowerError::HeadingUnwrapAmbiguous. Inside the accept region the chosen
branch necessarily coincides with the naive nearest-to-zero one, so the
curvature is a detector, not a corrector: its job is to turn silent
aliasing into a loud refusal. The |step| < pi half is what establishes
heading_at's exactness precondition, which the residual check alone does
NOT imply. Coarse grids that cannot serve heading_at (a full circle at
2 or 3 samples, a 270 degree arc at 2) are therefore refused rather than
answered. The residual hole -- curvature concentrated strictly between
endpoint samples -- is stated in the type docs rather than papered over.

Sample density follows arc length (build_default / samples_for, 0.1 mm
spacing clamped to [MIN_SAMPLES, MAX_SAMPLES]) so a long pass keeps the
spacing a short one gets and no interval can straddle a corner blend.
build_default's docs carry its cost profile for the S-e wiring: one
PathEval::eval and one f64 per sample, ~16 MiB and seconds-to-tens-of-
seconds at the MAX_SAMPLES clamp (passes past ~209.7 m), single-threaded
and precompute-side -- build once per pass, never per knot.

Handedness (CCW-positive kappa, left normal, right-handed about +z) is
inherited from the translational half, not re-derived; the conventions
are pinned in the module docs and asserted in tests. The module docs
also spell out that v_dot IS the world tangential acceleration while
v_ddot is NOT the world tangential jerk (that carries an extra -k^2 v^3),
since conflating the two is the obvious way to mis-wire the follow-up.

Tests: closed-form circle/line pins; full-formula hand evaluation plus an
independent time-domain finite-difference re-derivation of alpha and
zeta; body-axis/offset orientation conventions; 2pi sweep on circle,
rounded-rect and long-pass fixtures; route-1 (atan2 unwrap) vs route-2
(integral of kappa) agreement on real blend geometry; dphi/ds == kappa
finite-difference cross-check; heading_at exactness on and between
samples; and refusal pins for coarse grids, large-but-consistent steps,
non-finite inputs, and a swept neighbourhood of near-reversals.

Co-authored-by: patman-assist <patrick-ai@kgroo.co>
chore: normalize comments
Some checks failed
CI / build-test (pull_request) Failing after 5s
7ecbb7ec87
Co-authored-by: patman-assist <patrick-ai@kgroo.co>
style: rustfmt --edition 2024 drift fix
All checks were successful
CI / build-test (pull_request) Successful in 9m2s
b98b0f913a
Reformats files that predate the runner's current rustfmt; no code changes.

Co-authored-by: patman-assist <patrick-ai@kgroo.co>
merge: sync main into orient-law (post track2 merge)
All checks were successful
CI / build-test (pull_request) Successful in 8m48s
05bbb91909
Co-authored-by: patman-assist <patrick-ai@kgroo.co>
patman merged commit e0568ac8f8 into patman/angular-units 2026-08-24 02:23:22 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
patman/KGROO!117
No description provided.