--motion NAME | Committed motion name (axol motion.list) or a path to a motion .npz (required) |
--gain [SIDE.]JOINT.FIELD=VALUE | Override one gain for this run, e.g. left.elbow.kd=4.5 or shoulder_3.kd_host=8 (no side = both arms). Fields: kp, kd, kd_host, kd_host_hz, kd_host_q, j_eff, stiction_gain, stiction_load_gain, stiction_err_deg, dither_nm, dither_hz, stribeck_gain, stribeck_dfs, stribeck_load_gain, stribeck_vs, stribeck_pole, the friction model as friction.fc, friction.k, friction.fv, friction.fo, friction.fl, and the firmware position-loop gains as firmware.position_kp, firmware.position_ki, firmware.position_kd, firmware.speed_kp, firmware.speed_ki, firmware.profile_acc, the MyActuator 0xA4 planner as firmware.planner_accel (0 or 60000) with its per-tick speed-cap tracking firmware.cap_track (≥ 1, e.g. 1.2) and target lead firmware.planner_lead_ms, the 0x73 torque feed-forward’s firmware.tf_rated_current_a (A; MyActuator a4 joints on V4.4 firmware), and the gravity model’s link inertials mass (kg) and com.x / com.y / com.z (m, the driven link’s URDF frame — for trying a gravity correction before calibrating it) (firmware gains are written to the motors’ ROM at enable like the config values they replace, so a run leaves them there — the planner is pinned back to 0 by the next run without the override). Repeatable |
--stiffness S | Stiffness-slider position in [0, 1] for both arms (default: the panel’s, 1.0 unless changed — the tuned gains, where gain overrides land exactly; lower only adds compliance) |
--defaults | Run the bare calibrated defaults, ignoring the panel settings (per-joint gains, link mass / CoM, stiffness, has_gripper) |
--ik | Drive the run through the IK solver: each waypoint’s end-effector poses (FK of the reference, with elbow hints) are re-solved to joints exactly like teleop’s pose→joints loop, and the arms execute the solver’s output — still scored against the clean reference, so IK reconstruction error and tracking error show up together. Per-solve times and the solved-vs-reference deviation are stored on the run (ik_solve_ms_*, ik_dev_*) |
--noise MODE | Corrupt the motion before streaming it, at the noise source’s real pipeline entry point: network (jitter/outliers/stalls), ik (solver churn/jumps), combined, or none (default). Deterministic per --seed |
--filter | Replay the (possibly corrupted) stream through the production teleop filter stack (pose low-pass → EMA → trapezoid) before streaming — the hardware version of tune.filter: the arm physically shows what the stack removes and what it costs in lag. Off streams it raw |
--seed N | RNG seed for --noise — identical seed, identical corrupted stream (default: 0) |
--label TEXT | Free-form note stored on the run artifact |
--torque-threshold NM | Contact watchdog threshold (default: 8.0; 0 disables) |
--no-save-run | Don’t persist the run artifact (dry run) |
--a4 SIDE.JOINT | Drive one MyActuator joint with the firmware position loop (0xA4) instead of the MIT frame for this run, e.g. right.shoulder_1; repeatable. No compliance, no host feed-forward, NaN torque on that joint — see wire_mode in the config reference |
--loop-hz HZ | Realtime-core tick rate override for A/B runs (default follows --controller: 240 impedance, 400 position) — e.g. --controller position --loop-hz 240 to separate the rate from the controller. Impedance (MIT) is commanded at 240 Hz only: with --a4 joints inside the impedance controller the default is 480 Hz, the --a4 joints every tick and the impedance joints on alternate ticks (exactly 240 Hz each, their host feedforward and damping stepped at that rate); with any arm joint on impedance only 240 or 480 is accepted |
--repeat N | Replay the motion N times back to back in one session (default 1; 0 = until Ctrl-C). No homing in between: each pass after the first starts with a planned move back to the start pose if the motion does not end there. Each pass is scored and saved as its own run (label suffixed [k/N]), a one-line-per-pass summary (worst buzz, mean jitter, worst joint) closes the session, and --record captures the whole session in one trace — for soak runs and catching an intermittent buzz |
--learn N | Iterative learning over N passes (in place of --repeat). Pass 1 runs uncorrected; after each pass a per-joint position offset on the streamed motion is updated from that pass’s tracking error inside --learn-band — first the error advanced by the joint’s lag, then the inverse of each joint’s command-to-position response measured from the passes themselves. A pass that comes out worse than the best rolls back to the best offset with the gain halved. Every pass is saved with the offset it flew (correction in its series). The offset belongs to this motion: it measures what each joint needed, the ceiling a model-based feedforward can aim for |
--learn-imu | With --learn: the error learned away is the tool’s vertical deviation from its commanded path as the wrist IMU measures it (gravity tracked through the camera’s rotation by the gyro), spread over the learned joints by the height Jacobian — including flex and backlash the joint encoders cannot see. Rolls back only at 1.5× the best pass (the IMU error is noisier pass to pass) and never halves the gain below 0.2. Needs one driven arm and its wrist camera; refuses to start without the IMU. On the jelly robot’s slow_osc it took the deviation from the path from 0.67–0.78 mm to 0.26–0.31 mm RMS (1–15 Hz) |
--learn-gain G | Step size of the model-based updates (default 0.7; 0.9 converges faster where passes repeat closely — on a simulated arm 10 passes cut the band error 57% at 0.5, 77% at 0.9) |
--learn-band LO HI | Band the learning corrects, Hz (default 0.7 8): the shake, above the deliberate motion and its tracking lag |
--learn-max-deg D | Clamp on each joint’s learned offset, degrees (default 1.5) |
--learn-joint SIDE.JOINT | Learn only these joints (repeatable; default every joint of the driven arms that moves at least 1° and is not --hold) |
--correction RUN_ID | Fly the offset a --learn pass saved on every pass, without learning — to check a learned correction holds up |
--invert | Pre-compensate the streamed motion with each joint’s tracking model (see tune.tf) so the closed loop’s lag and resonance cancel; joints without a model stream as before |
--notch HZ | Notch the streamed motion at HZ (repeatable) with teleop’s command notch (VRTeleopConfig.command_notch_hz: causal biquads between the IK EMA and the tracker) — for structural modes the joint loops cannot see or damp; scored against the clean reference |
--notch-q Q | Quality factor of those notches (default 2.0; bandwidth f0/Q) |
--imu-damp C | Damp the tool’s vertical shake from the wrist IMU: a force of −C × the flex velocity (N·s/m) at the tool — the IMU’s band vertical velocity (gravity tracked through the camera’s rotation with the gyro) less the tool velocity FK of the measured joints gives, through an identical filter chain, so neither the deliberate motion nor what the joint loops already handle is damped, applied through --imu-damp-joint by the measured-pose Jacobian (almond_axol.tuning.imu_damping). For the shake the joint encoders cannot see — after learning, 60% of slow_osc’s 1–3 Hz wrist shake and 80% of its 3–15 Hz. Start at 5; on a simulated hidden 2 Hz mode 5/10/20 N·s/m damped 31/44/52% and 80 diverged. The damper switches itself off for the pass if the flex velocity passes 80 mm/s for 0.15 s (slow_osc’s own peaks near 65). Each damped pass saves the damper’s per-sample velocity and torques (imu_damp) |
--imu-damp-joint SIDE.JOINT[=SCALE] | Joints that apply it, optionally scaling --imu-damp for that joint (repeatable; default the driven arm’s shoulder_1, shoulder_2 and elbow). On jelly shoulder_1 damps the 1–3 Hz sway and the elbow the 3–15 Hz shake: right.shoulder_1 + right.elbow=0.6 at 120 N·s/m was the best setting (2026-10-01, below) |
--imu-damp-joint-lp SIDE.JOINT=HZ | First-order low-pass on one joint’s damping torque (repeatable) — to keep the elbow’s channel off an ~11 Hz mode without costing shoulder_1 its 1–3 Hz phase |
--imu-damp-ref encoder|command|model | What the tool’s velocity is measured against. encoder (default): the height FK of the measured joints gives — only the flex past the encoders is damped. command: the commanded height — the whole deviation from the path, encoder-visible wobble included (a 0.8 Nm shoulder_1 torque probe moved the 1–3 Hz tool height coherently while barely moving the hidden flex). model: the height the arm is expected to reach — each joint’s command through its tune.tf tracking model (~/.almond/tracking_models.json, DC gain pinned to 1), so the arm’s normal ~60 ms lag is not damped as wobble; joints without a model pass the command through |
--imu-damp-source imu|encoder | Measure the tool’s velocity with the wrist IMU (default) or with FK of the joint encoders — no camera and no IMU latency, but blind to flex past the joints; against the command, or the expected path with --imu-damp-ref model. Against the raw command it did not help on jelly (it fights the tracking lag) |
--imu-damp-lp HZ | High edge of the damped band (default 15, first-order). The loop’s phase wraps where the IMU’s ~15–25 ms round trip catches up with the arm’s modes (~7–8 Hz on shoulder_1, ~12 Hz on the elbow): a low edge here only moves the wrap lower — 40 (almost no lag) worked best |
--imu-damp-lead HZ | A lead-lag stage centred here (+37° at it, 4× gain above; default 0 = none) |
--imu-damp-notch HZ | Notch the damping force (Q --imu-damp-notch-q, default 1). On jelly a notch at the ~11 Hz buzz cost enough phase below it to drive a 6–7 Hz mode instead |
--imu-damp-max NM | Per-joint clamp on the damping torque (default 0.5) |
--imu-damp-hp HZ | Low edge of the damped band (default 1.0). The high-pass leads 90° at its own corner, so below ~2× it the force acts more like a spring than a damper; 0.5 halves the lead at 2 Hz and lets ~4 mm/s of slow_osc’s commanded motion through |
--imu-damp-alternate | Damping on passes 2, 4, … only — an A/B inside one session, free of the 10–20% drift between sessions |
--gyro-damp C | Damp the flex past the joint encoders from the wrist gyro: each --gyro-damp-joint SIDE.JOINT[=C] (default shoulder_1 and elbow) gets −C × (gyro rate − encoder-implied rate) about its own axis, N·m·s/rad. The causal signal matched its zero-phase version to −1° over 1–3 Hz, but the torque → flex path wraps its phase above ~5 Hz, so on jelly no gain or sign helped — see --torque-probe. Needs --gyro-mount |
--gyro-mount PATH / --gyro-mount-fit RUN_ID | The wrist cameras’ rotation against the gripper mount (default ~/.almond/wrist_imu_mount.json); --gyro-mount-fit Kabsch-fits it from a saved run’s slow motion (R² ≥ 0.99 expected), writes it and exits |
--torque-probe SIDE.JOINT=NM | Add a known 0.5–15 Hz multisine torque (≤ 1 Nm peak) to a joint through the damping path (repeatable; with --imu-damp-alternate on every second pass), to measure the joint-torque → tool response a damper closes its loop through |
--enc2 SIDE.JOINT | Have the realtime core read this MyActuator joint’s output-side encoder (0x60, 131072 counts/rev) into its trace (enc2_p/enc2_t in PREFIX_rt.npz; implies --record). On jelly it agreed with the control position to ~7 mdeg under 6 Nm of torque: the firmware already controls on the output encoder, and the flex the IMU sees is past the joints |
--hold SIDE.JOINT[=DEG] | Hold a joint steady instead of following the motion (repeatable): at the motion’s own start angle, or at the given joint-frame angle (degrees, inside the joint limits; the approach goes there). It keeps its controller and gains, commanded to one pose, and is scored as a parked joint (buzz and torque chatter only). Only the approach is collision-checked — a joint frozen while the others move can bring links closer than the recording did, so watch the first pass |
--record PREFIX | Flight recorder, as teleop’s --teleop.record: the replay’s measured joints to PREFIX_meas.npz and the core’s per-tick trace (target, command, measured position, motor speed, feed-forward terms) to PREFIX_rt.npz |
--controller impedance|position | Which control law the core runs the arms on for this run: impedance (the config default) is the production MIT frame at 240 Hz with the host feed-forward; position puts every joint on its motor’s own position loop — MyActuator 0xA4, Damiao position-velocity, the firmware.* gains — streamed at 400 Hz. Stiff, no host feed-forward, NaN torque on the MyActuator joints. Same motion and scoring, so the two controllers compare directly; --a4 still adds single joints inside the impedance controller. See controller in the config reference |
--arms both|left|right | Which arm(s) to bring up and drive (default: both). The other arm’s bus is left untouched, so an unpowered arm or a single-arm bench does not block the run |
--fast-impedance SIDE.JOINT | Run this impedance joint at 480 Hz — every tick of a 480 Hz core loop — while every other impedance joint stays at 240 Hz on alternate ticks (repeatable; e.g. --fast-impedance right.shoulder_1 --fast-impedance right.elbow). Sets the joint’s impedance_hz; an experiment, the gains were tuned at 240 |
--impedance-hz 240|480 | Command rate of the MyActuator impedance joints for this run: 240 (the config default, verified) or 480 — every tick of a 480 Hz core loop, the Damiao wrists staying at 240 Hz on alternate ticks. An experiment: the impedance gains, host damping and feed-forward were tuned at 240. See impedance_hz in the config reference |
--no-guard | Turn off the tracking guard. By default every driven joint’s tracking error is watched each sample, and playback aborts when a joint leaves its trajectory (--guard-dev-deg, default 10°, plus 0.1 s of the commanded speed — normal lag on a fast move is not a departure), oscillates (--guard-osc-deg, 1.5–15 Hz error RMS, default 1.5°) or vibrates (--guard-vib-deg, > 15 Hz error RMS, default 0.15°). A trip puts the run’s gain overrides back to the base config, holds the arm still for 1.5 s, then returns it to rest; the moves to and from the motion get twice the limits, and never less than twice the defaults. If the return trips twice the arm is left holding its last command (energized) rather than disabled mid-air. Trips are saved with the run and end the command with status 3. Sized on jelly’s 490 saved runs: healthy replays stay under 8.7° / 0.87° / 0.10° (99th percentile) and the runs that rang reached 11–14° / 2.0–2.4° / 0.11–0.16°; tighten the limits for slow creep motions (--guard-dev-deg 4 --guard-osc-deg 1). |
--no-imu | Skip the wrist IMU. By default each driven arm’s wrist ZED X One IMU is recorded (in its own process; a camera that will not open just means no IMU score) and every pass gets an imu score — see below |
--no-gripper | Run on the gripperless SKU (the panel’s axol.has_gripper already says so on a robot set up without grippers) |