Skip to the lesson
FixTheRobot Home / VLA / Prerequisites / M5.5

VLA · Prerequisite · Robotics Fundamentals

M5.5 — End-Effector Poses and Joint Commands

The two common ways to tell a robot where to move.

  • Prerequisite module 5
  • Robotics Fundamentals
  • Essential
  • About 14 minutes to read

A policy says “move the hand a little forward”, and the motors need joint angles. This lesson follows an end-effector action through delta handling, inverse kinematics, limits and interpolation, using the planar arm so every step can be checked.

Learning objectives

After this lesson, you should be able to:

  • Explain end-effector actions and the 7-dimensional delta action with a gripper.
  • Distinguish absolute and delta actions, and explain why the frame of a delta matters.
  • Convert a hand delta into a joint step with the Jacobian.
  • Limit a joint step while preserving its direction.
  • Interpolate between slow policy outputs and fast controller ticks.
  • Describe the closed loop from observation to joint command.
  1. Policy outputs an end-effector delta and a gripper value
  2. Delta becomes a target pose in the right frame
  3. Inverse kinematics gives joint targets
  4. Limits and safety checks
  5. Interpolation to the controller rate
  6. Joint controllers drive the motors
The path from an end-effector action to motion.

The one-sentence idea

A policy’s end-effector action is turned into joint commands by a chain of frame handling, inverse kinematics, limits and interpolation, repeated in a closed loop.

Small steps, in the right frame, with feedback.

From a policy output to motor motion

M5.1 said a policy outputs an action, and M5.2 to M5.4 explained the geometry underneath. This lesson connects them: how an end-effector command such as “move the hand 2 cm forward” becomes joint commands the motors can follow. The end-effector is the hand, gripper or tool at the end of the arm. A common chain, used by many robot-learning systems, looks like this:

  1. The policy outputs an end-effector action, often a small change of pose plus a gripper value.
  2. A target pose is computed from the current pose and that change.
  3. Inverse kinematics (M5.3) converts the target pose into joint targets.
  4. Limits and safety checks are applied.
  5. A joint controller drives the motors toward the targets, much faster than the policy runs.

Absolute poses, deltas, and the frame they are in

An end-effector action can be an absolute pose (“go to this exact place”) or a delta (“move by this much from where you are”). Many learned policies output deltas, because small changes are easier to predict and keep working if the starting position varies. A delta has six numbers (three for translation and three for rotation) plus the gripper, which makes the 7-dimensional action vector you will see in robot datasets.

A delta is meaningless without a frame. “Move 5 cm forward” can mean forward along the world’s x-axis, or forward along the hand’s own x-axis, and the two differ whenever the hand is turned. This is the frame bookkeeping of M5.4 applied to actions:

import numpy as np

def Rz(a):
    c, s = np.cos(a), np.sin(a)
    return np.array([[c, -s], [s, c]])

hand_pos = np.array([0.4, 0.1])
hand_rot = Rz(np.deg2rad(90))                     # the hand is turned 90 degrees
delta = np.array([0.05, 0.0])                     # "5 cm along x"

base_frame = hand_pos + delta                     # delta read in the base frame
tool_frame = hand_pos + hand_rot @ delta          # delta read in the hand's own frame
print(base_frame.round(3), tool_frame.round(3))

Output:

[0.45 0.1 ] [0.4  0.15]

The same numbers, (0.05, 0), move the hand along the base x-axis in one reading and along the base y-axis in the other. A policy trained on data where deltas were in the base frame, but run on a robot that reads them in the tool frame, will move in the wrong direction while looking perfectly healthy in the logs. When a dataset or a model gives an action space, confirm the frame, the units (metres or centimetres), and whether the values are absolute or relative.

Turning a delta into joint targets

For a small hand motion the Jacobian from M5.3 gives the joint change directly: Δq ≈ J⁺ Δx. Applying that every control step, called resolved-rate control, steers the hand without solving a full inverse-kinematics problem each time. Here it is on the planar arm:

l = np.array([1.0, 1.0])

def fk(q):
    a = np.cumsum(q)
    return np.array([np.sum(l * np.cos(a)), np.sum(l * np.sin(a))])

def jacobian(q):
    s1, c1 = np.sin(q[0]), np.cos(q[0])
    s12, c12 = np.sin(q[0] + q[1]), np.cos(q[0] + q[1])
    return np.array([[-l[0] * s1 - l[1] * s12, -l[1] * s12],
                     [ l[0] * c1 + l[1] * c12,  l[1] * c12]])

q = np.array([0.4, 1.2])
dx = np.array([0.05, 0.0])                        # policy output: move the hand 5 cm along x
dq = np.linalg.pinv(jacobian(q)) @ dx             # joint change that produces it
print(dq.round(4))
print((fk(q + dq) - fk(q)).round(4))              # how far the hand actually moved

Output:

[-0.0016 -0.0478]
[ 0.05   -0.0012]

The planned joint change moves the hand by (0.05, −0.0012), very close to the requested (0.05, 0), with a small error because the Jacobian is only a first-order approximation. That error is why this is done in small steps with feedback, not as one big jump. Notice also that a straight 5 cm motion of the hand needed a specific mix of joint motions (here almost all of it from the elbow). A different arm pose would split it differently, which is the joint-space versus Cartesian-space difference from M5.2.

The result then passes through limits. A large requested motion could demand a joint change that is too fast or too far. A simple safeguard scales the whole joint step down together, which keeps its direction and so keeps the hand moving the intended way, only more slowly:

