mod common;
use deke_linear::{JointLimits, TcpLimits};
use deke_types::glam::DVec3;
#[test]
fn captured_short_weld_is_within_jerk_or_refused() {
let robot = common::ur();
let dir = DVec3::new(
-0.204_193_764_827_666_16,
-0.926_076_600_752_295_5,
0.317_312_205_791_988_1,
);
let jl = 16.696_939_610_631_677;
let poses = common::straight(&robot, dir, 0.118_001_859_153_488, 7);
let mut cfg = common::config(0.119_636_760_917_933_6);
cfg.constraints.joint =
JointLimits::symmetric(0.364_662_007_797_896_65, 2.343_016_777_374_244_5, jl);
match common::follow(&robot, &poses, &cfg, &common::noop(), &()) {
Ok((traj, _)) => {
let dt = traj.dt().as_secs_f64();
let p = traj.path();
let mut fd_j = 0.0f64;
for i in 3..p.len() {
for j in 0..6 {
fd_j = fd_j.max(
(p[i].0[j] - 3.0 * p[i - 1].0[j] + 3.0 * p[i - 2].0[j] - p[i - 3].0[j])
.abs()
/ (dt * dt * dt),
);
}
}
assert!(
fd_j <= jl,
"returned trajectory FD jerk {fd_j:.2} exceeds limit {jl:.2}"
);
}
Err(e) => {
assert!(
format!("{e}").contains("jerk"),
"expected a jerk-limit refusal, got: {e}"
);
}
}
}
#[test]
fn fuzz_tcp_stays_on_commanded_line() {
let robot = common::ur();
let mut rng = common::Rng::new(0x5EED_0001);
let mut cases = 0;
for _ in 0..80 {
let dir = rng.unit_dir();
let len = rng.range(0.05, 0.20);
let n = rng.int(2, 8);
let speed = rng.range(0.01, 0.06);
let poses = common::straight(&robot, dir, len, n);
let Ok((traj, _)) =
common::follow(&robot, &poses, &common::config(speed), &common::noop(), &())
else {
continue;
};
cases += 1;
let dev = common::tcp_polyline_deviation(&robot, &traj, &poses);
assert!(
dev < 1e-3,
"TCP deviation {dev:.6} m — dir={dir:?} len={len:.3} n={n} speed={speed:.4}",
);
}
assert!(
cases > 30,
"too few reachable cases ({cases}) — generator drifted"
);
}
#[test]
fn fuzz_feasible_motion_never_exceeds_joint_limits() {
let robot = common::ur();
let mut rng = common::Rng::new(0x5EED_0002);
let mut cases = 0;
for _ in 0..80 {
let (vl, al, jl) = (
rng.range(0.4, 1.6),
rng.range(1.5, 6.0),
rng.range(15.0, 60.0),
);
let dir = rng.unit_dir();
let len = rng.range(0.05, 0.20);
let n = rng.int(2, 8);
let speed = rng.range(0.02, 0.10);
let poses = common::straight(&robot, dir, len, n);
let mut cfg = common::config_flag(speed, true);
cfg.constraints.joint = JointLimits::symmetric(vl, al, jl);
let Ok((traj, out)) = common::follow(&robot, &poses, &cfg, &common::noop(), &()) else {
continue;
};
cases += 1;
let fd_v = common::joint_vel_peak(&traj);
assert!(
fd_v <= vl * (1.0 + 1e-6),
"joint velocity {fd_v:.4} > limit {vl:.4} — dir={dir:?} len={len:.3} speed={speed:.4}",
);
for r in &out.retimer {
assert!(
r.peak_joint_accel <= al,
"continuous joint accel {:.4} > limit {al:.4} — dir={dir:?} len={len:.3}",
r.peak_joint_accel,
);
assert!(
r.peak_joint_jerk <= jl,
"continuous joint jerk {:.4} > limit {jl:.4} — dir={dir:?} len={len:.3}",
r.peak_joint_jerk,
);
}
}
assert!(cases > 5, "too few feasible cases ({cases})");
}
#[test]
fn fuzz_returned_output_respects_all_fd_limits() {
let robot = common::ur();
let mut rng = common::Rng::new(0x5EED_0010);
let (mut ok, mut err) = (0, 0);
for _ in 0..160 {
let (vl, al, jl) = (
rng.range(0.3, 2.0),
rng.range(1.0, 8.0),
rng.range(10.0, 80.0),
);
let dir = rng.unit_dir();
let len = rng.range(0.05, 0.25);
let n = rng.int(2, 9);
let speed = rng.range(0.008, 0.12); let forbid = rng.unit() < 0.25;
let tcp = if rng.unit() < 0.5 {
Some((rng.range(0.01, 0.2), rng.range(0.3, 5.0)))
} else {
None
};
let poses = common::straight(&robot, dir, len, n);
let mut cfg = common::config_flag(speed, forbid);
cfg.constraints.joint = JointLimits::symmetric(vl, al, jl);
if let Some((a, j)) = tcp {
cfg.constraints.tcp = TcpLimits::new(speed, a, j);
}
match common::follow(&robot, &poses, &cfg, &common::noop(), &()) {
Ok((traj, _)) => {
ok += 1;
let (fv, fa, fj) = (
common::joint_vel_peak(&traj),
common::joint_acc_peak(&traj),
common::joint_jerk_peak(&traj),
);
assert!(fv <= vl * (1.0 + 1e-6), "FD joint vel {fv:.4} > {vl:.4}");
assert!(fa <= al * (1.0 + 1e-6), "FD joint accel {fa:.4} > {al:.4}");
assert!(
fj <= jl * (1.0 + 1e-6),
"FD joint jerk {fj:.3} > {jl:.3} (speed={speed:.4} len={len:.3} n={n})",
);
let pk = common::tcp_speeds(&robot, &traj)
.iter()
.cloned()
.fold(0.0, f64::max);
assert!(
pk <= speed * (1.0 + 1e-3),
"TCP speed {pk:.5} > commanded {speed:.5}"
);
if let Some((a, j)) = tcp {
let (ta, tj) = common::tcp_accel_jerk_peak(&robot, &traj);
assert!(ta <= a * (1.0 + 1e-6), "TCP accel {ta:.4} > cap {a:.4}");
assert!(tj <= j * (1.0 + 1e-6), "TCP jerk {tj:.4} > cap {j:.4}");
}
}
Err(_) => err += 1,
}
}
assert!(ok > 90, "solver over-refusing: only {ok} ok / {err} err");
}
#[test]
fn fuzz_shallow_corners_respect_fd_limits() {
let robot = common::ur();
let mut rng = common::Rng::new(0x5EED_0011);
let mut ok = 0;
for _ in 0..100 {
let leg = rng.range(0.04, 0.12);
let turn = rng.range(-0.4, 0.4); let per = rng.int(3, 6);
let speed = rng.range(0.01, 0.08);
let (vl, al, jl) = (
rng.range(0.4, 2.0),
rng.range(1.5, 8.0),
rng.range(15.0, 80.0),
);
let poses = common::corner(&robot, leg, turn, per);
let mut cfg = common::config(speed);
cfg.constraints.joint = JointLimits::symmetric(vl, al, jl);
let Ok((traj, out)) = common::follow(&robot, &poses, &cfg, &common::noop(), &()) else {
continue;
};
if out.runs != 1 {
continue; }
ok += 1;
assert!(
common::joint_vel_peak(&traj) <= vl * (1.0 + 1e-6),
"corner FD vel over limit"
);
assert!(
common::joint_acc_peak(&traj) <= al * (1.0 + 1e-6),
"corner FD accel over limit"
);
assert!(
common::joint_jerk_peak(&traj) <= jl * (1.0 + 1e-6),
"corner FD jerk over limit"
);
}
assert!(ok > 20, "too few single-run corners ({ok})");
}
#[test]
fn fuzz_constant_speed_during_cruise() {
let robot = common::ur();
let mut rng = common::Rng::new(0x5EED_0003);
let mut cases = 0;
for _ in 0..80 {
let (vl, al, jl) = (
rng.range(0.5, 1.6),
rng.range(2.0, 6.0),
rng.range(20.0, 60.0),
);
let dir = rng.unit_dir();
let len = rng.range(0.06, 0.20);
let n = rng.int(2, 6);
let speed = rng.range(0.02, 0.08);
let poses = common::straight(&robot, dir, len, n);
let mut cfg = common::config_flag(speed, true);
cfg.constraints.joint = JointLimits::symmetric(vl, al, jl);
let Ok((traj, _)) = common::follow(&robot, &poses, &cfg, &common::noop(), &()) else {
continue;
};
let speeds = common::tcp_speeds(&robot, &traj);
let peak = speeds.iter().cloned().fold(0.0, f64::max);
if peak < speed * 0.99 {
continue; }
cases += 1;
assert!(
peak <= speed * (1.0 + 1e-3),
"TCP speed overshoot: peak {peak:.5} vs commanded {speed:.5}",
);
let thr = speed * 0.98;
let first = speeds.iter().position(|&v| v >= thr).unwrap();
let last = speeds.iter().rposition(|&v| v >= thr).unwrap();
let notch = speeds[first..=last]
.iter()
.cloned()
.fold(f64::INFINITY, f64::min);
assert!(
notch >= speed * 0.95,
"cruise dipped to {notch:.5} (commanded {speed:.5}) — dir={dir:?} len={len:.3} n={n}",
);
}
assert!(cases > 5, "too few cruising cases ({cases})");
}