/* 局部可观测时间律(2026-10-17,方案 §4.5) * 折线按"巡航时间" τ 参数化:每段 τ = max_j |Δq_j| / VMAX_j(各轴同步、主导轴满速)。 * 归一化速度 v∈[0,1](1 = 巡航):v ← min(1, v + A·dt, sqrt(2·A·剩余 τ)),只依赖当前速度与剩余路程。 * 输出 50 Hz(dt = 20 ms)状态序列,末点精确落在终点。 */ 'use strict'; const VMAX = [60, 60, 90, 90, 120, 180]; // °/s,与 tests/route_bench.cjs 同(轴速比 0.6/0.6/0.9/0.9/1.2/1.8) const A = 5.0; // 1/s²:0→巡航约 0.2 s(a_max 待真机参数) const DT = 0.02; function timeParam(P, opt) { opt = opt || {}; const vmax = opt.vmax || VMAX, a = opt.acc || A, dt = opt.dt || DT; const segT = []; for (let i = 1; i < P.length; i++) { let m = 0; for (let j = 0; j < 6; j++) m = Math.max(m, Math.abs(P[i][j] - P[i - 1][j]) / vmax[j]); segT.push(m); } const cum = [0]; for (const s of segT) cum.push(cum[cum.length - 1] + s); const total = cum[cum.length - 1]; const at = tau => { if (tau >= total) return P[P.length - 1].slice(); let i = 0; while (i < segT.length - 1 && cum[i + 1] < tau) i++; const u = segT[i] > 0 ? (tau - cum[i]) / segT[i] : 1; return P[i].map((v, j) => v + (P[i + 1][j] - v) * u); }; const out = [P[0].slice()], taus = [0]; let tau = 0, v = 0, guard = 0; while (tau < total - 1e-9 && guard++ < 100000) { const rem = total - tau; const v1 = Math.min(1, v + a * dt, Math.sqrt(2 * a * Math.max(0, rem - 0.5 * v * dt)) + 1e-9); let step = 0.5 * (v + v1) * dt; if (step <= 1e-9) step = Math.min(rem, a * dt * dt); // 防止末端速度掉到 0 卡住 tau = Math.min(total, tau + step); v = v1; out.push(at(tau)); taus.push(tau); } return { traj: out, taus, cum, total, steps: out.length - 1 }; } module.exports = { timeParam, VMAX, A, DT };