Skip to content

Sandbox ​

Scripts run in a restricted Lua 5.4 environment. Only a subset of the standard library is loaded. Anything that touches the filesystem, OS, or arbitrary module loading is blocked. Network access goes through a separate API that can be disabled from the menu.

Loaded libraries ​

  • base: core language functions (type, pairs, pcall, tostring, error, etc.)
  • math: math functions (sin, cos, sqrt, floor, random, etc.)
  • string: string manipulation (find, format, sub, gsub, match, etc.)
  • table: table operations (insert, remove, sort, concat, unpack, etc.)
  • coroutine: coroutine control (yield, resume, create, wrap, etc.)
  • utf8: Unicode string handling (len, codes, char, codepoint, etc.)

Blocked libraries ​

  • io: no file read/write
  • os: no os.execute, os.remove, os.clock, etc.
  • package: no require paths or module loading
  • debug: no debug.getinfo, debug.sethook or stack inspection

Removed functions ​

These are removed even though their library loads. Calling one fails with attempt to call a nil value.

  • From base: dofile, loadfile, require, rawget, rawset, rawlen, rawequal and collectgarbage.
  • string.dump.

load still works, but only for Lua source text. Given a precompiled (binary) chunk, it returns nil and an error message. Script files must be source text too, so a precompiled .lua file does not load.

Each script has its own globals, so a global you set is not visible to other scripts. _G is shared by every script. Code from load also runs in the shared _G unless you pass your own environment: load(src, "chunk", "t", _ENV).

Common replacements ​

  • To persist data, use Storage instead of io. To store a table, turn it into text with json.encode.
  • For timing, use the global clock() function instead of os.clock.
  • For network access, use the HTTP library. It is opt-in and can be turned off from the menu.
  • For modular code, use export and import. require is blocked, but these two globals let scripts share modules with each other:
lua
-- In your library script (e.g. utils.lua):
export("utils", {
    clamp = function(v, lo, hi) return math.max(lo, math.min(hi, v)) end,
})

-- In any other script:
local utils = nil

function on_tick()
    utils = utils or import("utils")
    if not utils then return end   -- library not loaded yet
    local x = utils.clamp(150, 0, 100)   -- 100
end

export(name, module) registers a table under a name. import(name) fetches it, or returns nil until the exporting script has loaded. Scripts load one after another, so an import at the top of a script can run before the library does. Call it from on_tick until it returns a table.