# Run everything in simulation

> Use dev-stack.sh to run the simulated core, rt-control, the dense daemon, NATS, the virtual pendant, the weld planner and the offline programming app on one machine, and check it with the smoke test.

URL: https://advancedmetalresearch.com/docs/guides/run-in-simulation
Section: RosieOS docs / Guides
Last updated: 2026-10-10

`dev-stack.sh`, at the repository root, runs a whole simulated cell on one Linux or WSL2 machine. Each service runs in its own session, with a pid file and a log. `stop` kills exactly what `start` began, and nothing else.

```bash
export DEV_STACK_UI_HOST=127.0.0.1 DEV_STACK_FIREWALL=0
./dev-stack.sh start                 # every service
./dev-stack.sh start rt-sim rt-control olp ui   # or only the ones you name
./dev-stack.sh status
./dev-stack.sh logs olp              # tail one or more logs
./dev-stack.sh restart ui
./dev-stack.sh stop
```

With no names, a command applies to every service. If you are new to the stack, start with the [Quickstart](https://advancedmetalresearch.com/docs/get-started/quickstart).

## Services

`start` launches the services in this order. It waits for `rt-sim`, `rt-control` and `pendant` to become healthy before it moves on.

| Service | What runs | Endpoint | Needs | If missing |
|---|---|---|---|---|
| `catalog` | External program-catalog script | `http://127.0.0.1:8787` | `CATALOG_DAEMON_SCRIPT` | Skipped. The script is not in this repository. |
| `nats` | `nats-server -a 127.0.0.1 -p 14222 -m 18222` | `nats://127.0.0.1:14222` | `nats-server` 2.14+ at `DEV_STACK_NATS_BIN` | Skipped |
| `rt-sim` | `rosie-rt-core-sim` with a compiled simulation machine | native `ipc.sock` | `make -C rt-core control sim` (built automatically if missing) | — |
| `rt-control` | `rt-control --backend simulation --pair-id local-dev --pair-revision 1` | `control.sock`, `jog.sock` | `rt-sim` healthy | — |
| `daemon` | `joint_trajectory_daemon` for cell `dev-cell` | TCP `127.0.0.1:8797` | `make -C motion-server/joint-trajectory/v1 all` | Skipped |
| `pendant` | Virtual pendant bridge, `robot-v4-sim` adapter | `http://127.0.0.1:51712` | Go; built from `steamdeck/virtual` on start | — |
| `motion` | Weld planner, `pixi run -e motion motion-serve` | `http://127.0.0.1:8796` | `weld_planner/v1` motion env, NVIDIA GPU | Fails. Its log says why. |
| `olp` | `offline-programming/v1/start-offline-programming.sh` | `http://127.0.0.1:8794` | Go, the Tesseract env, a C++ compiler | — |
| `ui` | Vite for the OLP UI | `http://127.0.0.1:5189/offline-programming/v1/ui/` | `npm ci` in `offline-programming/v1/ui` | — |

`daemon`, `pendant` and `olp` all refuse to start until `rt-control` is healthy. Before each one starts, the stack runs `motion-server/v1/local-rt-core.sh bind`. That command writes a consumer binding for the running simulator, so every client targets the same pair and configuration digest.

The `olp` log is the one to read when something is off. It reports its state in lines prefixed `olp-start:`:

- `rails:` says whether the seam worker (the weld planner's `default` env) is available.
- `motion:` names the weld planner origin used for planning and dense playback.

`olp` takes the longest to start, because the launcher builds whatever is missing and waits up to 270 s for its local simulator.

## The simulated machine

By default, `local-rt-core.sh prepare` starts from `rt-core/config/machines/simulation/simulation-program.json`, nine flat axes named J1 to J9. It rewrites each axis's travel and velocity limits from the Rosie 1400 URDF, switches the axes to a simulation drive profile that supports Home, and requires Home on every axis. It then compiles the result with `rtctl compile`.

The simulated robot therefore behaves like a real cell, in order: Home, Arm, then move.

To bind the actual Rosie 1400 description instead, set:

```bash
export DEV_STACK_MACHINE_CONFIG=rt-core/config/machines/simulation/simulation-rosie1400.json
```

The cell then serves the same robot description that OLP plans against. But that machine uses the real drive profile, and the simulated bus does not answer its Home objects. Home is refused, so nothing arms or jogs on it. Leave it unset unless you are working on that gap.

> [!NOTE] The simulator runs rt-core's state machine over a simulated bus. It does not model dynamics, contact or the drives' own control loops, and it does not qualify anything for powered motion. The separate `mujoco-sim/v1` project is an experimental physics model, not a stand-in for the controller.

## Where files go

| Path | Default | Contents |
|---|---|---|
| Stack directory, `ROSIE_DEV_STACK_DIR` | `${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/rosie-stack-$UID` | pid files, `<service>.log`, readiness files |
| Runtime directory, `ROSIE_LOCAL_RT_DIR` | `${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/rosie-dev-rt-$UID` | `ipc.sock`, `control.sock`, `jog.sock`, the simulator's `metrics.json` |
| Build directory, `ROSIE_LOCAL_RT_BUILD` | `rt-core/build/dev-stack` | `machine.json`, `config/<sha256>/`, `binding.env`, `olp-cells.json`, `olp-rt-core.json`, `daemon-rt-core.json`, `dense-plans/` |

`binding.env` exports the variables a client needs to reach the simulator: `ROSIE_RT_CONTROL_SOCKET`, `ROSIE_RT_JOG_SOCKET`, the pair, the configuration digest and the URDF path and hash. Source it to point your own tools at the stack:

```bash
source rt-core/build/dev-stack/binding.env
rt-core/build/rtctl describe --socket "$ROSIE_RT_CONTROL_SOCKET" --json | head -c 300; echo
```

To change the simulation paths, stop the whole stack first. `start` refuses to change `ROSIE_LOCAL_RT_DIR` or `ROSIE_LOCAL_RT_BUILD` under a running stack.

## Useful overrides

| Variable | Default | Effect |
|---|---|---|
| `DEV_STACK_UI_HOST` | `127.0.0.1`; `0.0.0.0` when a Tailscale address is found | Where the UI binds |
| `DEV_STACK_UI_MODE` | `dev`; `built` when a Tailscale address is found | `dev` hot-reloads. `built` serves a compressed bundle and needs `restart ui` after edits. |
| `DEV_STACK_FIREWALL` | `1` | `0` skips the helper that adds a `ufw` rule for remote UI access |
| `DEV_STACK_MACHINE_CONFIG` | `rt-core/config/machines/simulation/simulation-program.json` | The simulated machine, described above |
| `DEV_STACK_PENDANT_PORT` | `51712` | Virtual pendant bridge port |
| `DEV_STACK_DENSE_INGEST` | `127.0.0.1:8797` | Dense daemon TCP ingest |
| `STACK_WAIT_SECS` | `300` | How long `start` waits for health |

The complete list is in [Ports, sockets and environment](https://advancedmetalresearch.com/docs/reference/ports-and-environment).

> [!WARNING] `DEV_STACK_OLP_CELLS` and `DEV_STACK_OLP_REMOTE` add real cells to the OLP machine list, alongside the local simulation. Once they are set, the same UI can arm and move hardware. Read [Connect to a cell](https://advancedmetalresearch.com/docs/guides/connect-a-cell) and the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model) first.

## Run the smoke test

The composed smoke test checks that the stack works end to end. It builds the core and both motion servers, and starts `rt-sim`, `rt-control` and `pendant` in a private directory. It then runs two Go tests against them: one dense program that must complete, and one OLP jog that must move the simulated axes.

```bash
export TMPDIR=/dev/shm/rt-core/smoke
mkdir -p "$TMPDIR"
bash motion-server/v1/tests/dev-stack-smoke.sh
```

It ends with `dev-stack smoke: rt_core PASS (public jog motion and dense completion)`, and stops what it started.

## Without the dev stack

The OLP launcher also works on its own:

```bash
bash offline-programming/v1/start-offline-programming.sh
```

If no dev-stack binding is live, it builds rt-core and starts its own simulator and `rt-control` in a temporary directory. It stops them again when you press Ctrl+C.

## Sources

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

- `dev-stack.sh:1-80,172-193,240-297,299-395,436-470,555-613`
- `motion-server/v1/local-rt-core.sh:6-130`
- `motion-server/v1/tests/dev-stack-smoke.sh:1-30`
- `motion-server/joint-trajectory/v1/Makefile:7,16-17`
- `offline-programming/v1/start-offline-programming.sh:16-36,68-110,490-515,620-668`
- `weld_planner/v1/pixi.toml:156`
- `rt-core/config/machines/simulation/simulation-program.json`
- `rt-core/config/machines/simulation/simulation-rosie1400.json`
- `steamdeck/virtual/cmd/local/main.go`
