Synthetic vehicle paths that respect a speed cap but ignore how fast that speed is allowed to change produce trajectories that teleport between velocities, and the fix is to bound acceleration, deceleration, jerk, and turn rate against distributions measured from real telemetry.
Part of Physics-Based Path Generation: that stage builds trajectories from an explicit motion model rather than pure interpolation, and this page focuses on the one calibration step that most often lets an unrealistic path slip through — the kinematic envelope that constrains not just where a vehicle is but how violently its motion state may transition between samples.
A speed-limited path generator enforces ∣vt∣≤vmax at every sample but says nothing about the derivatives of velocity. The result is a trajectory that is valid position-by-position yet impossible frame-to-frame: it jumps from 5 m/s to 25 m/s across a single 1 s tick, implying an acceleration no road vehicle can produce, and it snaps between headings implying an instantaneous lateral force. Downstream models trained on such data learn a physics that does not exist, and any evaluator that reconstructs acceleration will flag the batch.
Acceleration is the first time-derivative of velocity and jerk is the second:
Real drivers keep jerk small because passengers feel it directly — comfortable longitudinal jerk stays under roughly 0.9 m/s³ and emergency maneuvers rarely exceed 2 m/s³. A generator that bounds only at still produces square-wave acceleration profiles: the acceleration is legal at every instant but switches sign discontinuously, which is a jerk spike of unbounded magnitude. Lateral motion has the same trap — a heading change Δψ over Δt implies a turn rate ω=Δψ/Δt and a lateral acceleration alat=vω that must also stay inside the friction ellipse. Calibration means fitting all four bounds — acceleration, deceleration, jerk, turn rate — to reference distributions rather than guessing round numbers, then rejecting or repairing any generated segment that leaves the envelope. Because the noise stage layered on later by adding realistic GPS noise to synthetic vehicle trajectories will itself perturb finite-difference derivatives, the clean path must sit comfortably inside the bound, not on its edge.
A speed cap alone permits square-wave acceleration and instantaneous turns; calibrated acceleration, jerk, and turn-rate bounds keep every state transition inside a physically feasible envelope.
Before calibrating anything, confirm the failure exists by reconstructing acceleration and jerk from a generated batch and comparing their tails to a reference set. Pin the toolchain so finite-difference results are identical across machines.
A jerk_p99 an order of magnitude above the reference is the signature of square-wave acceleration. Coordinates must be metric — reconstruct derivatives in a UTM zone or EPSG:6933, never in raw EPSG:4326 degrees, or the m/s² units are meaningless.
Calibration is two moves: estimate each bound from reference telemetry as a high percentile (not the max, which is dominated by GPS artifacts), then enforce them during generation by clamping the change in each motion-state variable per tick rather than the variable itself.
Clamp acceleration toward its target so the jerk implied by the change stays under j_max, then integrate. This is the step that eliminates the square wave: acceleration can no longer switch sign discontinuously because its own rate of change is bounded.
python
import numpy as np
defrate_limit(target_speed: np.ndarray, dt:float, env:dict,
v0:float=0.0)-> np.ndarray:"""Produce a speed profile that honours a_max, d_max and j_max."""
v = np.empty_like(target_speed, dtype=float)
v[0], a_prev = v0,0.0for t inrange(1,len(target_speed)):
want = target_speed[t]
a_des =(want - v[t -1])/ dt
a_des = np.clip(a_des,-env["d_max"], env["a_max"])# accel / brake caps# limit change in acceleration so |jerk| <= j_max
dj = np.clip(a_des - a_prev,-env["j_max"]* dt, env["j_max"]* dt)
a = a_prev + dj
v[t]=max(0.0, v[t -1]+ a * dt)# no negative speed
a_prev = a
return v
deflimit_turn_rate(heading: np.ndarray, dt:float, omega_max:float)-> np.ndarray:"""Slew-limit heading so per-tick turn rate stays feasible."""
out = np.copy(heading)for t inrange(1,len(heading)):
step = np.clip(heading[t]- out[t -1],-omega_max * dt, omega_max * dt)
out[t]= out[t -1]+ step
return out
Apply limit_turn_rate in concert with the speed limiter so lateral acceleration alat=vω stays inside the friction ellipse — at high speed the turn-rate cap must tighten, which you can enforce by scaling omega_max by min(1, a_lat_max / (v * omega_max)). Keeping the geometry feasible here is what lets preventing trajectory self-intersection in physics paths work with a smooth curve rather than fighting kinks the motion model injected.
Wire the envelope as a pytest gate so a regenerated batch cannot regress into teleporting motion. Give the gate a small slack over the fitted bound to absorb finite-difference noise from the downstream perturbation stage.
python
import numpy as np
deftest_batch_inside_kinematic_envelope(batch, dt, env, slack=1.10):"""batch: iterable of (N,2) metric paths. Fails if any derivative tail exceeds bound."""
worst ={"accel":0.0,"jerk":0.0,"turn":0.0}for xy in batch:
v = np.diff(xy, axis=0)/ dt
speed = np.linalg.norm(v, axis=1)
a = np.diff(speed)/ dt
j = np.diff(a)/ dt
h = np.unwrap(np.arctan2(v[:,1], v[:,0]))
w = np.diff(h)/ dt
worst["accel"]=max(worst["accel"], np.abs(a).max(initial=0.0))
worst["jerk"]=max(worst["jerk"], np.abs(j).max(initial=0.0))
worst["turn"]=max(worst["turn"], np.abs(w).max(initial=0.0))assert worst["jerk"]<= env["j_max"]* slack,f"jerk {worst['jerk']:.2f} > gate"assert worst["accel"]<=max(env["a_max"], env["d_max"])* slack
assert worst["turn"]<= env["omega_max"]* slack
The jerk assertion is the load-bearing one: acceleration and turn-rate caps are easy to satisfy trivially, but a batch that passes the jerk gate cannot contain the square-wave artifact that a speed cap alone permits. Run the same gate against a held-out reference batch to confirm the bounds are not so tight that real maneuvers would fail.
Low sample rate hides jerk violations. At dt = 5 s a genuine 1.8 m/s³ jerk is smeared into a single acceleration step and the finite-difference reconstruction underreports it, so the gate passes on a coarse trajectory that is actually infeasible. Calibrate and gate at the finest available tick, and if you must downsample, do it after the rate limiter so the enforced profile is preserved rather than aliased away.
Heading wrap at the antimeridian and at rest.arctan2 returns values in (−π,π], so a vehicle crossing due west flips heading by 2π and the naive turn-rate difference reports an impossible slew. Always np.unwrap the heading series before differencing. Separately, when speed is near zero the heading is numerically undefined — gate turn rate only on samples above a small speed floor, or a parked vehicle’s GPS jitter manufactures phantom turns.
Braking is not symmetric with acceleration. A single a_max used for both throttle and brake produces vehicles that accelerate as hard as they stop, which no reference distribution supports. Fit d_max separately — deceleration percentiles run roughly twice the acceleration percentiles for passenger cars — and clamp the two directions independently as the reproducer does.
Should I use the maximum observed acceleration or a percentile as the bound?
Use a high percentile such as the 99.5th, not the maximum. Reference telemetry maxima are dominated by GPS position spikes and dropped samples that imply superhuman accelerations, so fitting to them makes the envelope useless. A high percentile captures genuine hard maneuvers while discarding sensor artifacts, and you can validate the choice by confirming a held-out real batch passes the resulting gate.
Why bound jerk at all if acceleration is already capped?
An acceleration cap permits acceleration to jump between its extremes on consecutive ticks, which is a square-wave profile with unbounded jerk at every corner. Passengers feel jerk directly and real drivers keep it small, so a jerk bound is what forces the smooth ramp-in and ramp-out that distinguishes a plausible speed profile from a legal-but-impossible one. It is also the single most discriminating signal an evaluator can reconstruct from position data.
Do acceleration limits need to change with speed?
Yes for lateral acceleration and often for longitudinal. Turn rate must tighten as speed rises so lateral acceleration stays inside the tire friction ellipse, otherwise a feasible low-speed turn becomes an impossible high-speed one. Longitudinal acceleration also falls at high speed because available engine force drops, so a speed-dependent a_max fit from binned reference data is more realistic than a single constant.
How does GPS noise added later interact with these bounds?
Perturbing positions inflates finite-difference derivatives, so a clean path sitting exactly on the jerk bound will fail the gate once noise is applied. Keep the calibrated path comfortably inside the envelope and give the CI gate a small slack factor over the fitted bound to absorb that inflation. Order matters: enforce the kinematic envelope on the clean path first, then add noise, then gate with slack.