Contents

The SushiEngine command-line interface

SushiEngine ships with a small command-line tool that drives everything you do day to day: building the engine, running its tests, launching the editor and the player, running the headless render probes and the worked examples under samples/, baking the planetary and climatology assets, and managing the development container. It is a thin wrapper around CMake and CTest that reads your machine-specific toolchain paths from a configuration file so you don’t have to retype long compiler flags.

This guide is the full command reference, in plain English. If you just want to get the project compiling and running for the first time, see CONTRIBUTING.md instead — it walks you through the sibling checkout with SushiRuntime and your first build.

Installing the CLI

The CLI is a Python package that lives in the cli/ folder. Install it once and it puts two commands on your PATH:

  • se — short name.
  • sushiengine — long name.

They are identical; use whichever you prefer. Every example below uses se.

To install:

pip install -e cli               # inside a venv/conda env

The package depends on sushicore (the shared CLI presentation layer used across the Sushi stack), which is not published to any index — install it once from its sibling checkout first (pip install -e ../sushicore).

Two commands need extra Python packages that everything else does without. They are declared as optional extras so a plain install stays light, and each command prints its install line rather than an import traceback when the extra is missing:

pip install -e cli[planet]        # numpy + requests, for `se bake planet`
pip install -e cli[climatology]   # numpy + requests + netCDF4, for `se bake climatology`

How the CLI finds your compiler

The CLI reads toolchain paths from three files, each overriding the one before it:

  • cli/config.toml — committed, shared defaults (the same for everyone).
  • The workspace file .sushistack/workspace.toml under the SushiStack workspace home, which hub install writes so every module in the workspace resolves the same toolchain. A checkout outside a workspace has none.
  • cli/config.local.toml in this repository — your machine’s absolute paths (the SushiRuntime sibling location, vcpkg location, compiler executables). This file is gitignored, so your personal paths never get committed.

SE_* environment variables override all three, and a command-line flag overrides everything. If you’re ever unsure what the CLI is actually using, run se config to see the final resolved values and where each one came from, or se doctor to check that each tool they name is really there.

A syntax error in one of these files ends the command with one line that names the file and the position of the error, and exit code 1.

Command overview

Every command is a verb, and a word after it names the thing it acts on: se probe golden, se bake planet, se inspect climatology. se --help groups them in five panels, the same five this table uses.

Panel Command What it’s for
Build se build Configure and build the engine
Build se test Run the test suite through CTest labels
Build se run Run a built executable, or list or pick one
Build se clean Remove a build tree
Build se docs Generate the Doxygen API reference, or bundle the manual
Build se check Run the guard scripts continuous integration runs
Applications se editor Build and launch the ImGui editor
Applications se player Build and launch the ImGui-free player
Applications se package Install a built tree, archive it, and check it runs
Applications se demo Build and run one worked example from samples/
Applications se probe Build and run one headless Vulkan probe
Environment se config Show the resolved configuration
Environment se env Show the environment builds run under
Environment se status Show the checkout, each build tree and the baked assets
Environment se where Name where a relocated configuration file lives
Environment se setup Install what the engine needs to build
Environment se doctor Check the tools and siblings a build needs
Environment se link Record this checkout in a workspace’s module registry
Environment se unlink Remove that entry
Environment se container Build and run the development container
Assets se bake Bake an asset from public data
Assets se inspect Print a baked asset’s contents and provenance
Assets se cook Cook authored content into its runtime form
Add-ons se addon Scaffold and build third-party add-ons

se --version prints what is running and against which revisions; see se --version. se --describe prints the same command tree as JSON; see se --describe. Run any command with --help to see its options, and a group such as se probe or se bake with no word after it to see the words it takes.

Each command’s own help ends with examples. On a colour terminal the root screen shows the logo. On a dark terminal add [cli] and background = "dark" to cli/config.local.toml to give it a glow; COLORFGBG is read when the terminal sets it, and Windows Terminal does not.

Old spellings

Every command that changed its name still answers to the old one until 2027-03-25. After that date the old spellings are removed. They are hidden from se --help.

Old spelling New spelling
se run --sort se run --pick
se doxygen se docs
se render --probe X se probe X (gpu_tests becomes gpu-tests, atmosphere_global becomes atmosphere-global)
se render se probe render
se audio se demo audio_demo
se planet bake se bake planet
se planet geoid se bake geoid
se planet albedo se bake albedo
se planet inspect se inspect planet
se climatology bake se bake climatology
se climatology inspect se inspect climatology
se docker build se container build
se docker run se container run

An old spelling runs the new command with the same options, after one line on standard error that names the new spelling and the removal date. The line appears once per process, and only when standard error is a terminal, so a script that pipes or redirects its output never sees it. Set SE_NO_DEPRECATION_NOTICE=1 to silence it on a terminal too. The alias dates check of se doctor fails once the removal date has passed.

Build

se build

Configures (if needed) and builds the engine against the SushiRuntime sibling checkout.

se build                  # Release build (the default)
se build --type debug     # Debug build
se build --clean          # delete the build tree first, then build from scratch
se build --no-test        # skip compiling the test suite (SUSHIENGINE_BUILD_TESTS=OFF)
se build --examples       # also build the worked examples under samples/
se build --backend native # the SYCL-free execution lane, into build/native
se build --profiler tracy --profile-level 2  # every zone, streamed to Tracy
Option Values Default
--type, -t release, debug, relwithdebinfo release
--clean flag off
--no-test flag off (tests build)
--examples flag off
--backend runtime, native runtime
--profiler builtin, tracy builtin
--profile-level 0, 1, 2 1

