Actuators, joints and arms
The SDK names what it moves in two ways. A joint belongs to an arm: joint_4 is a joint of the left arm's chain,
named as the arm's description names it. An actuator belongs to the robot: it is one entry of the lists the
control loop reads and writes every tick, one per motor the loop drives, counted across every arm. The left arm's
joint_4 and the right arm's joint_4 are two joints of the same name, and two actuators of different numbers.
How a robot numbers its actuators
Each arm owns a run of actuators, in the order the robot lists its arms: its chain joints first, in the order its description lists them, then its gripper. A bimanual openarm, whose arms each have seven chain joints and a gripper, comes out as:
| actuators | arm | what they drive |
|---|---|---|
| 0 to 6 | left | joint_1 to joint_7 |
| 7 | left | the gripper's motor |
| 8 to 14 | right | joint_1 to joint_7 |
| 15 | right | the gripper's motor |
RobotConfig.actuator_summary says the same in a line, and
RobotConfig.layout gives each arm's actuators as numbers.
Two things follow from what an actuator is. A gripper the loop drives has an actuator but is no joint of the chain:
kinematics never sees it, and its actuator reads its motor's angle in radians, not how far the jaws are open, which
the gripper's GripperMap converts between. And a gripper or hand driven beside an arm,
rather than by the loop, has no actuator at all: the loop's lists hold only what the loop drives, so a robot can have
more motors than actuators.
Two ways to read the same snapshot
Everything the loop measured and wrote on a tick comes back as a Snapshot, whose lists,
q, tau, status_names and the rest, are indexed by actuator. Snapshot.arm reads
the same values by name instead: an ArmSnapshot holds each chain joint by the name its
description gives it, and its gripper with the jaw opening already worked out. Application code that cares about one
arm's joint or how far its jaws are open reads it that way, and never needs a number.
The numbers are for code that works on the whole robot at once. A JointCommand or a
JointTrajectoryCommand states a position per actuator, a recorded episode
stores one, and a guard's report names the actuators it clamped. Every by-name reading carries its actuator too, so
the two are never more than a lookup apart.
The lists are a fixed size, MAX_ACTUATORS, so the loop never allocates; a robot
uses the first of them, as many as it has actuators.