Skip to content

Getting Started

estherRay edited this page Sep 28, 2026 · 12 revisions

Get LOTUSim installed and running a live example in about 10 minutes!

LOTUSim is installed and run through Nix, a package manager that downloads everything the simulator needs (ROS 2, Gazebo, and all their dependencies) into its own isolated location on your computer. It won't conflict with or change anything else you have installed.

You don't need to know Nix to use LOTUSim. Follow the steps below in order and you'll have a working simulation in a few minutes.

LOTUSim runs natively on Linux and macOS.

On Windows? Nix doesn't run natively on Windows. Skip ahead to Windows users. You'll run everything inside WSL2, then the rest of this guide applies exactly as written.

Contents


One-time setup

Do this section once, no matter which path you choose.

Step 1 - Install Nix

LOTUSim is installed and run through Nix, a package manager that downloads everything the simulator needs (ROS 2, Gazebo, and all their dependencies) into its own isolated location on your computer. It won't conflict with or change anything else you have installed. You don't need to know Nix to use LOTUSim. Open a terminal and run:

curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install | sh -s -- --daemon

Once it finishes, close your terminal window and open a new one so the changes take effect. Other install options (for special setups) are listed on Nix website.

Step 2 - Add the ROS package cache

LOTUSim depends on Gazebo/ROS packages that aren't part of Nix's normal download cache. Without this step, the very first time you build or run LOTUSim, Nix will quietly compile those packages from scratch, that can take about an hour, with no progress message explaining why. This step lets Nix download them pre-built instead, which takes seconds.

It needs admin rights, but only once per machine:

sudo tee -a /etc/nix/nix.conf <<'EOF'
experimental-features = nix-command flakes
extra-substituters = https://ros.cachix.org
extra-trusted-public-keys = ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo=
EOF
sudo systemctl restart nix-daemon   # macOS: sudo launchctl kickstart -k system/org.nixos.nix-daemon

Without a trusted key the cache's signatures do not verify, so Nix builds from source anyway. That is why this step is not optional. A single-user install has no daemon and no trusted-users, and can skip it. If systemctl isn't found (e.g. macOS or a single-user Nix install), restart whichever service manager your install uses, or simply restart your computer. /etc/nix/nix.conf is the system-wide file, so the keys are trusted for every user and the signatures verify.

Step 3 - Install the GPU bridge

Skip this step you just want to use Podman (Path A, Option 2).

What is a GPU bridge?

LOTUSim's 3D window needs to talk to your graphics card. On most Linux distributions, Nix programs can't see your machine's GPU by default. Without this bridge, the simulation window may not open.

Install the one matching your graphics card:

nix profile add github:nix-community/nixGL#nixGLIntel     # Intel graphics

On an NVIDIA or hybrid/Optimus machine, also add the NVIDIA bridge so rendering uses the discrete GPU instead of falling back to Intel — this reads your driver's exact version off the running machine, so it needs --impure, and NVIDIA's userspace driver is unfree:

NIXPKGS_ALLOW_UNFREE=1 nix profile add --impure github:nix-community/nixGL#nixGLNvidia

See the nixGL project if you're unsure.

You're now ready to run LOTUSim!


Pick your path

There are three ways to get LOTUSim, depending on what you want to do:

I want to... Use this path
Just try it out, nothing left behind afterwards Path A - Run without installing
Have lotusim available any time, like a normal app Path B - Install LOTUSim
Change or contribute to LOTUSim's code Path C - Set up a dev environment

Path A - Run without installing

Good for a first try. Nothing is added to your system permanently (aside from Nix itself).

Pick one of these two:

Option 1 - via Nix

nix run github:naval-group/LOTUSim -- run

Option 2 - via a container (Podman)

You'll need Podman version 6 or later installed, check with podman --version, and upgrade if it's older. Then:

podman run --rm ghcr.io/naval-group/lotusim

For Nvidia cards, you will need the NVIDIA Container Toolkit installed and configured to use Podman, or the 3D window won't render.

Once it's running, jump to Try your first scenario.


Path B - Install LOTUSim

This puts lotusim and lotusim-ui permanently on your computer, like installing a normal application.

1. Install it

nix profile add github:naval-group/LOTUSim github:naval-group/LOTUSim#ui

2. Run it

lotusim run

In a second terminal:

lotusim-ui

Then open http://localhost:8080 in your browser.

Now head to Try your first scenario.


Path C - Set up your dev environment

1 - If you're going to modify LOTUSim's code, you want the developer workflow:

git clone https://github.com/naval-group/LOTUSim.git
cd LOTUSim
nix develop             # drops you into a shell with ROS 2, Gazebo, and build tools
mise run build          # builds the workspace
mise run sim            # runs it. You can add the param --gui

This gets the core simulator running. If you also want to work on the web UI or the physics engine (xdyn), or you want the full task reference — see Developing for LOTUSim below.


Windows users