The test suite is compiled by default; pass --no-test to skip it for a faster, engine-only build.

--examples turns on SUSHIENGINE_BUILD_EXAMPLES, which declares the demos under samples/. It is off by default because each demo is its own SYCL translation unit and so its own device compile; a plain se build only produces sandbox and pgs_demo, the two targets that prove a lane builds at all. To build and run one demo rather than all fifty-four, use se demo, which turns the option on for the length of that one command.

--backend selects which implementation SushiEngine::Execution denotes, which CMake settles before project() and therefore also decides whether SushiRuntime is part of the build at all:

  • runtime (the default) — the runtime’s SYCL task graph. Needs the sibling checkout and a SYCL toolchain, and is the only lane the shells, the samples and the full test suite are declared on.
  • native — the thread-pool backend for platforms the runtime cannot reach. No SushiRuntime subproject and no SYCL compiler; it carries sandbox, pgs_demo and the Execution conformance binary (se_native_execution_tests).

--profiler and --profile-level set SUSHIENGINE_PROFILER_BACKEND and SUSHIENGINE_PROFILE_LEVEL (see profiling). se editor, se player, se probe and se demo take both as well. Every configure states both, the defaults included, and reconfigures the lane’s own tree in place: se editor --profiler tracy followed by a plain se editor gives back a builtin editor in build/editor, and there is no separate Tracy tree. tracy needs Tracy installed through hub install. se test and se run do not configure, so they build whatever profiler the tree was last configured with.

se test and se run take the same --backend, so se build --backend native && se test --backend native builds and runs that lane end to end.

Each lane configures into its own subdirectory of build/ — build/default for se build, build/native for se build --backend native, build/editor for se editor, build/player for se player — so no two lanes ever clobber each other’s cache. Switching backend also re-configures from scratch if a tree ever does end up carrying the other lane’s cache. se probe and se demo are the exception: they reconfigure build/default in place rather than owning a tree, which is why se build re-runs configure on every invocation instead of only on a fresh tree.

Every configure states all eight SUSHIENGINE_BUILD_* options with an explicit ON or OFF, so it describes the tree it wants rather than the difference from whatever the last configure left behind. CMake keeps an option()’s value in the cache, so omitting a definition does not restore that option’s default — it keeps the previous value. An option one lane turns on is therefore the one the next lane turns off: se demo states SUSHIENGINE_BUILD_RENDER=OFF, and a plain se build states both the renderer and the samples off again.

Alternating between se probe and se demo in the same tree therefore costs a full configure each time, and each switch drops the other lane’s targets out of the build until that lane configures them back. That is the intended trade: an option that outlives the command asking for it is an option that keeps a whole subsystem compiling into every later se build and se test, with se clean as the only way to notice.

SUSHIENGINE_BUILD_FSR is the one option among the eight with no flag to ask for it: it is stated ON by every lane that builds the renderer (se editor, se player, se probe) whenever third_party/FidelityFX-SDK/ffx-api/CMakeLists.txt exists, and OFF by every other lane, se build included. The test names a file, not the directory, because cloning the superproject creates an empty submodule directory. Populating the submodule therefore adds the AMD FidelityFX Super Resolution backend, and its compile time, to every renderer build from then on. Each renderer-building lane’s configure line says FSR ON or FSR OFF.

se test

Runs the test suite through CTest. Tests are grouped by label, and you pick a group with --suite (-s):

se test                          # functional suite (the default)
se test --suite unit             # just the unit label
se test --suite functional       # unit + integration
se test --suite all              # every test
se test --suite functional --filter 'Integrator.*'   # ctest -R over test names
se test --repeat 50              # re-run each test up to 50x, stop on first failure
Option Values Default
--suite, -s unit, integration, functional, python, all functional
--filter, -f a ctest -R regex; a pytest -k expression with python none
--repeat, -r integer, 0 or more 0 (once)
--backend runtime, native runtime
--no-build flag off

--suite python runs the CLI’s own pytest tests. Their groups are pytest markers, not name prefixes: python -m pytest cli/tests/ -m planet selects every test covering se bake planet, including the raster and surface-class files whose names never mention a planet. With --suite python, --filter reaches pytest as -k, and --repeat N runs the whole suite up to N times and stops at the first failing run. The pytest suite has no build tree, so --backend native and --no-build are refused with exit code 2 and nothing runs.

se test compiles the tree before it runs anything, so what it reports is the code you have, not the code you last built. When the build fails it says so and stops rather than handing you a pass from the previous binary — a stale run is worse than no run, because its failures quote source text you have already changed and read as defects in the product. --no-build skips that step, which is only for deliberately inspecting a binary you know is out of date. Configure is not part of it: se test builds an already configured tree and leaves the tree’s -D flags to se build.

unit and integration each select one CTest label. functional is the umbrella over both, and all runs every registered test with no label selection at all.

--filter is a ctest -R regex matched against Suite.Case test names. --repeat is handy for hunting down flaky tests. For GoogleTest-level options CTest doesn’t expose (shuffling, break on failure), run the binary directly with se run and pass flags after --.

