deke-linear 2.0.0

Constant-TCP-speed Cartesian polyline following for serial manipulators.
Documentation
# deke-linear

Constant-TCP-speed Cartesian polyline following for serial manipulators.

Where the `deke-topp*` retimers are **time-optimal** (maximise speed under v/a/j
caps), `deke-linear` holds a **constant TCP travel speed** — the requirement for
welding and similar process motions — and degrades gracefully near singularities
rather than failing. It is a CNC-style constant-feedrate interpolator in three
stages, the latter two of which implement the `deke_types` `Planner` / `Retimer`
traits so they compose with the rest of the ecosystem.

## Pipeline

```
&[DAffine3] poses + FollowConfig
   │
 [A] path::condition   → Vec<CartesianRun>      split at sharp corners; shallow
   │                                            corners smoothed (squiggle Catmull–Rom),
   │                                            arc-length parameterised
 [B] CartesianLinearPlanner : Planner           dense IK → DP branch track
   │                                            (analytic IK, manipulability-weighted,
   │                                            no Jacobian inversion → singularity-safe)
 [C] ConstantSpeedRetimer  : Retimer            constant feedrate held where the joint
   │                                            v/a/j MVC allows; smooth dips elsewhere
 LinearFollower::follow → SRobotTraj            per-run trajectories stitched at rest
```

- **Corners (hybrid).** A vertex whose turn angle exceeds
  `PathConditioning::sharp_corner_angle` is *sharp*: the path splits there into runs
  that start and stop at rest, keeping the vertex exact. Shallower corners are
  smoothed by the Catmull–Rom spline and traversed without stopping.
- **`forbid_interior_dips`.** By default the speed dips smoothly where the joint
  v/a/j geometry forces it (a shallow corner, a near-singular patch). Set this flag
  to require the commanded speed to hold flat through a run's interior — the only
  sub-commanded speed allowed is the rest ramp at each run's start and end. If a dip
  would be forced anywhere in the interior, the retime fails with
  `SpeedDipRequired { run, s, feasible_speed, commanded }` instead of slowing down.
- **Singularity tolerance.** The planner inverts each pose with the chain's analytic
  IK (every branch, limit-filtered, no Jacobian inversion) and routes a continuous
  joint track that maximises manipulability `√det(J·Jᵀ)`. The retimer's
  feasible-speed ceiling is `min_j v_max,j / |q'_j(s)|`, so as a singularity is
  approached the commanded speed dips smoothly to zero instead of demanding infinite
  joint speed.

## Example

```rust
use deke_linear::{LinearFollower, FollowConfig};
use deke_types::glam::DAffine3;

let follower = LinearFollower::new(&chain);            // any ContinuousFKChain + IkSolver
let (traj, diag) = follower.follow(&poses, &cfg)?;     // poses: &[DAffine3], cfg: FollowConfig<N>
```

## Free tool-axis yaw (functional redundancy)

A tool that is rotationally symmetric about one of its axes — a welding torch,
spray head — leaves the rotation about that axis free (a 5-DOF task on a 6-DOF
arm). Declaring it lets the planner spend that DOF to steer around singularities.

```rust
use deke_linear::{FollowConfig, JointLimits, RedundantAxis, RedundantOptions};
use std::time::Duration;

let cfg = FollowConfig::weld(35.0, joint_limits, Duration::from_millis(8))  // 35 in/min
    .with_redundancy(RedundantOptions {
        axis: RedundantAxis::PosZ,                 // ±X/±Y/±Z or Custom(unit vec), tool frame
        yaw_window: (-45f64.to_radians(), 45f64.to_radians()),
        ..RedundantOptions::default()
    });
```

Yaw is a smooth scalar, so it is gridded coarsely and resolved by a **single global
DP** over `(station) × (yaw × branch)` — exact, so it finds the globally optimal yaw
track in one pass. A manipulability node cost steers off singularities, a yaw-rate
edge penalty keeps the spin smooth, and the velocity reconfiguration test rejects
discontinuous edges; the yaw may sweep freely across the window (bounded only by
per-step `max_yaw_step`). That coarse `ψ(s)` schedule is then refined at fine
arc-length spacing, with analytic IK placing the arm exactly each step
(predictor–corrector). The free axis is configurable because tool frames differ
(this project is Z-forward; others are X-forward).

## Reconfiguration by joint velocity

The planner takes a `max_velocity` and per-joint velocity ceilings: any edge that
would drive **any** joint past a configurable fraction (default 90%) of its limit
*at that speed* is treated as a reconfiguration/discontinuity and rejected. At weld
speeds (20–50 IPM) a joint approaching its velocity limit is the signature of a
singularity or wrist flip, so this cleanly separates "needs to slow down" from
"can't be done continuously." `FollowConfig::weld()` wires it up; otherwise set
`PlannerOptions::{max_velocity, joint_v_max, reconfig_vel_fraction}`.

## Scope / notes

- **Orientation** is a full TCP pose per vertex (slerped along the path). For a
  symmetric tool, declare the free axis via `with_redundancy` (see above) to let the
  planner resolve the yaw.
- Joint **velocity** is enforced exactly; **acceleration/jerk** are enforced by the
  path-tangent projection plus a jerk-limited integrator. The `q''(s)·ṡ²` curvature
  cross-term is a deliberate first-pass approximation (negligible at process speeds;
  see the corner tests).
- Geometry runs in `f32` (squiggle); kinematics and timing in `f64`.

## License

Apache-2.0.