> ## Documentation Index
> Fetch the complete documentation index at: https://docs.roboticks.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Sim Runners

> Gazebo Harmonic and Webots in hosted or self-hosted GPU pools. When sim minutes are worth the spend and how to mark tests so the scheduler routes correctly.

# Sim runners

A simulation test is any test that needs a 3D physics world to evaluate the system under test. Roboticks supports **Gazebo Harmonic** and **Webots** today, on hosted G4dn.xlarge Spot Fleet GPUs or self-hosted via the [`roboticks-runner`](https://github.com/roboticks-io/roboticks-runner) binary on your own GPU box.

<Info>
  **Sim minutes are the only meaningful cost dimension on the platform.** They exist because GPU time is expensive. Reserve sim tests for the cases where headless ROS2 won't do.
</Info>

## When is a sim test worth it

| Use sim when                                                    | Use headless ROS2 when                                                 |
| --------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Test asserts on physics (collision, traction, suspension)       | Test asserts on message contracts, topic publication, parameter wiring |
| Test exercises perception against a 3D world                    | Test exercises perception against a recorded MCAP                      |
| Test requires a full navigation stack with map + plan + execute | Test exercises planner alone with mocked map server                    |
| You're verifying a safety scenario that needs the actuator loop | You're verifying a unit's response to a known input                    |

The default should be **headless ROS2**. Push the sim envelope only when the headless test would lie.

## Mark the test

Two parameters on `@requires_sim`:

```python theme={null}
from roboticks import requires_sim

@requires_sim("gazebo", gpu=True)
def test_navigation_through_warehouse(...):
    ...

@requires_sim("webots", gpu=False)
def test_differential_drive_kinematics(...):
    ...
```

* `engine`: `"gazebo"` or `"webots"`. The scheduler refuses to dispatch a `gazebo` test to a `webots` runner.
* `gpu`: When `True`, routes to G4dn.xlarge Spot Fleet (hosted) or any self-hosted runner labelled `gpu=true`. When `False`, the scheduler can use a CPU-only sim runner for lightweight scenarios.

See [decorator reference](/sdk/decorators) for the full signature.

## The job router

```mermaid theme={null}
%%{init: {"theme": "neutral", "themeVariables": {"primaryColor": "#4040ff"}} }%%
flowchart LR
    T["Test with<br/>@requires_sim('gazebo', gpu=True)"] --> R{Job router}
    R -->|self-hosted matches?| SH["Self-hosted pool<br/>(label: gpu=true)"]
    R -->|else, hosted enabled?| H["Hosted G4dn.xlarge<br/>Spot Fleet"]
    R -->|neither| F["Queue + warn"]
```

Routing priority:

1. **Self-hosted pools** with matching labels take precedence (free of sim-minute charges).
2. **Hosted pools** take the rest. Sim minutes are metered per the [pricing page](/pricing).
3. If neither pool can serve the request, the job queues with a warning the dashboard surfaces immediately.

You set the policy at the project level: **Settings → Runners → Routing**. Default is "prefer self-hosted; fall back to hosted with consent".

## Hosted sim runners

| Pool                | Instance                    | Image                          | GPU       |
| ------------------- | --------------------------- | ------------------------------ | --------- |
| `hosted-ros2-cpu`   | Fargate Spot, 2 vCPU / 4 GB | `ros:humble` + `roboticks`     | —         |
| `hosted-gazebo-gpu` | G4dn.xlarge Spot Fleet      | `ros:humble` + Gazebo Harmonic | NVIDIA T4 |
| `hosted-webots-gpu` | G4dn.xlarge Spot Fleet      | `ros:humble` + Webots          | NVIDIA T4 |

Cold-start ranges from 30 s (Fargate cached image) to 90 s (Spot Fleet cold scale-up). Image pull and spin-up are **not** billed — only wall-clock the job runs.

## Self-hosted sim runners

Run the Go binary, declare your GPU, and you're in the pool. See [Runners → Installation](/runners/installation) for the full setup. The relevant excerpt for sim:

```yaml theme={null}
# /etc/roboticks/runner.yaml
runner:
  name: sim-box-01
  labels:
    sim: gazebo
    gpu: "true"
    gpu_model: NVIDIA-RTX-A6000
  capacity: 2  # concurrent jobs
```

The runner reports `nvidia-smi` output on enrolment; the scheduler uses it to match `gpu=True` jobs to physical GPUs.

## Cost realism

A typical hosted Gazebo nav test is 60–120 s of wall-clock per run. A nightly suite of 50 such tests, running every weekday, on the Team tier (500 minutes included):

* 50 tests × 90 s = 75 min / night.
* 20 weekdays × 75 min = 1,500 min / month.
* 1,500 − 500 (included) = 1,000 metered minutes.
* 1,000 × $0.10 = **$100 / month overage\*\* on a single team running this cadence.

The self-hosted path pays the GPU once and runs free. The hosted path pays per-minute and amortises across no one. Pick deliberately.

## Image customisation

Hosted images are pinned. If you need a customised Gazebo world or extra `apt` packages, two options:

1. **Bundle in your test tree.** The runner makes `tests/sim_assets/` available to launched worlds. Best for project-specific worlds.
2. **Bring your own image** on a self-hosted runner. The runner pulls the image you point it at, including your custom Gazebo or Webots build.

Hosted custom images land in Enterprise via a managed image registry.

## Next

<CardGroup cols={2}>
  <Card title="Runners overview" icon="server" href="/runners/overview">
    Hosted vs self-hosted, pools, labels, autoscaling.
  </Card>

  <Card title="Self-host a runner" icon="microchip" href="/runners/installation">
    Get a GPU box into the pool.
  </Card>

  <Card title="Pricing" icon="dollar-sign" href="/pricing">
    How sim minutes are counted.
  </Card>

  <Card title="Launch testing" icon="rocket" href="/testing/launch-testing">
    Multi-node sim test patterns.
  </Card>
</CardGroup>