Because it is one regex, not gtest’s --gtest_filter, several suites go on one side of a |, not a : — 'Unit_A.*|Unit_B.*', never 'Unit_A.*:Unit_B.*'. ctest reads a colon as a literal character, matches nothing, and still exits zero, so a filter that names no tests looks like a pass instead of a mistyped filter.

se run

Runs a built executable. With no target it runs the project’s default, which cli/config.toml sets to sandbox.

se run                                                     # run the default target
se run sandbox                                             # run a specific binary by name
se run --list                                              # what the tree holds, no build
se run --pick                                              # interactively pick from the list
se run se_functional_tests -- --gtest_shuffle --gtest_break_on_failure
Option Values Default
TARGET an executable name target_bin from the config (sandbox)
--list flag off
--pick flag off
--no-build flag off
--backend runtime, native runtime

Like se test, this compiles before it launches, and for the same reason — plus one more: the target is found by searching the build tree, so an executable you have just added does not exist to be found until something builds it. --no-build launches whatever is there.

--list prints every executable the lane’s tree holds, with its path, and builds nothing, so it shows what the last build produced. --pick builds first, then shows the same executables as a numbered list and runs the one you choose.

The target name is matched exactly first, then by substring. Anything after -- is forwarded straight to the program.

se clean and se docs

se clean                  # remove the build/default tree
se clean --type native    # remove build/native
se clean --type all       # remove the whole build/ directory
se docs                   # generate Doxygen documentation from .config/doxygen/Doxyfile
se docs bundle --release 1.2.3                   # pack the manual into build/docs/bundle
se docs bundle --release 1.2.3 --out build/site  # write the archive somewhere else

se clean takes --type/-t (build, native, editor, player or all; default build, which is the build/default tree) and nothing else. se docs on its own takes no options and runs Doxygen; set doxygen_exe in cli/config.local.toml if Doxygen is not on your PATH.

se docs bundle packs the pages docs/publish.toml lists into docs-bundle-<release>.tar.gz, the archive docs.sushisystems.io reads, and writes the archive’s SHA-256 in a .sha256 file beside it. --release is required and takes three integers, as in 1.2.3. --out names the folder the archive goes to, build/docs/bundle by default. This repository publishes getting_started/, guides/ and architecture/ and no API reference, so the bundle does not run Doxygen. The command stops and names the page when a published page links to a file that does not exist, has no level-one heading, or is not reached from docs/README.md.

se check — the guard scripts

Runs the checkers under tools/layering/ and tools/documentation/ from one registry, the same one continuous integration reads, so what fails on your machine is what fails a push (docs/design/COMMAND_LINE_TAXONOMY.md §7.4).

se check                              # run every registered check
se check layering shell-invocation    # run only the named checks
se check --list                       # print the registry: name, script, what it enforces

Exits 0 when every check requested passes, 1 when any fails, 2 when a name is not registered. The twelve names: layering, shell-invocation, frame-view-parity, host-frame-ownership, dependency-provisioning, settings-serialization-parity, kernel-headers, module-documentation, documentation-length, source-comments, design-citations, design-backlog-coverage.

Applications

se editor — the ImGui editor

se editor                 # build (Release) and launch the editor
se editor --type debug    # Debug build
se editor --no-run        # build the editor but do not launch it
se editor --validation    # launch with the Vulkan validation layers on
se editor --trace out.json  # record a Chrome trace until the editor exits
se editor --profiler tracy  # stream the zones to the Tracy profiler
Option Values Default
--type, -t release, debug, relwithdebinfo release
--no-run flag off
--validation flag off
--trace file path none
--profiler builtin, tracy builtin
--profile-level 0, 1, 2 1

Configures with SUSHIENGINE_BUILD_EDITOR=ON, builds the sushiengine_editor target into its own build/editor tree, separate from se build’s build/default, so the two never clobber each other’s CMAKE_BUILD_TYPE, and launches the se_editor binary.

--validation passes the flag to se_editor and points the Vulkan loader at the Khronos layer vcpkg installed, by setting VK_LAYER_PATH to the triplet’s bin directory when VkLayer_khronos_validation.json resolves there. When it does not, the command refuses to launch and tells you to run se setup, or hub install inside a SushiStack workspace. A run that started anyway would be silently unvalidated, which reads exactly like a clean pass. See known issues for why the environment variable is needed at all.

--trace <file> passes the file, made absolute, to se_editor --trace. The editor starts a capture at launch and writes it as a Chrome trace when it exits; Perfetto (ui.perfetto.dev) and chrome://tracing open the file. The Profiler panel’s capture button records the same kind of file on demand into the project’s logs folder. There is one capture at a time: under --trace the button reads Stop capture, and pressing it writes the launch capture into logs instead of the --trace file, which then receives only a capture started later from the panel, or nothing and an error at exit. A build with SUSHIENGINE_PROFILE_LEVEL=0 still writes the file, with no zones in it, and logs a warning saying so.

se player — the ImGui-free player

se player                                     # build (Release) and launch the player
se player --type debug                        # Debug build
se player --no-run                            # build the player but do not launch it
se player --validation                        # launch with the Vulkan validation layers on
se player --trace out.json                    # record a Chrome trace until the player exits
se player -- --scene path/to/scene.scene      # load a scene explicitly
se player -- --headless --frames 30           # run 30 frames with no window, then exit
Option Values Default
--type, -t release, debug, relwithdebinfo release
--no-run flag off
--validation flag off
--trace file path none
--profiler builtin, tracy builtin
--profile-level 0, 1, 2 1

