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.tomlunder the SushiStack workspace home, whichhub installwrites so every module in the workspace resolves the same toolchain. A checkout outside a workspace has none. cli/config.local.tomlin 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 carriessandbox,pgs_demoand theExecutionconformance 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. Aboot.jsonsitting 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--headlessruns. 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/editorandbuild/player. A configured tree shows its build type, its backend and four of itsSUSHIENGINE_BUILD_*options. It readsSTALEwhen a file underengine/,applications/ortools/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, orabsentwith these bakecommand 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-runshows every step and changes nothing.--toolchain <name>also installs that SYCL toolchain; repeatable.--no-gpuskips the toolkit for this machine’s GPU.--yesanswers 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, fromdoxygen_exeorPATH;extra planet,extra climatology,extra dev— every module the extra declares imports;validation layer— the Khronos layer manifest sits in the triplet’sbindirectory;docker—docker versionreaches 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 and se unlink
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.
--depthmay 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.

