Skip to content

Getting Started

malcom-neo edited this page Sep 30, 2026 · 13 revisions

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

There are two ways to run LOTUSim:

  • With a container (Podman). The quickest way to try it: one command, and no Nix needed.
  • With 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, and you don't need to know Nix to use it. This is the way to install LOTUSim permanently or to develop it.

LOTUSim runs natively on Linux and macOS. On Windows, the Nix paths run inside WSL2; see the Windows tab under One-time setup.

Contents


Pick your path

I want to... Use this path Needs Nix?
Try it out quickly, with nothing to set up Run with a container No
Try it out with Nix, without installing lotusim Path A - Run without installing Yes
Have lotusim available any time, like a normal app Path B - Install LOTUSim Yes
Change or contribute to LOTUSim's code Path C - Set up your dev environment Yes

Run with a container (no Nix)

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.


Run with Nix

Paths A, B and C all run LOTUSim through Nix. Do the one-time setup first, then pick one.

One-time setup

Do this once per machine. Pick your OS:

Linux / macOS

Step 1 - Install Nix

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. For more options, check their official website: Nix website.

Step 2 - Add the binary caches

LOTUSim depends on Gazebo/ROS packages, and on Naval Group's own builds, 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. It needs admin rights, only once per machine:

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

/etc/nix/nix.conf is the system-wide file, so the keys are trusted for every user and the signatures verify.

Step 3 - Install a graphics bridge (optional, Linux only)

Programs installed through Nix can't see your system's graphics drivers on their own, so LOTUSim's 3D Gazebo window may fail to open or fall back to slow software rendering. nixGL bridges that gap by pointing LOTUSim at the driver already on your machine. It's optional: pick the bridge that matches your graphics card, or skip it and come back here if lotusim run --gui doesn't open a window. macOS users can skip this step.

Not sure which graphics card you have? Run lspci | grep -iE 'vga|3d'.

Intel or AMD (both use the open-source Mesa drivers, so they share one bridge despite the Intel in its name):

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

NVIDIA, including hybrid/Optimus laptops (for hybrid laptops, install the Intel/AMD bridge above too). It reads your installed driver's exact version, so it needs --impure, and NVIDIA's driver is unfree, so it needs NIXPKGS_ALLOW_UNFREE=1:

NIXPKGS_ALLOW_UNFREE=1 nix profile add --impure github:nix-community/nixGL#nixGLNvidia
Windows (WSL2)

Nix doesn't run natively on Windows, so you'll run LOTUSim inside NixOS-WSL, a full NixOS system running under WSL2.

1. Install WSL (skip if you already have it)

Open PowerShell as Administrator and run:

wsl --install --no-distribution

Restart Windows if prompted.

2. Install NixOS-WSL

Download nixos.wsl from the latest NixOS-WSL release. If you have WSL 2.4.4 or later, you can double-click the file to install it. Or install it from PowerShell:

wsl --install --from-file nixos.wsl

On older WSL versions, use:

wsl --import NixOS $env:USERPROFILE\NixOS nixos.wsl --version 2

Then open it:

wsl -d NixOS

Optionally, make it your default distro with wsl -s NixOS.

3. Enable mirrored networking

This lets ROS 2's device discovery work without extra configuration, and lets your Windows browser reach the LOTUSim UI on localhost. In PowerShell, open:

notepad $env:USERPROFILE\.wslconfig

and add:

[wsl2]
networkingMode=mirrored
memory=16GB
swap=16GB
4. Configure NixOS for LOTUSim

This step sets nix binary caches and graphics bridge. Inside the NixOS shell, open the system config:

sudo nano /etc/nixos/configuration.nix

Add these lines inside the main { ... } block. Keep what's already there, especially the imports and system.stateVersion lines.

  # Flakes + binary caches (replaces Step 2)
  nix.settings = {
    experimental-features = [ "nix-command" "flakes" ];
    extra-substituters = [ "https://ros.cachix.org" "https://naval-group.cachix.org" ];
    extra-trusted-public-keys = [
      "ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo="
      "naval-group.cachix.org-1:ytTEzFEeuzQrC9IRYLzHGa5OnM65G95M6/sbPd0fy28="
    ];
  };

  # GPU access through the Windows driver (replaces Step 3)
  hardware.graphics.enable = true;
  wsl.useWindowsDriver = true;

  # Needed to clone the repo and for flakes to see your files
  environment.systemPackages = with pkgs; [ git ];

Apply it:

sudo nixos-rebuild switch

Without the trusted keys, Nix will quietly build the ROS/Gazebo packages from source the first time, which takes about an hour. If a build seems stuck, check that this rebuild succeeded.

5. Restart WSL

In PowerShell:

wsl --shutdown

Then reopen NixOS with wsl -d NixOS and continue with Path A, B or C.

Troubleshooting: If ROS nodes can't find each other, check the Windows Firewall first, since mirrored networking is sometimes blocked by default. NixOS also has its own firewall; for local development you can add networking.firewall.enable = false; to configuration.nix and rebuild. If --gui fails to open a window, try the Intel/AMD nixGL bridge from Step 3 (in the Linux / macOS section) as a fallback.

You're now ready to run LOTUSim!


Path A - Run without installing

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

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

In a second terminal, start the web UI:

nix run github:naval-group/LOTUSim#ui

Then open http://localhost:8080 in your browser and head 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.

To update later, see Upgrading an installed build.

Now head to Try your first scenario.


Path C - Set up your dev environment

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.


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 the Unity Editor, 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. Path C above is 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 flake:

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