Configures with SUSHIENGINE_BUILD_PLAYER=ON, builds the sushiengine_player target into its own build/player tree, and launches the se_player binary. --validation means the same thing here as on se editor: the CLI resolves VK_LAYER_PATH to the vcpkg layer manifest first and refuses to launch when it cannot find one. --trace means the same thing too, minus the panel button. Arguments after -- are forwarded to the binary, which accepts:

  • --manifest <path> — the boot manifest to read. A boot.json sitting next to the built executable is read automatically when this is not given.
  • --scene <path> — the scene to load, overriding whatever the manifest named. A bare positional path means the same thing.
  • --headless — run without a window and exit after a fixed number of frames, which is what makes the player runnable on a machine with no display.
  • --frames <n> — how many frames --headless runs. Default 60.

The command line always wins over the manifest, so local testing never means editing the shipped configuration file.

se package — install, archive, verify

Installs a built tree, archives it with CPack, and runs the installed player with the developer environment stripped from the child process — the only way to tell a tree that carries its own dependencies from one that borrows this machine’s. The archive is something a developer or tester unpacks and runs, not an installer.

se package                          # build the player tree, install, archive, verify
se package --tree default           # install from build/default instead of build/player
se package --prefix /tmp/stage      # install somewhere other than build/stage
se package --no-archive             # install only, skip CPack
se package --no-verify              # skip the packaging oracle
se package --no-build               # package whatever was built last
se package --request build.json     # ship the content a Build Settings request names
Option Values Default
--tree a name player (the tree configured with an application shell)
--prefix a path build/stage
--no-archive flag off
--no-verify flag off
--no-build flag off
--request a path none

--request takes a build request written by the editor’s Build Settings window: which content to ship, and what to tell the shipped player at launch.

se demo — the worked examples

samples/ declares fifty-four examples, one directory per subject. Every one is headless and self-checking: it exits 0 when the thing it demonstrates still holds, so a demo is something you run as well as read.

se demo --list                    # print every sample and the group that declares it
se demo                           # the same list; a bare `se demo` names nothing to build
se demo pgs_demo                  # build and run pgs_demo
se demo audio_demo                # name a target exactly
se demo --type debug pgs_demo     # build in Debug
se demo --no-run pgs_demo         # build only
se demo physics_sample_scene -- out.scene    # arguments after `--` reach the demo
Option Values Default
NAME a sample target, or part of one none — prints the list
--list flag off
--type, -t release, debug, relwithdebinfo inherits build/default’s type, or release
--no-run flag off
--profiler builtin, tracy builtin
--profile-level 0, 1, 2 1

The name resolves in three steps, narrowest first: the exact target name, then the name with the _demo suffix every group appends, then a substring match when exactly one target matches it. A name matching nothing, or more than one, exits 2 without running CMake; an ambiguous one lists the targets it matched. The list itself is read from the sushiengine_add_sample(...) calls in the samples/ CMake files, so it cannot drift from what a configure declares. Most demos read no arguments of their own; physics_sample_scene, which takes the path to write, is the one that does.

Configures build/default in place with SUSHIENGINE_BUILD_EXAMPLES=ON, which is what declares the sample targets at all, plus SUSHIENGINE_BUILD_AUDIO=ON for a demo in the samples/audio group, which declares nothing without the compiled audio backend. The build type and SUSHIENGINE_BUILD_TESTS carry over from that tree’s existing cache, and every other option is stated OFF, so the next se build turns the samples back off instead of declaring fifty-four extra device compiles from then on.

A few demos are declared only when an optional vcpkg package is installed: Opus, Vorbis and HDF5 back the compressed-codec and measured-HRTF audio samples. They still appear in --list, which reads the CMake text; the group’s CMakeLists.txt names each package for hub install.

This lane reaches the runtime execution backend only. samples/CMakeLists.txt declares the demos when SUSHIENGINE_BUILD_EXAMPLES is on and SUSHIENGINE_EXECUTION_BACKEND is runtime, and se demo always configures the runtime tree, so the two agree today. A demo lane pointed at the native backend would find no sample targets declared at all.

se probe — the headless Vulkan probes

se probe takes the probe’s name as its next word. The nine probes are render, golden, clouds, water, atmosphere, atmosphere-global, capabilities, framegen and gpu-tests, each described below; se probe --help lists them with one line each.

se probe render                 # build and run the triangle smoke test
se probe render --type debug    # build in Debug mode
se probe golden --no-run        # build only
se probe atmosphere -- --hours 3 --profile column.csv
Option Values Default
--type, -t release, debug, relwithdebinfo inherits build/default’s type, or release
--no-run flag off
--profiler builtin, tracy builtin
--profile-level 0, 1, 2 1

Configures build/default in place with SUSHIENGINE_BUILD_RENDER=ON. Three values carry over from that tree’s existing cache — the build type, SUSHIENGINE_BUILD_TESTS and SUSHIENGINE_BUILD_EXAMPLES — so running a probe never clobbers a debug, test-enabled or sample-enabled development tree; every other option is stated OFF. --type overrides the build type that carried over, and an unconfigured tree falls back to Release. Every probe runs without a window, so they work over SSH and in continuous integration. Anything after the options is passed straight through to the probe.