def limit_step(dq, max_step):
    scale = min(1.0, max_step / np.abs(dq).max())
    return dq * scale

big = np.array([0.5, -0.1])
print(limit_step(big, 0.1))
print(limit_step(np.array([0.05, 0.02]), 0.1))      # small steps are left alone

Output:

[ 0.1  -0.02]
[0.05 0.02]

Clipping each joint separately would change the direction of the motion and push the hand off its path, so scaling the whole step is the usual choice. Other checks reject the command altogether: a target outside the workspace (M5.2), a joint limit reached, or a configuration near a singularity (M5.3).

Two clocks: the policy and the controller

A large model may output a new action 5 to 20 times a second, while the joint controllers run at hundreds or thousands of cycles a second. The controller therefore needs a smooth path between consecutive targets. The simplest form is interpolation: spread the change over the intermediate control ticks.

q_now = np.array([0.40, 1.20])
q_target = np.array([0.31, 1.40])                  # joint target from the policy
ticks = 4                                          # controller ticks per policy step
path = [q_now + (q_target - q_now) * (i + 1) / ticks for i in range(ticks)]
print(np.array(path).round(3))

Output:

[[0.378 1.25 ]
 [0.355 1.3  ]
 [0.332 1.35 ]
 [0.31  1.4  ]]

The joints move in four equal steps and arrive exactly at the target when the next policy output is due. Each controller tick then drives the motors, typically with a feedback law that is the subject of M5.6. If a policy is slow, say because it is a very large model, the robot either moves slowly or waits between actions. This is why inference speed shows up as an explicit topic in Phase 8, and why some VLA designs produce several future actions at once to cover the gap (Phase 4).

The closed loop in action

Putting it together, here is a tiny stand-in for a policy that outputs a hand delta pointing toward a target, clipped to 5 cm per step, and the pipeline above executing it on the planar arm. The “policy” is a one-line rule, not a learned model, but the loop around it is the same one a VLA sits in:

target = np.array([1.2, 0.8])
q = np.array([0.4, 1.2])

for step in range(12):
    hand = fk(q)                                              # observation: where the hand is
    delta = np.clip(target - hand, -0.05, 0.05)               # "policy": step toward the target, at most 5 cm
    dq = limit_step(np.linalg.pinv(jacobian(q)) @ delta, 0.2) # IK step and joint limit
    q = q + dq                                                # command the joints, and they follow
print(fk(q).round(2), round(float(np.linalg.norm(target - fk(q))), 4))

Output:

[1.2 0.8] 0.0005

After 12 steps the hand is within 0.0005 of the target. Each pass observes, decides, converts to joints, limits, and moves. The gripper value in a real action is handled alongside this: it is a number, often mapped from a normalised range to a physical opening, and sent to the gripper’s own controller. Everything beyond this point, the controllers that drive the motors and the safety layers that sit around them, is the subject of Phase 9.

Common misconceptions

Misconception 1 “A delta action is just three numbers to add.”

Correction The numbers only mean something in a specific frame, and rotation deltas are not added like positions. Frame mistakes move the robot the wrong way without any error.

Misconception 2 “The policy’s output goes straight to the motors.”

Correction It passes through inverse kinematics, limits, safety checks and interpolation, and joint controllers execute it.

Misconception 3 “Clipping each joint independently is a safe way to limit motion.”

Correction It changes the direction of the joint step and pushes the hand off its path. Scaling the whole step keeps the direction.

Misconception 4 “The policy and the controller run at the same rate.”

Correction The policy is usually much slower, and the controller fills in the motion between its outputs.

Engineering takeaways

  1. End-effector actions are often a 6-D pose delta plus a gripper value.
  2. Deltas are easier to learn than absolute poses, but they need a stated frame.
  3. The same delta read in the base frame and the tool frame moves the hand in different directions.
  4. Resolved-rate control uses Δq = J⁺ Δx to turn a hand delta into joint steps.
  5. Scale a too-large joint step as a whole to keep its direction.
  6. Interpolation bridges the slow policy rate and the fast controller rate.
  7. Targets can be rejected for workspace, joint limits or singularities.
  8. Check the frame, the units and absolute versus relative for any action space.

The policy decides where the hand should go, and a whole chain of geometry and limits decides how the joints get it there.

Knowledge check

Try each question first, then reveal the explanation.

Question 1

A hand is rotated 180° from the base frame. A policy outputs a delta of (0.05, 0) intended in the tool frame. In which base-frame direction does the hand move?

Explanation

Along −x, by 5 cm. A 180° rotation turns the tool’s x-axis to point along the base’s −x direction, so (0.05, 0) in the tool frame is (−0.05, 0) in the base frame.

Question 2

A joint step is [0.4, 0.1] rad and the limit is 0.2 rad per step. After limiting by scaling the whole step, what is it?

Explanation

[0.2, 0.05] rad. The largest entry is 0.4, so the scale is 0.2 / 0.4 = 0.5, and both entries are halved, which keeps the direction.

Question 3

A policy produces actions at 10 Hz and the joint controller runs at 500 Hz. How many controller ticks fall between consecutive policy outputs?

Explanation

50. The controller ticks 500 times a second and the policy 10 times, so 500 / 10 = 50 ticks per policy action, over which the motion is interpolated.

What’s next

You can now follow an action from policy to joints. Next is the feedback idea that makes the joint controllers hit their targets.

Next lesson

M5.6 — Basic feedback control

Using measured error to correct a robot's motion.

Back to the VLA roadmap prerequisites