Create an adapter.
Bring SCRIPT//GO resources to your engine or framework by writing one adapter file and passing the conformance runs.
scriptgo-sdk/docs/site/create-adapter.mdOn this page
This guide is for people who maintain an engine, a framework or a game server and want their users to run SCRIPT//GO resources. You do not port the runtime. You write an adapter: one file that connects the portable runtime to your host.
There are two ways in:
- Your host runs Lua. Write an adapter for the portable Lua core. That is this guide.
- Your engine or server is written in .NET. Embed the .NET host kit (
sdk/dotnet, experimental) instead. It runs Lua 5.4 and TypeScript or JavaScript side by side, with per-resource quotas, and exposes your engine's natives to every language. Its README covers embedding.
Either way, your creators get the rest of SCRIPT//GO on top: packages from the registry
(scriptgo add), compatibility shown from real runs, license checks through the
licensing API, and paid offers sold from their own Tebex store.
How the pieces fit
The runtime in sdk/lua/scriptgo is pure Lua, compatible with Lua 5.1, 5.4 and LuaJIT. It
implements everything that does not depend on an engine: the manifest parser, the scheduler
(CreateThread, Wait, timers), events, exports, the value codec, the sandbox and the capability
registry. It knows nothing about engines.
The adapter supplies only what the host owns:
| The adapter supplies | Used by |
|---|---|
| A clock | Wait, SetTimeout, ScriptGo.time.monotonic |
| File reads (and a file listing, for script globs) | Loading resources, ScriptGo.resource.readFile, require |
| Persistence | ScriptGo.storage |
| Players (optional; the host tracks joins itself) | ScriptGo.players, playerJoining, playerDropped |
| Privileges | ScriptGo.permissions.has, restricted commands |
| Configuration | ScriptGo.config.get, GetConvar |
| Notification output and logging | ScriptGo.ui.notify, the host log |
| HTTP (optional) | ScriptGo.http, ScriptGo.licensing |
The core does not change. To be listed, an environment also needs an entry in the support matrix
(sdk/lua/scriptgo/capabilities.lua, from which sdk/environments.json is generated).
What an adapter looks like
The adapter is a plain table. The reference adapters in sdk/adapters/ (standalone, love2d,
godot, luanti, neovim, cctweaked) show complete ones; the in-memory adapter in sdk/lua/scriptgo/testing.lua is
the smallest.
-- adapters/myengine.lua
return {
environment = "myengine", -- a lowercase slug
adapterVersion = "myengine-0.1.0",
sides = { server = true, client = true }, -- the resource sides this host runs
-- true when this host provides the capability. events and logging are always on.
capabilities = {
storage = true, players = true, permissions = true, commands = true,
config = true, ui = true, http = false, licensing = false,
},
now = function() return MyEngine.time_seconds() end, -- monotonic, in seconds
-- Optional: Unix seconds, when the engine's os.time() is something else (CC: Tweaked's is the
-- in-game hour). Resources then get it from os.time() and ScriptGo.time.now().
-- wallTime = function() return math.floor(MyEngine.epoch_ms() / 1000) end,
log = function(level, origin, message) MyEngine.log(level, origin .. ": " .. message) end,
readFile = function(path) return MyEngine.read_text(path) end, -- nil when missing
listFiles = function(root) return MyEngine.list_files(root) end, -- optional
storage = {
load = function(key) return MyEngine.kv_get(key) end, -- JSON text or nil
save = function(key, text) MyEngine.kv_set(key, text) end,
},
permissions = {
has = function(playerId, privilege) return MyEngine.has_priv(playerId, privilege) end,
},
config = {
-- resourceName is nil for a convar (GetConvar).
get = function(resourceName, key) return MyEngine.setting(resourceName, key) end,
},
ui = {
notify = function(target, n) MyEngine.toast(target, n.title, n.message) end,
},
-- Optional: request({ method, url, headers, body }, done), then done({ status, body, headers }).
-- http = { request = function(req, done) ... end },
}The host is created around it:
local scriptgo = dofile("sdk/lua/scriptgo/init.lua")
local host = scriptgo.newHost({ adapter = dofile("adapters/myengine.lua") })
host:addResource("resources/inventory_pro")
host:startAll()
host:playerJoin(1, "Ada")Capability values in the adapter are true or false. The support levels the registry shows are
declared in the matrix, not in the adapter: supported, partial (say what is missing),
fallback (for example ui printed to the log) and unavailable.
Ticking the scheduler
Your engine already has a main loop. Call host:tick() from it once per frame or server step;
without an argument it reads the time from the adapter's now. Wait(0) resumes on the next
tick.
Sides
Resources have a server side and a client side. Your adapter declares which ones your host runs
(sides):
- Single-process hosts (a game, a desktop app) run both. The client side is player
1. Network events are looped back through the value codec, so tables are copied and functions are refused exactly as on a real network. Do not shortcut this: resources must behave the same everywhere. - Server-only hosts run only server scripts. Luanti is one: its mods are server-side.
- Networked hosts connect real servers and real clients. A host that already runs this
resource shape needs only a shim mapping
ScriptGo.*onto its own API.
Sandbox expectations
The runtime builds each resource side's environment itself. Your adapter must not widen it.
Do not expose io, os.execute, debug, package, load, loadstring, dofile or the
engine's raw API to resources. Expose engine features as capabilities instead.
Note
The Lua sandbox is not a security boundary for untrusted code. Your host should load resources its operator chose to install. SCRIPT//GO itself does not execute uploaded code yet: its validation only parses it.
Conformance
The goal is evidence, not a promise. Conformance is checked in three layers.
- Manifest fixtures.
sdk/lua/spec/fixtures/manifests/holds pairs of*.luamanifests and*.expect.jsonresults. The Lua parser and the TypeScript parser (packages/manifest) must both produce the expected result for every pair. If your host parses manifests itself, run it against the same fixtures. - Runtime behaviour. The SDK's tests run the scheduler, events, exports, codec and sandbox on
every Lua interpreter in
tools/runtimes/bin(5.1, 5.4, LuaJIT, built bytools/build-lua.ps1). The repository exposes them ascorepack pnpm lua:test. - Real runs.
scriptgo verifyruns resources in the real runtime and writes oneCompatibilityRunreport per run intoverification/reports/. The registry ingests these reports throughPOST /v1/admin/compatibility-runs.
The conformance probes in sdk/conformance/ are the reference workload. sg_probe prints one
SGCHECK PASS|FAIL <name> line per check (storage, time, capabilities and the
capability_unavailable refusal, permissions, config, promises, exports, and the official
EventBus and Permissions kits), and scriptgo verify turns those lines into checks of its
report. sg_probe_net checks ScriptGo.http and ScriptGo.licensing against an API, and
sg_probe_perks checks ScriptGo.perks (check, cache, catalog, purchase links, the event queue),
and sg_probe_presence checks player match (ScriptGo.presence: a report, the link codes; the
standalone run also compares the host's fingerprint with Node's HMAC-SHA256).
The
official packages (scriptgo//event-bus, scriptgo//inventory-kit,
scriptgo//permissions-kit) add more workload. A host that does not use the CLI writes the same
report itself, as the LÖVE and Godot verification modes do.
From adapter to listed environment
- Open an issue or contact SCRIPT//GO with your adapter and its capability table.
- The conformance probes and the official packages are run in your runtime, and their reports are ingested by the registry.
- The environment appears in the directory as Beta, listing its capabilities and the gaps that remain. It becomes Available when the adapter is stable and the conformance runs pass.
Each environment page shows the adapter version, the languages and Lua dialects, the runtime versions tested and the latest run (its status, date and run id). Verified comes from runs, never from a form.
See also
- Runtime: the globals and the
ScriptGonamespace your adapter backs. - SDK and capabilities: how resources consume capabilities.
- Environments: the current directory and its matrix.