se probe render renders a triangle offscreen and reads two pixels back, which proves the device, shaders, pipeline and submit path came up. It takes no arguments of its own.

se probe framegen measures frame generation. It drives the scene view tick by tick with interpolation on, reads back whichever image each tick presents, and prints its mean luma labelled real or generated, so the viewport’s brightness beat is a number rather than an impression. It reports and does not assert.

se probe framegen                     # orbiting camera, one generated frame
se probe framegen -- --still          # the control run: camera held
se probe framegen -- --ticks 96       # longer sample
se probe framegen -- --no-aa          # without temporal reconstruction

se probe golden is the renderer’s regression oracle: it renders a fixed scene for a fixed number of frames and compares it against the references in tests/goldens/render/ — the whole frame by hash and thumbprint, and each pass’s output by its own hash, so a mismatch says which pass changed rather than only that something did. Run it before and after any change to a pass.

se probe golden                    # compare
se probe golden -- --dump          # ...and write a PPM of any mismatch
se probe golden -- --update        # re-record, deliberately
se probe golden -- --no-capture    # whole-frame comparison only
se probe golden -- --count-allocations   # per-frame allocation counts
se probe golden -- --goldens DIR   # read and write references elsewhere

se stamps a recorded golden with the checkout’s commit, suffixed -dirty for uncommitted work, or unknown when git cannot answer; -- --commit <value> overrides it.

A golden is a statement about one GPU and one driver, which is why this is not a CTest case and why --update is an act rather than a remedy: a red run is the harness working. Read the thumbprint distance and the per-pass lines, and re-record only once the change is understood and wanted. --no-capture renders the way a shipping build allocates, which tells a real difference from one that exists only under capture; it cannot be combined with --update. --count-allocations prints the allocations of frames 4 to 12, their median and the two graph stage times, and leaves the comparison as it was. See tests/goldens/render/README.md for what a golden covers and what it does not.

se probe clouds captures the volumetric sky from the canonical cloud-diagnosis viewpoints (ground, zenith, high altitude, orbit, space) in every cloud debug view mode, writing each frame as a BMP under cloud_captures/. It is the eye the cloud repair work is steered by: run it after a cloud change and read the files instead of flying the editor camera by hand.

se probe clouds                          # the full viewpoint x mode matrix
se probe clouds -- --viewpoint zenith    # one viewpoint
se probe clouds -- --mode envelope       # one debug mode
se probe clouds -- --out captures/       # write somewhere else
se probe clouds -- --viewpoint ground --sequence 24 --velocity 60,0,0   # 24 frames in motion
se probe clouds -- --genus cumulus --coverage 0.3 --viewpoint km100_down # one genus

A motion sequence writes one BMP per frame while the camera translates, 60 m/s above, which separates carve-side shredding (in every still) from temporal-resolve smear (only under motion). --genus renders one genus on the authored path, and every capture prints its cloud-pixel statistics.

The --clouds-off / --fog-off / --atmosphere-off / --surface-off switches disable one renderer term at a time, which is how a veil or a ring is attributed to the pass that draws it rather than to the cloud march that happens to share the pixel.

se probe water composes the world the editor starts with (its default environment from your preferences) through the shared frame body, or a .scene given with --scene and --project. It stands at each viewpoint, renders the warm frames (240 by default), waits until terrain residency reports no uploads for 16 frames, and writes every water debug view to .scratch/water/<viewpoint>_<mode>.bmp. Per viewpoint it prints the ocean’s camera_height_metres and camera_ground_clearance_metres, whether the underwater pass ran, and the water node count. The presets stand over the Sea of Marmara: coast_high (41.075 N 28.25 E, 230 m, facing south, 30 degrees down), ocean_low (40.8 N 28.2 E, 3 m, facing east, level) and ocean_pitch_up (the same eye pitched 20 degrees up). Altitudes are above still water.

se probe water                                    # the three presets x six debug views
se probe water -- --at 40.8,28.2,3,90,0 --mode sea   # one geodetic eye: lat,lon,alt,heading,pitch
se probe water -- --viewpoint coast_high --warm 400 --hour 10
se probe water -- --scene assets/scenes/player_smoke.scene --project .

The modes are sea, seabed_depth, floor_tap, body_colour, sky_reflection and floor_radiance, WaterSettings::debug_view 0 to 5. --hour sets the UTC hour of the sky date, --settle-timeout caps the residency wait, and --width/--height/--out work as in clouds.

se probe atmosphere steps the regional weather nest through hours of simulated time in seconds of wall clock and reports both the observer column and the whole domain’s sky — a measuring instrument rather than a smoke test. Run it with -- --help for the full list; the ones worth knowing are --hours (default 3), --sample <minutes> (default 10), --diurnal (drive the sun through a real day instead of holding it), --profile <path.csv> (the full vertical state, one row per level per sample), --series <path.csv> (one row per sample), --tier (low, medium, high — the default — or ultra), and the --albedo / --beta / --slab / --exchange / --surface-temp / --seed / --eddy / --pbl-depth / --pbl-w / --critical / --humidity / --sweeps overrides that isolate one term of the physics at a time.

se probe atmosphere -- --hours 11 --diurnal --sample 45

The three rightmost columns are the domain’s sky — what fraction of columns hold cloud, the mean coverage and the mean cloud base — because a single column is a noisy sample of a 192² field, and “is there cloud” is a question about the sky rather than about where the observer happens to be standing.

