Skip to the content.

Getting started

Docs index

Nothing to install but Docker. The whole scenario suite runs headless, on the CPU, in about ten minutes. A GPU is needed only for the O3DE scene.


1. Prerequisites

Want to Need
Run the scenario suite, sweep it, hack on the stack Docker
Watch a run in RViz Docker, an X server, xhost +local:
Run the O3DE orchard An NVIDIA GPU with Vulkan, the container toolkit, ~120 GB of disk and several hours for the first engine build
git clone <your fork> kraken-demo && cd kraken-demo

Every command below is run from the repository root.


2. The scenario suite

docker compose -f docker/docker-compose.yml run --rm test

That builds the workspace and runs all ten scenarios against the headless simulator. Reports land in /tmp/kraken_reports/<scenario>.json inside the container; set KRAKEN_REPORT_DIR to move them.

To poke at it by hand instead:

docker compose -f docker/docker-compose.yml run --rm stack
# inside:
colcon build --symlink-install && . install/setup.bash
ros2 launch kraken_scenarios scenario.launch.py scenario:=total_gnss_dropout

scenario.launch.py takes report:=, seed:=, namespace:= and simulator:=headless|o3de. The scenario names are the file stems in ros2_ws/src/kraken_scenarios/scenarios/.

Do not quote a single run. The harness is not reproducible to the decimal place — see design. Measure a distribution:

ros2 run kraken_scenarios sweep total_gnss_dropout -n 10
ros2 run kraken_scenarios sweep total_gnss_dropout -n 8 --fixed-seed   # jitter only

Against the O3DE scene, add --simulator o3de --namespace kraken1 --spawn-point line1. The spawn point is not optional there in practice: O3DE outlives the stack, so without it every run after the first starts wherever the previous one stopped.


3. Watching a run

RViz lives in its own image and only subscribes, so it can be started and killed mid-run without disturbing anything. On the host first:

xhost +local:
docker compose -f docker/docker-compose.yml run --rm viz

Set KRAKEN_NAMESPACE for a robot other than kraken1; the entrypoint rewrites the config, since RViz stores topics and Target Frame as absolute strings.

Several displays ship disabled, because they cost bandwidth or clutter for a view that is only wanted while explaining something:

display what it is for
Ground truth the simulator’s true pose, against the filter’s in magenta
Lidar (raw) the unfiltered sweep, reaching 96 m — compare against what the costmap actually marks
Camera front left / right what the stereo pair saw when visual odometry lost the row
Camera ground whether the downward camera is aimed where the mounting says

4. The orchard coverage run

This one needs the O3DE scene, so build it first — see simulation §2 and docker/README.md.

export KRAKEN_ROOT=... O3DE_HOME=...

# Always start a fresh simulator. Spawning into a running one stacks a second
# robot on the same point and they collide.
#
# The level must be named. autoexec is set to `playground`, which has no trees
# and, more to the point, no spawn points - so `spawn line1` finds nothing to
# place, no robot publishes odometry, and Nav2 waits forever for a
# `kraken1/odom` that never exists. Note `+loadlevel`: the `-LoadLevel=` form
# parses without complaint and does nothing.
docker rm -f kraken_ns
docker compose -f docker/docker-compose.yml run --rm -d --name kraken_ns sim \
  /o3de/ROSConDemo/Project/build/linux/bin/profile/ROSConDemo.GameLauncher \
  +loadlevel main

# main chains through intermediate loads, so wait for main by name.
until docker logs kraken_ns 2>&1 | grep -aq "main.*spawnable loaded"; do sleep 5; done

docker compose -f docker/docker-compose.yml run --rm --entrypoint bash stack -c '
  source /opt/ros/$ROS_DISTRO/setup.bash && cd $KRAKEN_WS
  colcon build --packages-select kraken_interfaces kraken_nav kraken_scenarios
  source install/setup.bash

  # Spawns, checks the robot is really publishing, and only then starts the
  # stack. Doing those by hand is how a stack ends up running against a robot
  # that never spawned, which from the inside looks like a Nav2 fault: it waits
  # forever for an odom frame nobody publishes.
  #
  # It waits for /spawn_entity rather than for a duration, which is the same
  # gate as the `docker logs` line above asked over ROS instead of by scraping
  # a container. The old 30 s timeout was one slow load from failing on a
  # healthy simulator - a measured run waited 90 s.
  ros2 launch kraken_nav orchard_bringup.launch.py &

  # Wait for Nav2 to actually be active rather than for a duration. nav_bringup
  # holds the servers until every one of them answers, so this returns as soon
  # as it is true and does not return early when the machine is loaded.
  until ros2 service call /kraken1/lifecycle_manager_navigation/is_active \
        std_srvs/srv/Trigger | grep -q "success=True"; do sleep 2; done
  ros2 action send_goal /kraken1/cover_rows kraken_interfaces/action/CoverRows \
    "{aisles: 18, aisle_pitch: 3.5, aisle_skip: 0,
      row_near_x: 5.0, row_far_x: 43.0, row_heading_deg: -90.0}"'

Do not gate a script on orchard_bringup’s own exit code. ros2 launch returns 0 whether or not the spawn failed, so a caller checking $? sees success on a run that started nothing. Scripts should call sim_admin spawn directly, which returns 1.

aisle_skip: 0 lets the skip rule work it out from the turning circle. What the machine then does is mission planning.


5. Tests and figures

Neither needs a simulator or a GPU:

colcon test --packages-select kraken_nav      # 12 tests, the turn geometry
colcon test --packages-select kraken_orchard  # row fitting
python3 docs/figures/make_figures.py          # regenerates the SVGs, and
                                              # asserts the same reference table

6. Where to go next

Conventions and review expectations are in CONTRIBUTING.md.