Contents

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:

  1. The environment variable SUSHIENGINE_ROOT, when set.
  2. Failing that, the checkout the se package itself was installed from, if that checkout still has its root CMakeLists.txt and engine/ 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 MODULE library with no lib prefix, 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:

  1. Installs the addon_sdk component from the engine’s own build tree.
  2. Configures the add-on’s CMakeLists.txt against that installed SDK, with the engine’s own compiler and, by default, the engine’s own build type.
  3. Builds it.
  4. Copies the artefact to <project>/addons/bin/<name>.dll (.so on Linux) — the layout the editor scans on startup.
  5. 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.