se probe atmosphere-global runs the global dynamical core through simulated weeks in seconds of wall clock and prints what the flow is doing, which is how baroclinic growth and its equilibration are measured. Its options are --days, --sample-hours, --seed, --longitudes, --latitudes, --upper-jet, --lower-jet, --perturbation, --series <path.csv>, --climatology <path> (run on a baked climatology instead of the analytic bands) and --damping-hours.

se probe atmosphere-global -- --climatology assets/atmosphere/climatology.set0

se probe capabilities is the instrument to run after installing a new GPU or updating a driver. It first confirms VK_LAYER_KHRONOS_validation is installed on the Vulkan loader’s layer path and fails outright if it is missing, before touching a device, because requesting a layer the loader cannot find does not fail and would report a clean run that checked nothing.

It then brings the device up with validation forced on, prints the device’s capability report, and dispatches real GPU work through every capability the report lists as enabled: a meshlet draw through the renderer’s own meshlet.task and meshlet.mesh shaders, a dynamic rendering pass against a fragment-shading-rate attachment at the negotiated texel size, and a few frames of a hand-built lit scene through VulkanSceneView’s real pass stack, which is what makes the shadow comparison sampler and IBL capture actually run. Every validation message at error severity is printed and counted; the probe exits non-zero when that count is not zero. It takes no arguments of its own.

What it proves is narrower than “the renderer is universal”, in two ways. First, it proves that on the one device it ran on, the paths this renderer takes when a capability is enabled do not violate the Vulkan specification — a clean run on one vendor’s GPU says nothing about another vendor’s; run it again on every GPU this project claims to support. Second, the counted window starts after the Vulkan instance and logical device already exist: a VUID that fires during instance or device creation is not counted, because installing the probe’s counting messenger any earlier would mean adding a Vulkan-specific field to RenderDeviceDescription, a header deliberately kept backend-neutral.

se probe gpu-tests runs the GPU integration test suite (sushiengine_gpu_tests), which stands up a real render device and is why it lives beside the other probes rather than in se test. It needs SUSHIENGINE_BUILD_TESTS=ON in build/default already, which is on by default, so a fresh se build carries it, and it fails before configuring with a message to run se build first if that tree turned tests off. Pass-through arguments go to GoogleTest.

se probe gpu-tests
se probe gpu-tests -- --gtest_filter=Integration_RenderDeviceFixture.*

Environment

se config and se env

se config         # print the resolved config and where each value came from

se env            # print the environment cmake/ctest/run subprocesses run under
se env --all      # show every variable, not just build-relevant ones

se config takes no options. se env takes --all (-a), off by default. Both commands are declared once, in sushicore.diag_commands, and every Sushi CLI registers them from there.

se status

Prints what is in this checkout and what state it is in, in four blocks. Every line comes from files already on disk, so it runs no build, no CMake and no network request, and it answers on a half-built tree. It takes no options.

  • Project and Runtime name the checkout, its branch, and whether the SushiRuntime sibling the configuration points at is there.
  • Build trees has one line for each of build/default, build/native, build/editor and build/player. A configured tree shows its build type, its backend and four of its SUSHIENGINE_BUILD_* options. It reads STALE when a file under engine/, applications/ or tools/ is newer than every file in the tree. An absent or unconfigured tree shows the command that builds it instead.
  • Cooked assets lists each planet pack under cooked/planet/ and the climatology asset with its size, or absent with the se bake command that makes it.
  • Next names the command for the first stale tree, or says nothing is stale.

se setup

Installs what the engine needs to build, then prints the se doctor table. It reads three dependency fragments in order: the base fragment that ships with sushicore, SushiRuntime’s, and cli/sushistack.deps.toml. It installs under ~/.sushisystems, or under SUSHISYSTEMS_HOME when that is set, and writes the paths it found to cli/config.local.toml.

  • --dry-run shows every step and changes nothing.
  • --toolchain <name> also installs that SYCL toolchain; repeatable.
  • --no-gpu skips the toolkit for this machine’s GPU.
  • --yes answers the LLVM download prompt.

It exits 2 without installing anything when it finds no SushiRuntime checkout. hub install does the same for every checkout of a workspace in one command.

se doctor

Prints one table of checks, everything a build needs before a build finds out the hard way. It builds nothing and changes nothing. --for <group> keeps one group: build, test, infer or eval.

The first rows are the ones every Sushi CLI shares: python, cmake, ctest, ninja, c++ compiler, git, modules (the SushiRuntime checkout exists), toolchains (a SYCL toolchain is present), dependencies (every declared library is installed) and toolchain stamps. The engine adds eight:

  • root marker — the working directory is inside a checkout that carries the root marker;
  • doxygen — it resolves, from doxygen_exe or PATH;
  • extra planet, extra climatology, extra dev — every module the extra declares imports;
  • validation layer — the Khronos layer manifest sits in the triplet’s bin directory;
  • docker — docker version reaches a server within five seconds;
  • alias dates — no old spelling has passed its removal date.

A failed row prints what it found and the command or setting that fixes it: se setup, pip install -e cli[planet], a key in cli/config.local.toml. The shared rows and root marker are required, and the command exits 1 when one of them fails. The engine’s other seven rows show as warnings.

