Writing and shipping an add-on
An add-on is a shared library, built outside this repository, that a SushiEngine project loads
at startup and that registers native behaviors into BehaviorRegistry. It is the way to add
game logic without editing engine CMake or rebuilding the engine. This guide walks from
se addon new to a packaged player that loads the result, with the commands and outputs a
real run produced.
If you have not written a native behavior before, read
NATIVE_BEHAVIORS.md first — an add-on’s behavior is the same
IEntityBehavior that guide describes, just compiled in its own project instead of the
engine’s.
What an add-on is, and is not
An add-on links nothing from the engine. It compiles against a published header subset and
receives every capability it uses — the logger, the assertion handler, the event bus, the
behavior registry — as a pointer inside SeAddonHost, handed to it once, at load. The engine
never sees the add-on’s source. What it leaves out, in this release: components defined by an
add-on, editor panels, physics services, and hot reload. An add-on loads once at startup and
stays loaded until the process exits; see
ADDON_EXTENSION_POINT.md §3 for why, and what a later
release may add.
An add-on must be built with the compiler that built the engine, in the engine’s own build
type. SushiEngineAddonSDKConfig.cmake records the engine build’s compiler identity, version
and ABI, and refuses to configure against a different one. A different build type still
configures — se addon build --type debug against a release engine, for instance — but the
host’s fingerprint gate refuses to load the result, and names the field that disagreed. This
is not a limitation to work around; it is the whole point of the fingerprint, described in
ADDON_EXTENSION_POINT.md §3.2: an add-on built with a
different compiler is silently wrong in ways nobody wants to debug, and this way it is refused
by name instead.
SUSHIENGINE_ROOT
se addon build needs the engine checkout it is building against — the source of the SDK it
installs and the compiler and CMake toolchain it configures with. It finds one two ways, in
order:
- The environment variable
SUSHIENGINE_ROOT, when set. - Failing that, the checkout the
sepackage itself was installed from, if that checkout still has its rootCMakeLists.txtandengine/directory in place.
An add-on’s own project carries no marker the second path can find on its own — that path only
works from inside a normal engine checkout. From an add-on project outside the engine tree,
set SUSHIENGINE_ROOT once, in your shell profile or before the command:
export SUSHIENGINE_ROOT=/path/to/sushiengine
Without it, se addon build stops with “Could not find the engine checkout; set
SUSHIENGINE_ROOT.”
1. Scaffold the add-on
From inside a SushiEngine project (a directory se project already knows about), or any
directory once SUSHIENGINE_ROOT is set:
se addon new spinner
[SUCCESS] Wrote C:\Users\sushi\SushiProjects\rehearsal\addons\spinner
This writes <project>/addons/spinner/: a CMakeLists.txt that calls
find_package(SushiEngineAddonSDK REQUIRED) and one function,
sushiengine_add_addon(spinner SOURCES ...), and a source file registering one behavior,
SpinnerBehavior, that subclasses IEntityBehavior. Edit on_update — or any of the other
lifecycle hooks NATIVE_BEHAVIORS.md documents — to add your own logic;
nothing else in the generated tree needs to change to build.
se addon new refuses a name that is not a valid C identifier, and refuses to overwrite an
existing addons/<name>/ directory; neither failure writes anything to disk.
2. What sushiengine_add_addon does for the author
The generated CMakeLists.txt calls exactly one function, so the add-on author never sees a
compiler flag. Behind it, sushiengine_add_addon:
- Compiles the SDK’s own support sources into the add-on — the entry binding, the log
formatter, the assertion dispatch, the emergency sink, the debugger and the stack trace —
so the add-on links no engine library and still gets
se_addon_entry, logging and assertions for free. - Sets the language standard, hides symbol visibility by default, and applies the engine
build’s own
MSVC_RUNTIME_LIBRARY, iterator debug level, exception model, RTTI and floating-point determinism flags, taken from the installed SDK’s own build. - Builds the add-on as a
MODULElibrary with nolibprefix, named to match the platform’s loader convention.
The author’s CMakeLists.txt.in template is written once by se addon new into the user’s own
project; writing that template is not an edit to this repository’s build configuration, since
it configures a build this repository never runs.
3. Build it
se addon build addons/spinner
[1/8] Building CXX object CMakeFiles/spinner.dir/.../addon_sdk/sdk/source/debugger.cpp.obj
...
[8/8] Linking CXX shared module spinner.dll
[INFO] Copying ...\addons\spinner\build\spinner.dll -> ...\addons\bin\spinner.dll...
[INFO] Checking the add-on's C runtime imports...
C:\Users\sushi\sushiprojects\rehearsal\addons\bin\spinner.dll: release
[SUCCESS] Built C:\Users\sushi\sushiprojects\rehearsal\addons\bin\spinner.dll.
In order, se addon build:
- Installs the
addon_sdkcomponent from the engine’s own build tree. - Configures the add-on’s
CMakeLists.txtagainst that installed SDK, with the engine’s own compiler and, by default, the engine’s own build type. - Builds it.
- Copies the artefact to
<project>/addons/bin/<name>.dll(.soon Linux) — the layout the editor scans on startup. - Checks the copy’s C runtime imports against the engine’s own, on Windows, and fails the command if they disagree.
Build against a different type deliberately with se addon build --type debug; the command
still succeeds, with a warning that the host will refuse the result at load.
4. Load it
In the editor: not yet. se addon build already places the artefact at
addons/bin/<name>.dll, the layout the editor is designed to scan on startup, but the editor
does not open that directory yet — see “What this guide does not cover yet” below. Once it
does, the design is that its log panel shows one Info line per accepted add-on,
addon_host: <name> loaded, engine <version>, the behavior appears in the ScriptComponent
type list, and a refused add-on logs an Error line naming the field that disagreed while the
editor continues without it.
In a packaged player, se package builds every add-on the project lists, installs the
artefacts beside the player executable, and writes their names into the staged boot.json’s
addons array. At boot, the player resolves each named add-on under
<install>/addons/<name><extension> and loads only those — a stray library left in that
directory without a matching manifest entry is never loaded.
se package --tree player --request build_request.json --prefix ...\package
[SUCCESS] Packaged 1 add-on(s): spinner.
CPack: - package: .../packages/SushiEngine-0.1.0-Windows.zip generated.
[INFO] Verifying the installed tree with a scrubbed environment...
[INFO] [player] player started (main.cpp:126)
[INFO] [addon_host] spinner loaded, engine 0.1.0 (addon_loader.cpp:182)
SushiEngine player: 60 headless frame(s) completed.
C:\Users\sushi\SushiProjects\rehearsal\package\addons\spinner.dll: release
ok: 1 add-on(s) loaded and passed their import check: spinner
[SUCCESS] The installed tree runs with no developer environment on PATH.
What a refusal looks like
A fingerprint mismatch — the commonest cause is a debug add-on beside a release player, or the
reverse — is refused rather than half-loaded: no se_addon_entry call, no registration, no
subscription. The player’s log names the field:
[ERROR] [addon_host] spinner refused: runtime_library mismatch: 1 vs 2; host engine 0.1.0,
add-on engine 0.1.0 (addon_loader.cpp:93)
[ERROR] [player] SushiEngine::Player::PlayerApp: refused 1 add-on(s) named by the boot manifest
exit=1
The player exits with code 1 when an add-on its manifest names is refused — a shipped game
without its own gameplay code is broken in a way the player cannot show, so it stops rather
than run silently incomplete. The editor’s policy is different: it logs the same refusal at
Error, publishes addon.load_refused, and continues, so a developer can fix the add-on from
inside the editor without restarting it. An add-on whose registration throws is refused the
same way in both hosts; the editor additionally warns that any registrations the add-on made
before throwing stay in place until restart, since nothing can undo them.
What this guide does not cover yet
The editor does not load add-ons yet — that wiring is the one piece of
BETA_RELEASE.md B6 still open, and the “In the editor” step above
describes the behaviour once it lands, not something you can run today. Nothing here has run on
Linux: se addon build, the SDK install and the C runtime import check are all proven on
Windows only so far. See REMAINING_WORK.md’s B1–B7 row for the
current state of both gaps.

