tune.tf turns the replay into the joint’s command → position frequency response and fits
H(s) = K (1 + s/ωz) ωn² / (s² + 2ζωn s + ωn²) · e^(−sτ)
— the closed loop’s natural frequency, damping, an optional zero and the delay. tune.motion --invert then streams a known motion through the inverse, cancelling the tracking lag and resonance.
It is the smaller half on slow motion: on slow_osc only 3–9% of the 1–3 Hz shake was a linear response to the command — the rest is friction, calibrated with tune.friction --profile slow. It dominates the error of faster motion.
1. Build the sweep — motion.chirp
The sweep is refused if it would leave the joint’s range.
2. Replay it — tune.motion
--gain / --fast-impedance overrides you run the motion with — the model belongs to that loop.
3. Fit — tune.tf
It prints the response at a few frequencies, the model, a warning when the loop is lightly damped (how much a command component at its resonance is amplified) or when the fitted resonance lies above the band the data covers, and — when the run has the wrist IMU — how coherent the wrist’s vertical motion is with the command per band: where it drops, something the joint encoder does not see is moving the tool.