se link records this checkout in a SushiStack workspace’s module registry and se unlink removes the entry. Both take the workspace from --workspace <path>, then SUSHISTACK_HOME, then the nearest parent directory that is a workspace.

se where

Some configuration files live under .config/ instead of beside the tool that reads them. se where names where one lives and the exact command that points its tool at it. It never looks for the project root, so it answers outside a checkout: se where doxygen for one file, se where --list for all of them.

The topics are doxygen (.config/doxygen/Doxyfile), docker (.config/docker/Dockerfile), dockerignore (.config/docker/Dockerfile.dockerignore) and config (cli/config.local.toml). Each row also names the se command that does the job for you, such as se docs or se container build. A bare se where prints the whole table, and an unknown topic points you at --list.

se --version

$ se --version
sushiengine-cli 0.1.0
D:\Projects\sushiengine
engine ec37f391-dirty
runtime d41f2c6

Four lines: the installed CLI package and its version, the project root, the engine’s git describe (suffixed -dirty for uncommitted work), and the SushiRuntime sibling’s short commit, or missing. Outside a checkout the root reads not inside a project, and a revision git cannot supply in time reads unknown, so the command never fails.

se --describe

Prints the command catalogue as JSON and exits 0: the program name, the installed version of sushiengine-cli, and one entry per command with its help line and every argument and option. A command inside a group is listed under its full name, such as bake planet or container run. The old spellings are left out. The shape is the one SushiHub’s contract/describe.schema.json fixes, so a tool that reads hub --describe reads this too.

se container — the development container

se container build                       # build the `sushiengine` dev image
se container build --no-cache            # rebuild every layer, ignoring Docker's cache
se container build --runtime-ref <ref>   # clone this SushiRuntime branch/tag/sha, not main
se container run                         # start the container with the source mounted
se container run --admin                 # run privileged (--privileged --cap-add=SYS_ADMIN)
se container run --no-gpu                # skip GPU passthrough (CPU SYCL device still works)

Every flag is off by default.

Assets

se bake planet, se bake geoid, se bake albedo — planetary assets

Builds the .sushiplanet assets the terrain system reads: a cube-sphere height pyramid per body per quality tier (docs/design/SOLAR_SYSTEM_OVERHAUL/README.md §5).

se bake planet                                   # the Moon, compact tier (33 MB download)
se bake planet --body moon --tier standard       # 64 pixels/degree (530 MB download)
se bake planet --depth 5                         # deeper than the source supports; see below
se bake planet --refresh                         # re-download instead of using the cache
se bake planet -o /tmp/moon.sushiplanet          # write somewhere else
se bake planet --region 40,40,45,50              # re-bake only this box, in place
se bake planet --workers 8                       # compile 8 regions at once, in processes
se bake planet --resume                          # pick up a full-pyramid bake a kill interrupted
se bake planet --body earth --geoid              # also bake the EGM2008 geoid pack
se bake geoid earth                              # bake just the geoid pack, no terrain re-bake
se bake albedo --body jupiter                    # bake the body's measured colour cube map

se bake planet:

Option Values Default
--body, -b earth, mars, mercury, moon, venus moon
--tier, -t compact, standard compact
--refresh flag off (use the cache)
--depth, -d integer, 0 to 20 the depth the source data supports
--output, -o a path cooked/planet/<body>.<tier>.sushiplanet
--region south,west,north,east, degrees none (bakes the whole body)
--workers, -j integer, 1 or more 1 (one process, no parallelism)
--resume flag off (start over)
--geoid flag off (Earth only; ignored for other bodies)

se bake geoid <body>:

Argument/Option Values Default
body a body key; only earth has an EGM2008 model (required)
--tier, -t compact, standard standard
--refresh flag off (use the cache)

Not every body offers both tiers: Mercury ships standard only and Venus compact only, because each has one global product. se bake planet --help prints the body list from the same table the baker reads.

--region re-bakes and patches only the tiles a geographic box covers, in place, instead of writing a whole new pack. The pack named by --body/--tier (or --output) must already exist — there is nothing to patch otherwise — and the box is resolved separately at every depth that pack’s index holds, so a corrected region is corrected at each level the renderer might draw it from, orbit included. A region re-bake rewrites levels; it cannot add one (docs/design/PLANETARY_APPEARANCE.md §2.1). --depth is ignored when --region is given.

--workers compiles that many regions at once, each in its own process; only this process writes the pack file. Each worker gets the block-cache budget divided by the worker count, so raising --workers does not raise the memory ceiling. --region ignores it. See docs/archive/agent/reports/2026_08_28_BAKE_THROUGHPUT.md for measured timings.

--resume skips every tile a progress marker beside the destination (<destination>.progress.json) already holds, and a resumed run finishes byte-identical to an uninterrupted one, serial or with --workers. It is refused when the destination is missing or the marker disagrees with its body, tier, depth or tile count, so it cannot blend two bakes (docs/design/PLANETARY_APPEARANCE.md A1). --region ignores it, and a clean finish clears the marker.

--geoid also writes a <body>.sushigeoid coefficient pack beside the terrain output, Earth only, with no tier in the name because one geoid covers every tier of a body: it downloads NGA’s EGM2008 spherical harmonics archive once, truncates to degree 88, and prints a per-station accuracy report before failing the bake if any station’s residual exceeds five metres (docs/design/OCEAN_AND_WATER_SYSTEM/README.md §11.1).

