Guide
Sandbox limits
Every script runs in its own Lua state with the limits below. The limits are fixed in the runtime; a script cannot raise them.
Memory
| Limit | Value |
|---|---|
| Heap per script | 8 MiB |
| Heap for all scripts together | 32 MiB |
| Source file size | 1 MiB |
| Scripts loaded at once | 16 |
An allocation that would cross a heap limit fails inside Lua as a memory error. A source file larger than 1 MiB is refused at load. The seventeenth Load is refused with “too many scripts are loaded; unload one first”.
Instructions and time
The top level and each event callback have an instruction budget and a wall-clock budget. The runtime counts instructions with a Lua hook that fires every 100 instructions, so a budget is enforced to the nearest 100.
| Stage | Instructions | Wall clock |
|---|---|---|
| Top level (load) | 200 000 | 250 ms |
Each event callback (paint, game events) |
50 000 | 4 ms |
The shutdown event |
50 000 | 50 ms |
The top level of the file gets the full load budget. When a budget runs out the callback stops with “instruction budget exceeded” or “time budget exceeded”.
What happens on an error
- An error in the top level refuses the load. The Lua state is closed and the message shows in the tab.
- An error in an event callback closes the script. It shows as
error:in the tab and stops running until you load it again. - An error in the
shutdowncallback is logged; the script is unloaded regardless.
What is opened
The Lua standard libraries base, table, string, math and utf8 are opened. io, os, debug, package and coroutine are not.
What is removed
These globals are removed from every script:
print warn collectgarbage dofile load loadfile require rawget rawset rawequal rawlen setmetatable getmetatable
string.dump is also removed. There is no way to load code at run time and no bytecode is ever accepted; only text source compiles.
The require that the marketplace provides for pinned dependencies is a different function; see require and pinned dependencies.
Each script has its own globals
A script’s top-level assignments go into a per-script _ENV, not the shared globals. Two loaded scripts never see each other’s variables. Reads fall through to the standard globals, so string.format and math.floor work as usual.
Register your event callbacks from the top level with client.set_event_callback; each is an ordinary closure that captures the script’s own upvalues.