# Motion paths and planning

> The four ways a RosieOS client moves the robot, who holds authority on each path, which rt-control lane each one uses, and what is verified before and during the motion.

URL: https://advancedmetalresearch.com/docs/concepts/motion-and-planning
Section: RosieOS docs / Concepts
Last updated: 2026-10-10

Every motion in RosieOS reaches the drives the same way: a client holds the `rt-control` lease, sends one of three kinds of motion request, and the 1 kHz core decides at its motion start gate whether the motion may begin. What differs between the paths is who the client is, how it gets its authority, and how much checking happens before the request reaches `rt-control`.

> [!WARNING] **Energised motion.** This moves the robot. RosieOS has no software E-stop: keep the hardware E-stop within reach and clear the cell before you arm. Jog, moves and Home are checked against joint limits only, not against collisions. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

![Four motion paths. The OLP server, the Cartesian motion server, the dense trajectory daemon and your own code all send requests to rt-control, which forwards them to the motion start gate in rosie-rt-core.](https://advancedmetalresearch.com/assets/docs/motion-paths.svg)

*Figure: Four motion owners, one public API, one start gate. The red edge is the core's motion start gate, which every motion passes.*

## The three kinds of motion request

`rt-control` accepts motion in three forms. Each one ends at the same [motion start gate](https://advancedmetalresearch.com/docs/get-started/safety-model#motion-start-gate).

| Lane | Operations | What the client sends | Used by |
|---|---|---|---|
| **Jog** | `begin_jog`, datagrams on `jog.sock` (or WebSocket frames on a remote cell), `end_jog` | Per-axis velocities in rad/s or m/s, each update with its own deadline | OLP joint and Cartesian jog, the Cartesian motion server |
| **Trajectory** | `prepare_trajectory`, `start_trajectory` | A short list of timed points: `time_ns`, positions in rad or m | OLP joint and Cartesian moves, the Cartesian motion server's position moves |
| **Program** | `prepare_program` (`POST /v1/program`), `start_program` | An immutable dense trajectory in the [`.rdt` format](/docs/reference/rdt-format) | OLP Load and Play, the dense trajectory daemon |

Home is a fourth, separate operation (`home`). It runs the drive's native homing on the named axes.

The operations themselves are documented in the [rt-control HTTP API](https://advancedmetalresearch.com/docs/apis/rt-control-http). Their lease and fence rules are in [Control authority](https://advancedmetalresearch.com/docs/concepts/control-authority).

## The four motion owners

| Path | Owner process | How a client reaches it | Extra authority layer | rt-control lanes |
|---|---|---|---|---|
| [Offline programming](https://advancedmetalresearch.com/docs/concepts/motion-and-planning#olp) | OLP server | HTTP on `127.0.0.1:8794` | Selected-target generation headers | Jog, trajectory, program, Home |
| [Cartesian motion server](https://advancedmetalresearch.com/docs/concepts/motion-and-planning#cartesian) | `robot-v4-cartesiand` | UDP intent packets and NATS commands | NATS leader lease | Jog, trajectory, Home |
| [Dense trajectory daemon](https://advancedmetalresearch.com/docs/concepts/motion-and-planning#dense) | `joint_trajectory_daemon` | TCP on `127.0.0.1:8797` for bytes, NATS for play | NATS leader lease | Program, Home |
| [Your own client](https://advancedmetalresearch.com/docs/concepts/motion-and-planning#direct) | Your process | The Go or C++ client | None beyond the rt-control lease | Any |

The owners share one `rt-control`. Whichever holds the lease is the only one that can move the robot; the others are refused with `control_already_owned` until it releases. See [One controller at a time](https://advancedmetalresearch.com/docs/get-started/safety-model#one-controller).

### Offline programming

The OLP server is the operator path, and the only one with plan-time verification. It holds the `rt-control` lease on behalf of a browser or the v5 pendant and exposes the machine-control routes under `/api/offline-programming/v1/dense-execution/`.

- **Programs.** Load fetches a `.rdt` by digest from the weld planner. The planner writes one only when every seam and connecting move passes its verifier, so a program that failed verification has nothing to load. Load then checks the plan's identity and robot, requires Home on every configured axis, acquires the lease and calls `prepare_program`. Play calls `start_program`. See [What is verified before motion](https://advancedmetalresearch.com/docs/get-started/safety-model#what-is-verified).
- **Joint jog.** One axis at a time, at a fraction of that axis's described maximum velocity. The browser sends a hold every 50 ms. Each hold lives for the cell's jog input age (250 ms on a LAN cell), then the axis ramps to a hold.
- **Cartesian jog.** A base- or tool-frame twist. OLP asks `robot-v4-cartesiand --resolve-only` for joint velocities from the measured pose, scales them to the joint ceilings, and sends them on the same jog lane.
- **Joint and Cartesian moves.** A bounded displacement, planned by OLP as a per-joint trapezoid (joint moves) or as IK waypoints at 1 mm or 0.25° spacing (Cartesian moves), then sent with `prepare_trajectory` and `start_trajectory`.
- **Home.** Native Home on all configured axes or on a subset.

Joint jog, moves and Cartesian jog all plan at 75% of each axis's described maximum velocity, times the operator's speed fraction. The 75% is a measured reserve for drive overshoot.

A mutating request must carry the selected target's generation and cell ID in `X-RT-Target-Generation` and `X-RT-Target-Cell`, so a request that a browser issued against one cell cannot land on another. OLP also requires a browser heartbeat: if none arrives for 5 s while OLP holds the lease, it stops the machine with `ui_heartbeat_lost`.

See the [Offline programming HTTP API](https://advancedmetalresearch.com/docs/apis/olp-http) and the guide [Connect to a cell and run a program](https://advancedmetalresearch.com/docs/guides/connect-a-cell).

### Cartesian motion server

`robot-v4-cartesiand` turns a stream of UDP intent packets into Cartesian jog. Each packet carries six normalised axis values in [-1, 1] and a speed scale. The server maps them to at most 0.2 m/s linear and π rad/s angular, slews them at 0.75 m/s² and 540°/s², resolves joint velocities through a damped Jacobian with joint-limit and singularity scaling, and streams them on the `rt-control` jog lane. Each output's deadline is at most 250 ms after the packet arrived.

Its authority has two layers. It holds the `rt-control` lease itself, and it grants its own NATS leader lease to one controller at a time. Every motion packet and NATS command must carry the current leader lease ID and fence epoch. Releasing the deadman, a neutral packet, a stale packet or a fence mismatch halts the jog. Joint jog, go-home without an admitted run, and weld I/O are refused on the `rt_core` backend.

See the [Cartesian motion server](https://advancedmetalresearch.com/docs/apis/cartesian-motion-server).

### Dense trajectory daemon

`joint_trajectory_daemon` is a store-and-play path for `.rdt` programs that come from somewhere other than OLP. The data plane is TCP: `upload` stores and validates a blob and never moves anything. The control plane is NATS: a controller acquires the daemon's leader lease, then sends `play` with the exact six-field plan identity.

On `leader_acquire` the daemon takes the `rt-control` lease. On `preload` it calls `prepare_program`. On `play` it runs native Home on any axis whose Home is not valid (or restores the saved anchor, if configured), enables and arms the axes, waits for readiness, and calls `start_program`.

> [!IMPORTANT] Programs played through the dense daemon are checked against the `.rdt` format rules and rt-core's native limits only. The daemon does not ask the weld planner or check the plan's robot and cell identity. Treat it as a direct path.

See the [Dense trajectory daemon](https://advancedmetalresearch.com/docs/apis/dense-trajectory-daemon).

### Your own client

Your code can call `rt-control` directly through the [Go SDK](https://advancedmetalresearch.com/docs/apis/go-sdk) or the [C++ client](https://advancedmetalresearch.com/docs/apis/cpp-client). Use one of these for anything that moves. The SDKs renew the lease in the background.

`rtctl` cannot drive motion in practice. It never renews the 500 ms lease, so the lease expires between commands and outputs are inhibited. Use `rtctl` for inspection and one-off setup, not motion. [Your first motion](https://advancedmetalresearch.com/docs/get-started/first-motion) walks through a complete SDK program against the simulator.

## What is checked, and where

| Check | Weld program through OLP | Program through the dense daemon | Jog | OLP moves | Home |
|---|---|---|---|---|---|
| Weld planner verifier: collisions, penetration, limits, tracking | Yes | No | No | No | No |
| Plan identity and robot/cell identity | Yes, at Load | Plan identity only, at preload and play | n/a | n/a | n/a |
| Planned at 75% of described velocity | Planner's own limits | Producer's own limits | Yes (OLP); 0.2 m/s and π rad/s caps (motion server) | Yes | n/a |
| `.rdt` format validation | Yes | Yes | n/a | n/a | n/a |
| rt-core native admission: position limits, velocity, continuity | Yes | Yes | Per update | Yes | n/a |
| Motion start gate and per-cycle output permit | Yes | Yes | Yes | Yes | Yes |

rt-core checks joint limits and rates. It does not check collisions with the cell, the positioner or the part. Only the weld planner's verifier does that, and only for planned weld programs.

## Starting from rest

A trajectory or program must continue from the held position. If the robot is at rest and the first point is within the axis's completion tolerance of the held position, the core inserts a short, bounded alignment ramp to the first point. Any larger gap is refused. So the first point of a motion may be slightly offset from the reported position, but never by more than that tolerance.

Inside a dense program, segments hand over in place: each segment starts and ends at rest, and the next one starts where the last one ended. The core never interpolates across a segment boundary. See [Segments](https://advancedmetalresearch.com/docs/reference/rdt-format#segments).

## Stopping

Every path ends in the same software stops: Stop, Halt, Release and lease expiry. Release runs Stop and then gives up the session. The owners add their own triggers:

| Path | Also stops on |
|---|---|
| OLP | Browser heartbeat lost for 5 s, lease renewal lost, a refused or failed operation (OLP runs Stop and Release before it reports the refusal; a slow or unsent request is the exception), a new target selection |
| Cartesian motion server | Deadman released, neutral or invalid packet, leader lease lost or changed, NATS `stop` or `disarm` |
| Dense daemon | NATS `stop` (always accepted, never fenced), leader lease released or replaced, any native refusal |

None of these is an emergency stop. See [Software stops](https://advancedmetalresearch.com/docs/get-started/safety-model#software-stops).

## Related pages

- [Safety model](https://advancedmetalresearch.com/docs/get-started/safety-model)
- [Architecture](https://advancedmetalresearch.com/docs/concepts/architecture)
- [Control authority](https://advancedmetalresearch.com/docs/concepts/control-authority)
- [Weld planning and verification](https://advancedmetalresearch.com/docs/concepts/weld-planning-and-verification)
- [Ports, sockets and environment](https://advancedmetalresearch.com/docs/reference/ports-and-environment)

## Sources

Written from these files in the RosieOS repository (https://github.com/advanced-metal-research/RosieOS):

- `rt-core/adapters/rosie/control/http.go:85-168`
- `rt-core/include/motion_readiness.hpp:97-130`
- `rt-core/include/start_alignment.hpp:24-55`
- `offline-programming/v1/server.go:190-206`
- `offline-programming/v1/dense_execution.go:24-63`
- `offline-programming/v1/internal/denseexec/rt_core.go:556-616,735-851,980-1031`
- `offline-programming/v1/internal/denseexec/session.go:22`
- `offline-programming/v1/internal/denseexec/joint_jog.go:17-31,306-361`
- `offline-programming/v1/internal/denseexec/joint_move.go:17-19,56-97,437-491`
- `offline-programming/v1/internal/denseexec/cartesian_jog.go:52-62,118-208`
- `offline-programming/v1/internal/denseexec/cartesian_move.go:17-51`
- `motion-server/v1/src/cartesian_resolver.hpp:38-42,99-160`
- `motion-server/v1/src/robot_v4_cartesian_daemon.cpp:58-103`
- `motion-server/v1/src/robot_v4_cartesian_nats_protocol.hpp:2487-2519`
- `motion-server/v1/src/rt_core_cartesian_runtime.hpp:184-544`
- `motion-server/joint-trajectory/v1/src/joint_trajectory_daemon.cpp:1-9`
- `motion-server/joint-trajectory/v1/src/command_dispatch.hpp:66-207`
- `motion-server/joint-trajectory/v1/src/rt_control_executor.hpp:48-318`
- `weld_planner/v1/python/weldplan/dense_joint_trajectory.py:700-705`
- `weld_planner/v1/python/weldplan/native_admission.py:204-250`
- `rt-core/tools/rtctl/control.go:73-83`