se bake geoid <body> bakes only the .sushigeoid pack beside an existing terrain pack, without re-baking the terrain: --tier picks which terrain pack directory it lands in, since the pack itself is named for the body alone, and --refresh re-downloads the archive. It prints the same per-station accuracy report --geoid does and exits non-zero on the same five-metre failure. Use it instead of se bake planet --geoid whenever the terrain pack already exists.

se bake albedo bakes a body’s measured albedo onto a six-face cube map, cooked/planet/<body>.albedo.sushicube unless --destination says otherwise; it takes --body/-b (default moon) and --refresh. The ice giants get a flat colour from their published albedo spectrum, Jupiter and Saturn a resampled mosaic, and Earth, Mars and the Moon the colour source their baked terrain already carries, with the provenance inside the asset.

The terrain bake downloads a public-domain topography raster once (LOLA for the Moon, from the NASA PDS Geosciences Node — no credentials), caches it under build/planet-cache/, and writes the asset to cooked/planet/<body>.<tier>.sushiplanet. That tree is gitignored, since it holds derived artifacts rather than authored content: see assets/planet/README.md. Nothing breaks without one — a body with no baked terrain falls back to the analytic ground the sky pass already draws.

Three things it does that are worth knowing about:

  • It verifies the grid convention before baking anything. A raster read with longitude mirrored produces a planet that looks entirely reasonable and is wrong everywhere, so the bake samples known landmarks (the South Pole–Aitken floor, the far-side highlands) and refuses to proceed if they are not where they should be.
  • It reports the depth the data supports, not the depth you asked for. --depth may go deeper, but the asset still records the source’s own resolution, so nothing downstream mistakes resampled levels for measurement.
  • It audits its own output. After writing, it re-reads the asset through every rule the engine’s reader applies and compares decoded elevations against the source raster. It refuses to claim success if anything came back further off than quantisation can account for — for the compact lunar tier that is 0.09 m against a 0.18 m step.

se bake climatology — the climatology asset

Builds the climatology the global atmospheric core relaxes toward: three zonal profiles plus two surface fields, written with their provenance inside the asset.

se bake climatology                    # about 15 MB of downloads, once
se bake climatology --bands 90         # two-degree latitude bands instead of one
se bake climatology --refresh          # re-download instead of using the cache
se bake climatology -o /tmp/clim.set0  # write somewhere else
Option Values Default
--refresh flag off (use the cache)
--bands integer, 2 or more 180 (one-degree bands)
--output, -o a path assets/atmosphere/climatology.set0

The bake reads NCEP-NCAR Reanalysis 1, NOAA OISST V2 and Natural Earth coastlines — all public, none needing credentials — derives the profiles and surface fields, and prints an audit of everything it read and derived. It refuses to write if the land total, the implied humidity, or the round trip through its own reader disagrees.

se inspect planet and se inspect climatology

se inspect planet cooked/planet/moon.standard.sushiplanet
se inspect climatology                     # assets/atmosphere/climatology.set0

Each reads a baked asset back and takes one optional path, no options. se inspect planet prints the body, the tile pyramid, the elevation range and the provenance, and defaults to cooked/planet/moon.compact.sushiplanet. se inspect climatology prints the grid, the extremes and the provenance.

se cook matter

se cook matter assets/model.gltf
se cook matter assets/model.gltf --spacing 0.05 --out cooked/matter

Runs the se_matter_cook tool from build/default over one .gltf or .glb model and cooks its nodes into .sushimatter blobs. --spacing sets the lattice spacing in metres, --material overrides the recipe’s material id, and --out names the directory to write; without --out the tool cooks into memory only. A plain se build builds the tool.

Add-ons

se addon — third-party add-ons

Scaffolds and builds a behaviour add-on against the engine’s own add-on SDK, from a project directory that lives outside the engine checkout.

se addon new spinner                        # scaffold addons/spinner/ in the current project
se addon new spinner --project /path/proj   # scaffold under a different project
se addon build addons/spinner               # build it against the engine's SDK
se addon build addons/spinner --type debug  # build it Debug
Command Argument Option Default
se addon new name (a C identifier) --project the current directory
se addon build addon_dir --type, -t the engine’s own build type

se addon new writes <project>/addons/<name>/ from the templates in cli/sushiengine/templates/addon/, with one behaviour to start from and a CMakeLists.txt that consumes the generated SushiEngineAddonSDKConfig.cmake.

se addon build installs the addon_sdk component from the engine’s own build tree, configures and builds the add-on against it with the engine’s own compiler and toolchain, copies the result to <project>/addons/bin/<name>.dll (.so off Windows), and — on Windows only, since the check reads PE import tables — verifies the copy links the same C runtime as the engine. A --type other than the engine’s own still builds, with a warning that the host will refuse the result at load.

It needs a built engine tree to install the SDK from (se build), and an engine checkout to find that tree in: SUSHIENGINE_ROOT, when set, names it directly; otherwise the command falls back to the checkout this installed CLI was built from, which only resolves under an editable install (pip install -e cli), not from a wheel. Set SUSHIENGINE_ROOT when running se addon build from a wheel install or from outside the engine checkout.

Building without the CLI

The CLI is a convenience, not a requirement — it only runs CMake and CTest for you. See the Getting set up section of CONTRIBUTING.md for the equivalent raw cmake invocation.