Nix isn't natively supported on Windows, so you'll run it inside WSL2 (Windows Subsystem for Linux). It's a lightweight Linux environment that runs alongside Windows. Once it's set up, everything else in this guide (Paths A, B, and C) works exactly as written, from inside your WSL2 terminal.

  1. Open PowerShell as Administrator and run:
   wsl --install -d Ubuntu-24.04
  1. Enable mirrored networking, so ROS 2's device discovery works without extra configuration. Open:
   notepad $env:USERPROFILE\.wslconfig

and add:

   [wsl2]
   networkingMode=mirrored
  1. Restart WSL, then open your Ubuntu shell and follow any of the paths above (A, B, or C) exactly as written.

If you hit networking issues after this, check your Windows Firewall, mirrored networking is sometimes blocked by default.


Try your first scenario

  1. With LOTUSim and the web UI both running, open the UI in your browser:
  2. In the left panel, under "Launch Scenario", select demo.yaml.
  3. Click Launch Scenario. You should see an arrow representing an LRAUV (an underwater vehicle) appear and start moving. 🎉

Want to see more? Check the Tutorial page for further examples, or run lotusim --help to see every scenario/world your build includes.


Optional: Set up the 3D rendering

This is only needed if you want photorealistic rendering through Unity. The demo scenario above works without it.

  1. Install Unity
  • Download the Unity Hub and follow the on-screen installation guide.
  • Create or sign in to your Unity account (UDN), choose a license type, and then install the Unity Editor through Unity Hub.
  • Use Unity version 2022.3.18f1 (required for HDRP water system).
  1. Clone the Unity project
git clone --recurse-submodules https://github.com/naval-group/LOTUSim-Unity-modules
cd LOTUSim-Unity-modules
git submodule update --remote --merge
  1. Open the project
  • In Unity Hub -> Projects, add the "LOTUSim-Unity-modules" folder and open it.
  • Once the project opens, go to the Project tab (bottom panel), open the Scenes folder, and load one of the scenes.
  1. Launch a scene

In Unity Hub, open a scene from the imported project (for example, the defense scenario).

If the scene appears black, check that your graphics drivers are properly installed.


Developing for LOTUSim

This section covers building LOTUSim from source, working on the web UI, developing the physics engine (xdyn), and how the pieces fit together internally. It assumes you've already done the one-time setup above. If you just want to run LOTUSim rather than modify it, Path C above already gives you the short version.


Tasks

mise run <task>. The devShell provides mise, so no separate install.

Task What it does
build Build the colcon workspace
rebuild Clean, then build
clean Remove build/, install/ and log/
sim Run a simulation world
check Headless smoke test: vessels spawn, sensors render, gz logs no error
test Run the gtest suite (advisory — does not gate the build)
test:assets Check that --assets-path composes assets roots instead of replacing them
lint:tidy Run clang-tidy over the workspace sources
doc Generate the doxygen documentation
image Build the container image and load it into docker or podman

mise tasks prints this list.


Which command to use

Every session

nix develop            # once per shell
mise run build
mise run sim --gui

Before a PR

mise run check         # headless smoke test
mise run test:assets   # --assets-path composition

Before publishing — rare, and the only thing that exercises the packaged artifact:

nix run .#lotusim -- run --gui

mise run sim runs install/, which the flake never sees. The wrapper, the state seeding, and the assets derivation live only in flake.nix, so nothing in the everyday loop covers them.

Use --gui, not headless. The GPU-driver bridge is fatal only for --gui; a headless run warns and carries on, so it passes whether or not the bridge survived into the packaged environment. mise run check does not close that gap either, it calls gz from the devShell and never goes through the flake's wrapper.

What users run - not part of the dev loop, but worth knowing what you are shipping:

nix profile add github:naval-group/LOTUSim github:naval-group/LOTUSim#ui
lotusim run --gui
lotusim-ui

nix profile upgrade LOTUSim ui       # move both to the current main

lotusim is not on PATH inside nix develop; it is a flake package, not a devShell tool. Use mise run sim there.

Code it runs Worlds resolved from
mise run sim install/ — your last mise run build repo assets/worlds/
nix run .#lotusim store build of the git-tracked tree ~/.local/share/lotusim, then store
lotusim run store build, frozen at install time same

Anything reached through the flake sees git-tracked files only. Modifications to tracked files are picked up; a brand-new file needs git add first or it is silently absent.


Setting up the full stack

Path C above gets the core simulator running. LOTUSim also has a web UI (two repos) and a physics engine (a separate project, xdyn) — here's how to develop each.

Web UI

The UI lives in two repositories, each packaged as its own flakee:

Frontend, with the packaged backend:

git clone https://github.com/naval-group/LOTUSim-UI-frontend.git
cd LOTUSim-UI-frontend
nix develop                                   # nodejs
npm ci && npm run dev                         # vite, http://localhost:5173
nix run ./path/to/LOTUSim#ui-backend          # the other half

Backend, with the packaged frontend:

