Skip to main content
The linear half of motion calibration — what Reforge’s per-axis sine sweeps measure. A chirp excites one joint through the production controller; 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

Add the same --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.

4. Use it — tune.motion --invert

Pre-compensates every modelled, moving, unheld joint of the streamed motion (frequency domain over the whole trajectory, inverse gain capped at 3× and faded to 1 above the identified band, correction tapered in and out over 0.5 s so the stream still starts and ends on the motion). Scored against the clean reference. A joint whose stored gains differ from the run’s is warned about; one whose model’s resonance was extrapolated is skipped.