Episode 02 / Written companion

Free recording track

I broke my robot arm on purpose

A frozen benchmark turns the gap between a nominal arm and a changed one into failures you can reproduce and explain.

Read the companion now. The recording is forthcoming; “Free” identifies its planned release track.

A position servo acts like a spring toward its goal. Under load it settles short of the goal, where the spring’s torque balances gravity, so a heavier payload sags further. The stock baseline sends inverse-kinematics angles straight to the servos and inherits that sag. Two classical fixes go on top: an outer PID per joint, whose integral removes the steady sag but not the transients, and gravity feedforward, which offsets each goal by the nominal gravity torque divided by the servo’s stiffness. Neither knows the arm’s true payload, friction or stiffness, and this episode exploits exactly that.

Score all three with one frozen benchmark: the trajectories, perturbations and metrics of the course’s benchmark spec, which the starter kit ships as bench.py. Tune on the nominal arm, then change the arm, not the controllers: +150 g; a +100 g payload step 15 s into the hold; gear friction ×2; servo stiffness −30% on every joint, or on the shoulder alone as its P coefficient at 70% would make it; a 50 g weight swinging on a 20 cm string; a 1° elbow zero offset; warm-up; noise; and 10 and 20 ms of latency. The best classical controller must keep the nominal RMS error on the slow figure-eight under 2 mm, and at least one perturbation must push the maximum error past 10 mm or trip an overload.

A baseline that survives every perturbation gets a separate stress sweep, never a weaker tuning. Keep every seed with its commit and config, and write a cause for each result, nominal included: sag, lag, windup, latency or an overload trip. Then draw: the figure-eight, your name from an SVG path, and the same drawing with 150 g of weights clipped to the pen holder, where the stock controller’s line should sag visibly. Plot every drawing over its command.

Take this into your next build

A failure is useful when you can name its mechanism and rerun it from a config and a seed.

Put the idea to work

Move it classically, then break it

Solve inverse kinematics, put PID and gravity feedforward on top of the servos’ own loops, and score them with bench.py, the harness every later module reruns. Then reproduce the sim-to-real gap on purpose and explain each failure.

Build module 02