git clone https://github.com/naval-group/LOTUSim-UI-backend.git
cd LOTUSim-UI-backend
nix develop ./path/to/LOTUSim#ui-backend      # node, ROS, and lotusim_msgs on AMENT_PREFIX_PATH
npm run setup                                 # rclnodejs ships no prebuilt for this Node, so it compiles
npx generate-ros-messages
npm run dev                                   # go to http://localhost:8080
nix run ./path/to/LOTUSim#ui-frontend         # the other half

The backend's shell has to come from the main LOTUSim repository, because only LOTUSim generates its messages. Editing interfaces/ there is enough to change what the UI can talk about: packages.messages builds from your working tree, so the next nix run .#ui carries the new types.

Before pushing, check the change still works in the packaged build, which is what users actually get:

nix run .#ui --override-input lotusim-ui-frontend git+file:///path/to/LOTUSim-UI-frontend

Running the whole packaged UI from the main repo:

nix run .#ui                 # open http://localhost:8080; the backend serves the API on :5000

packages.ui provides a binary named lotusim-ui, so nix profile add github:naval-group/LOTUSim#ui puts it on PATH permanently. #ui-backend and #ui-frontend are lotusim-ui-backend and lotusim-ui-frontend, and run either half alone.

The UI is a separate package on purpose, not a lotusim subcommand: its closure carries no gz- paths, against the full Gazebo stack for lotusim. Folding them together would put Gazebo behind the UI and the whole Node stack behind the simulator.

Dynamism engine (xdyn)

The devShell puts a pinned xdyn-for-cs on PATH, taken from lxdyn's published image. To use a local build instead:

cd /path/to/lxdyn
zig build
./zig-out/bin/xdyn-for-cs        # bind it to the same host:port the scenario's `uri:` names

There is no config pointing LOTUSim at one build or the other, whichever process holds that port when the vessel activates is the one it talks to. Run your build by explicit path, or from outside nix develop, since a bare xdyn-for-cs inside this repo's devShell resolves to the pinned one. nix run does not reach a local checkout: lxdyn's flake declares no apps.

There is no reconnect. activateInterface retries three times at spawn and then gives up, with no handler that re-dials a dropped connection. Restarting a rebuilt xdyn-for-cs leaves an already-spawned vessel's connection dead -> respawn the vessel, not the whole simulation.


Reference

Upgrading an installed build

New versions don't appear automatically, run this whenever you want to update:

nix profile upgrade LOTUSim ui       # by name, as `nix profile list` shows them
nix profile upgrade --all            # every entry, nixGL included

Ad-hoc runs sit behind a second cache. Nix remembers a flake reference's resolution for tarball-ttl - one hour by default - so a nix run inside that window can resolve the previous revision:

nix run github:naval-group/LOTUSim --refresh -- run --gui

--refresh forces the lookup, and nix profile upgrade resolves through the same cache, so it wants the flag too when you are chasing a revision pushed minutes ago.

nix profile list prints the locked flake URL and store path behind each entry, which is what to read when an installed build behaves differently from mise run sim: they are different artifacts, frozen at different times.

Assets roots and --assets-path

--assets-path adds assets roots to the core one rather than replacing it, so a consumer project composes its own models and worlds with the core catalogue. It is colon-separated and repeatable, and both forms append:

mise run sim --assets-path /path/to/mine other.world
mise run sim --assets-path /a:/b --assets-path /c other.world

Roots are searched in order. A world is taken from the first root that holds it; model:// URIs resolve across all of them. mise run test:assets checks this.

State, worlds and models

Worlds and models are bundled, so a bare world name resolves without a path - aerialWorld.world, circling_ship_example.world, defenseScenario.world, gz_sensor_example.world, lotusim.world and xdyn_multithread_test.world. lotusim --help lists whatever a given build carries.

lotusim run takes --gui and --debug. Anything starting with a dash goes straight to gz sim instead, so lotusim -s -r worlds/lotusim.world and every other gz option still work.

On first run the packaged lotusim seeds ~/.local/share/lotusim, which is also where scenarios you create and models you upload are written — the store copy is read-only, so it cannot be that place. LOTUSIM_STATE_HOME moves the whole directory; GZ_SIM_RESOURCE_PATH, LOTUSIM_MODELS_PATH and LOTUSIM_SCENARIOS_PATH override one at a time, and the wrapper defers to each when it is already set.

mise run sim does not use the state directory at all, it reads the repo's assets/, so a developer edits models in-tree.

Physics comes from xdyn, over a websocket

physics_engine_interface is a websocket client, not a launcher: it dials the uri named in the scenario YAML, once per vessel per domain, and never invokes an xdyn binary itself. mise run sim and mise run check start Gazebo only. Without an xdyn-for-cs listening, a vessel spawns and renders but does not move.

mise run check needs no xdyn: gz exits 0 whether or not a model resolved, so the log is the only signal, and the check reads the log.

What the devShell exports

The devShell evaluates mise.toml on entry and exports what colcon build installs, so LOTUSIM_*, xdyn-for-cs and the workspace's own Python and ROS packages are in scope without sourcing anything. The examples under examples/ rely on that.


Something not working?

See the Troubleshooting & FAQ page.

Clone this wiki locally