Spaces:
Running
Download README.md from bacool/arm-playground: direct link, hf CLI and curl.
- Browser
- Download file 26.8 kB
-
https://huggingface.co/spaces/bacool/arm-playground/resolve/main/README.md
- Command line
-
hf download hf://spaces/bacool/arm-playground/README.md
-
curl -L -o README.md https://huggingface.co/spaces/bacool/arm-playground/resolve/main/README.md
title: Arm Playground
emoji: 🦾
colorFrom: blue
colorTo: yellow
sdk: static
app_file: index.html
pinned: false
license: mit
short_description: Robot kinematics and control you can touch
Arm Playground
Learn robot kinematics and control by touching them.
An interactive two-joint arm with 30 guided lessons, the last six on the real five-joint arm in 3D,
plus the Workbench: a playable 3D bench where you drive the SO‑101, record your driving, replay it blind and teach a small policy,
built as a companion to
Unit 2 · Classical Robotics of the Hugging Face Robotics Course,
plus a second page for Unit 1: what LeRobotDataset actually fetches when you ask for a temporal window.
▶ Open the playground · ▶ The real arm in 3D · ▶ Workbench · ▶ Unit 1 · Datasets · What's inside · How it compares · Deep links
Also hosted as a Hugging Face Space, and the source of the diagrams offered to the course in huggingface/robotics-course#37.
Iterative IK racing to a solution. The map's green and purple regions are the basins of attraction: start anywhere in green and you converge to A, purple to B.
Why this exists
Unit 2 of the Hugging Face Robotics Course makes one argument: even a two-joint arm needs real math to control, and that math breaks on contact, clutter and cameras, which is why the field is learning from data. The unit gives the equations (forward kinematics, inverse kinematics, the Jacobian, feedback) but no way to push on them. Its source even carries TODO placeholders for the diagrams.
This playground is those diagrams, made interactive. Every equation in the unit is a slider, a drag or a button, and each tab walks you through it with predict-then-check questions: you commit to a number before the answer appears.
It uses the course's own notation (q = [θ₁, θ₂], p(q), J(q)⁺, k_p Δp) and its running example: the SO‑100 arm with four joints locked, so it moves in a plane. Tab 5 then gives the locked joints back, one at a time, on the real arm in 3D, so you can watch the planar math stop being enough.
What's inside
Two views, always in sync. Task space: where the hand is. Configuration space: every pixel is one pose (θ₁, θ₂). Hover it to preview that pose as a ghost arm; click to go there. Turn on a floor or shelf and watch them carve regions out of the map. Every tab has a "What do the symbols mean?" key: tap a symbol and the matching thing on screen pulses, so J(q) stops being notation and becomes "those two arrows at the hand."
| Tab | You learn | You can | Lesson |
|---|---|---|---|
| 1 · Forward kinematics | FK is two arrows added head to tail; θ₂ is relative; the workspace is a ring | Drive θ₁, θ₂; change link lengths; hover the pose map | 6 steps |
| 2 · Inverse kinematics | IK has 0, 1 or 2 answers; obstacles delete them; iterative solvers find the one whose basin you start in | Drag a target; jump to either solution; run an iterative (damped least-squares) solver and see its path; paint the basins of attraction | 6 steps |
| 3 · Jacobian & diff-IK | J's columns are each joint's "nudge"; det J = l₁l₂ sin θ₂; singularities make joint speeds explode | Wiggle a joint to see its column; drag a desired hand velocity; run diff-IK into a singularity with a plain vs damped inverse; teleop the hand with arrow keys | 5 steps |
| 4 · Feedback control | Open loop trusts the model; feedback fixes model error; k_p·Δt > 1 overshoots, > 2 diverges; latency and saturation change the picture; tracking isn't planning; and, beyond the course, why PD needs mass: P on an arm with inertia is an undamped spring, k_d is the damper | Step, circle, sweep or drag targets; vary gain, control rate, latency, model error, joint speed limit; watch the error trace against the ideal e^(−k_p t) response, step-response metrics and the discrete error sequence; switch on inertia and tune k_d to critical damping | 7 steps |
| 5 · The real arm (3D) | The plane of tabs 1–4 was one slice of a five-joint arm; one spare joint turns "A or B" into a continuous family of poses; the pose map can no longer be drawn; J becomes 3 × 5 (which is why the course writes J⁺), then 6 × 5 with rank 5 | Orbit the SO‑101, drawn from its URDF; unlock joints one at a time; sample the reachable volume; hold the fingertip still while the arm moves (self-motion); drag a target in 3D and watch the same damped pseudo-inverse follow it | 6 steps |
![]() Forward kinematics. The grey dashes show θ₂ is measured from link 1. |
![]() Inverse kinematics with obstacles. The target is inside the ring, but the shelf blocks both solutions. |
![]() Jacobian. Columns as arrows, the velocity ellipse, and the det J = 0 rows. |
![]() Feedback control. Lesson, live math, error trace and step metrics. |
![]() Give the arm mass. With inertia on, proportional control alone is an undamped spring; k_d is the damper. |
![]() Unit 1 · Datasets. Drag a frame through an episode and watch delta_timestamps become indices, clamps, masks and tensor shapes. |
Tab 5: the real arm, in 3D
Two joints in a plane, then the pan unlocks and the plane swings like a door, then all five joints follow a target.
Tabs 1–4 teach the course's simplification: two joints, one flat plane. Tab 5 shows what was simplified away. The arm is the SO‑101 (the current revision of the course's SO‑100), built from the robot's own description file, and the tab starts with three of its five joints locked, which is exactly the arm of tabs 1–4. A translucent sheet marks the plane you have been working in. Then you unlock joints:
- The plane was a slice. Same arm, same ring, real sizes: the upper arm is 116 mm and the forearm 135 mm, not the two equal links the course draws.
- Unlock the pan. The sheet swings about a vertical axis and the flat ring sweeps out a volume.
- Three joints for two numbers. Unlock the wrist and the fingertip can stay put while the arm moves: tab 2's "A or B" becomes a continuous family.
- The map no longer fits. A pose is five numbers. A 5° grid over two joint angles has 5,184 cells; over this arm's five joints it has about 166 million.
- Why the course writes J⁺. The Jacobian is 3 × 5. No inverse exists, and tab 3's damped pseudo-inverse works unchanged.
- Five joints, six numbers. Position plus orientation needs six; the 6 × 5 Jacobian has rank 5. The hand-written model is running out, which is where Unit 2 hands over to learning.
Where the numbers come from. Joint order, axes, offsets and limits are read from so101_new_calib.urdf in TheRobotStudio/SO‑ARM100, pinned at commit 385e8d7 (Apache‑2.0). An unmodified copy is in docs/vendor/, and the tests check the page's table against it number for number, then check forward kinematics against a separate implementation (reference_fk.py).
What's exact and what's a model is listed on the tab itself. In short: the kinematics, link lengths, Jacobian and the 5 < 6 count are exact to the URDF; the rods are a schematic of the joint centres (not the printed parts), the reachable points are a random sample, the solver is numerical (within 0.1 mm when it converges, and it says so when it doesn't), and a URDF is an idealised arm.
No 3D library. The view is about a dozen rods, a grid and a few thousand points, so it is drawn on an ordinary 2D canvas with a hand-written camera, perspective divide and depth sort. That keeps the tab at one 55 KB file (18 KB gzipped), with no WebGL requirement, and it still opens from a downloaded copy of the folder (ES-module 3D libraries can't be loaded from file://). Two flat views, side and top, are always shown next to it and are the precise place to drag a target.
![]() The real arm (3D). The pan is unlocked: the sheet is the plane of tabs 1–4, the dots are sampled wrist positions, and the flat views on the right stay exact. |
The Workbench: drive it, record it, replay it, teach it
Mission 1. Drive the gripper with the keyboard or a thumb stick; the corner inset is the wrist camera, the image a policy would be fed.
Tabs 1–5 let you inspect an arm. The Workbench lets you operate one, and operating is what the rest of the course is about: LeRobot's method is teleoperate, record, train, deploy. Seven missions, each with a predict-then-check question, each one a course idea you feel before you read it:
| # | Mission | The idea | Course |
|---|---|---|---|
| 1 | Put the cube in the bin | Cartesian teleoperation: tab 3's damped pseudo-inverse running under your thumbs | Unit 2 |
| 2 | Now do it joint by joint | Why a leader arm exists: a straight line in the room is a curve in joint space | Unit 2 |
| 3 | Reach the far corner | Joint limits and the workspace, felt as the hand refusing to go | Unit 2 |
| 4 | Record a demonstration | Your episode as rows: 30 a second, six numbers of state and six of action, under the real LeRobotDataset names |
Unit 1 |
| 5 | Replay it blind | The cube is moved 4 cm and your recording replays: the jaws close on air, because a replay never looks | Unit 2 |
| 6 | Drive through lag | Every command lands 200 ms late; tab 4's latency lesson, in your hands | Unit 2 |
| 7 | Teach it | A small network trains in the page on your own demonstrations and is scored on eight spots it never saw. One demo is hit or miss; four from different spots make it reliable | Unit 4 |
What's exact: the arm's joints, lengths and limits are the SO‑101 URDF's, the same tested K3 code as tab 5; the control is damped least squares on four joints for four numbers (x, y, z and the jaws' pitch), checked in the tests to 1 mm; a recording replays its own joint path bit for bit; the lag queue delays a command by exactly six frames at 30 updates a second.
What's a toy, and says so on the page: the arm is simplified shapes in the real colours; grasping is one rule (the closed jaws take the cube if its centre is within 21 mm of the fingertip) with no contact physics; falling is plain gravity onto flat surfaces; the policy is a small network that is handed the cube's position, where the course's ACT has to find it in camera images.
How it is built: workbench.html + workbench.js, with three.js r149 vendored as a plain script (docs/vendor/three.min.js, MIT) so the page still opens from a downloaded copy; no build step. The core of workbench.js (control, world, grasp, recorder, replay, lag, the policy's training) is pure and runs in npm test; the view is a three.js layer over it. Training runs in a Web Worker, with a same-thread fallback for file://. Without WebGL the page points you to tab 5, which draws the same arm on a plain canvas.
The Unit 1 page: datasets in practice
Unit 1's only code (page 1.4) is about delta_timestamps: asking LeRobotDataset for a window of past observations and future actions around one frame. The course explains it with a static diagram. datasets.html makes it live, against the real facts of lerobot/svla_so101_pickplace (50 episodes, 11,939 frames, 30 fps, six motors, two 480×640 cameras) and the fetch rule in lerobot 0.6.1's dataset_reader.py:
- Episode timeline. Drag t through a 240-frame episode. Observation frames light up blue, action frames orange, and any offset that runs past the episode edge is drawn hatched red with an arrow showing it being clamped back to the edge frame and flagged in
{feature}_is_pad. It never crosses into the neighbouring episode. - Window editor with the course's three recipes (basic BC, history BC, action chunking). An offset that isn't a whole number of frames, like 0.05 s at 30 fps, shows the same
ValueErrorLeRobot raises. - Joint traces of the six action values with the fetched chunk marked, so "the next 0.3 s of the plan" is something you can see.
- The sample, live: every key of
dataset[t]with its tensor shape, the_is_padmasks, and the batched shapes from aDataLoader, including the 0.6.1 quirk that a single-offset video window is squeezed to[3, 480, 640]whileactionkeeps[1, 6]. - The exact Python for the current window, copyable, plus the working form of streaming (
next(iter(...)), sinceStreamingLeRobotDatasetis anIterableDatasetand the course'sstreaming_dataset[100]raises). - Six predict-then-check lessons, from "how many frames is 0.1 s" to why a padded chunk must be masked in the loss (ACT multiplies its L1 loss by
~action_is_pad).
How it compares
Two-link arm visualisers are a well-worn genre. I checked the closest ones against their own pages on 2026‑09‑25. This playground is narrower in scope (two joints in a plane, plus one tab on the five-joint arm in 3D) and deeper on what Unit 2 teaches.
| Arm Playground | ArmLab | ShareTechnote | CompuTools | |
|---|---|---|---|---|
| FK and both closed-form IK solutions | ✓ | ✓ | ✓ | ✓ |
| Joint limits | elbow only | ✓ | — (ignored by design) | ✓ |
| Obstacles, drawn in configuration space | ✓ | — | — | — |
| Closest feasible pose when IK fails (optimisation form) | ✓ | — | — | — |
| Iterative IK solver + basins of attraction | ✓ | — | — | — |
| Jacobian columns + manipulability ellipse | ✓ | — | ✓ | ✓ (index / SVD) |
| Singularity blow-up, plain vs damped inverse | ✓ | — | described | detection |
| Feedback control with gain, rate, latency, model error, saturation | ✓ | — | — | — |
| Guided lessons with predict-then-check questions | ✓ (30, plus 7 Workbench missions) | quick-start list | written explainer | — |
| PD control on an arm with inertia (spring + damper) | ✓ | — | — | — |
| More than two joints / 3‑D / DH tables | 5 joints in 3‑D from the URDF (tab 5 and the Workbench); no DH tables | — | — | ✓ |
| Teleoperate, record an episode, replay it, train a policy on it | ✓ (Workbench) | — | — | — |
| Trajectories, waypoints, export | — | ✓ | — | path trace + CSV export |
Use CompuTools for 6‑DOF arms and DH parameters, and ArmLab for trajectory planning. Use this one to understand why Unit 2 ends by arguing for learning. For a comparison of iterative IK solvers (CCD, FABRIK, Jacobian transpose), see Saeed Ghorbani's interactive post. If I've mischaracterised a tool, open an issue.
Deep links for teachers
Any setup can be shared as a link (use the 🔗 button). Parameters go after #tab, joined by &:
| Param | Meaning | Example |
|---|---|---|
fk ik jac fb r3 |
Tab (r3 = the real arm in 3D) |
#ik |
step |
Lesson step (1-based) | step=4 |
t1, t2 |
Joint angles (degrees) | t1=30&t2=60 |
tx, ty |
IK target | tx=1.2&ty=1.35 |
l1, l2, lim |
Link lengths, elbow limit | l2=0.5 |
floor, shelf, basins |
Toggles | floor=1&shelf=1 |
inv, lam |
Jacobian tab: plain / damped inverse, damping λ |
inv=damped&lam=0.1 |
path, kp, rate, lat, merr, qmax, ff |
Feedback settings | path=step&kp=75 |
inertia, kd |
Give the arm mass; damping gain | inertia=1&kd=11 |
solve |
Start the iterative IK solver (2 = run instantly) |
solve=1 |
iter |
Run exactly N solver iterations and freeze (animation frames) | iter=12 |
theme |
light / dark |
theme=dark |
q, free |
Tab 5: the five joint angles in degrees (pan, lift, elbow, wrist flex, wrist roll); which joints are unlocked, one digit each | q=0,20,-74,0,0&free=01100 |
cam |
Tab 5: camera yaw, tilt (degrees) and distance (mm) | cam=-52,22,860 |
plane, cloud, axes, cols, triad |
Tab 5 toggles: the plane of tabs 1–4, reachable points, joint axes, Jacobian columns, fingertip orientation | cloud=1&cols=1 |
tg, mode |
Tab 5: target x,y,z in mm; mode=follow tracks it, mode=self holds the fingertip while the wrist turns |
tg=250,120,170&mode=follow |
m (on workbench.html) |
Start the Workbench at mission 1–7 | workbench.html#m=5 |
Try it: both IK solutions blocked by a shelf · iterative IK racing to a basin · latency turns a safe gain unstable · the real arm with its pan unlocked · five joints following a target in 3D
Proof you can run
npm test
Thirty-eight checks, no dependencies (Node 18+). They slice the math out of the pages and verify it against identities and the lessons' own worked examples: FK↔IK round-trips, the Jacobian against finite differences, det J = l₁l₂ sin θ₂, the closed-form solutions for the lesson targets, that the damped pseudo-inverse stays finite where the plain inverse fails, and, for the Unit 1 page, that the delta_timestamps fetch rule reproduces LeRobot's frame indices, clamping and is_pad masks.
Fourteen of them cover tab 5: the joint table equals the vendored URDF number for number; forward kinematics matches known poses from an independent implementation (python3 docs/vendor/reference_fk.py) and a quarter-turn worked by hand; the links are 116 mm and 135 mm; the side view reduces to tab 1's own formula; the 3 × 5 and 6 × 5 Jacobians match finite differences, with rank 5; self-motion holds the fingertip within 0.1 mm; the Solve button's several-starts search reaches at least 99% of targets that are reachable by construction (a single search misses over 5% of them, which is why it retries); and the numbers the lessons and labels quote are recomputed from the kinematics.
Eight cover the Workbench: the hand follows commands to within 1 mm with the jaws pitched down at every cube spot; pushing past the edge of reach stops the hand and tilts the jaws without breaking a joint limit; the grasp rule at its 21 mm boundary; a recording replays bit for bit, reproduces its outcome, and misses once the cube is moved 4 cm; the world advances in 1/30 s steps at 30, 60, 120 or 144 Hz; lag delays a command by exactly N steps; the toy policy trained on four demonstrations, clean or clumsy, delivers at most of eight unseen spots and beats a policy trained on one; and every number a mission quotes comes from the code that produces it.
Under the hood
- No build.
index.html(Unit 2),arm3d.js(tab 5, fetched the first time that tab is opened),datasets.html(Unit 1) andworkbench.html+workbench.js(the Workbench) are vanilla HTML, CSS and JavaScript. KaTeX (from jsDelivr) renders the Unit 2 equation cards and falls back to plain text offline; three.js r149 is vendored for the Workbench only. - Tab 5: forward kinematics is the URDF's chain of fixed transforms and joint turns in double precision; the Jacobian is geometric (column k = axis k × (point − joint k)); IK and self-motion are damped least squares, Jᵀ(JJᵀ + λ²I)⁻¹ with λ = 6 mm/rad, steps capped at 40 mm and 0.2 rad, clamped to the URDF joint limits. That search only goes downhill from where it starts and can stall against a limit, so the Solve button retries from up to 31 other starting poses (the pan aimed at the target, then seeded random poses) before reporting a miss. The 3D picture is a hand-written orbit camera with painter's-algorithm depth sorting on a 2D canvas; it redraws only when something changes.
- Kinematics: closed-form FK/IK; J(q) analytic; the damped pseudo-inverse is Jᵀ(JJᵀ + λ²I)⁻¹.
- Collisions: exact segment-vs-box tests (Liang–Barsky); the configuration-space map is a 2° grid.
- Iterative IK: damped least squares (λ = 0.15, step 0.6, capped at 0.2 rad per iteration). Basins come from running it from every cell of a 120×120 grid.
- Feedback sim: integrates at 2 ms. The controller updates at the chosen rate with zero-order hold, measures the hand with the true geometry (like a camera) after the chosen latency, builds J from possibly wrong link lengths, and clamps joint speeds. The bar chart shows the linearised sequence ek+1 = ek − kpΔt·ek−d. With inertia on, a command is an acceleration instead of a velocity, q̈ = J⁺(kpΔp + kd(ṗ* − ṗ)), integrated twice, and the bars switch to the unit-mass spring–damper response.
- Not built, and why: see BACKLOG.md.
- Conventions: angles in degrees in the UI and radians in the math; θ₂ is relative to link 1; lengths in abstract units (default l₁ = l₂ = 1). Only the elbow has a joint limit; the shoulder is free. Tab 5 is in millimetres with the URDF's limits on all five joints; there θ₁ and θ₂ are the lift and elbow joints up to a fixed offset and a sign, as drawn in its side view.
- Lessons: each step resets its tab to a baseline (link lengths, obstacles, inverse type) before applying its own setup, so steps are reproducible in any order. Progress is remembered in
localStorage.
Credits
An unofficial companion to the Hugging Face Robotics Course, which is based on the Robot Learning Tutorial. Not affiliated with Hugging Face. The equations and the SO‑100 planar example follow the course's Unit 2. Tab 5's arm geometry comes from the SO‑101 URDF in TheRobotStudio/SO‑ARM100 (Apache‑2.0); see docs/vendor/NOTICE.md.
Built by Bakul Badwal while working through the course. MIT licensed.






