Venus Lua API › Core

Environment & Sandbox

The sandboxed Lua subset scripts run in — which libraries and globals exist, and the per-script isolation model.

authored lua/venus_lua/src/sandbox.rs, lua/venus_lua/src/lua_state.rs

Environment & Sandbox

Scripts run on Lua 5.5, but not the stock interpreter. Before any script loads, the engine
opens a reduced set of standard libraries, removes several base-library globals, and hands every
script its own isolated global environment. Two goals: stop one script from corrupting another (or
the engine), and deny scripts the host filesystem / process / debug reach that game logic never
needs.

If a name from the Lua 5.5 manual is missing at runtime, this
page is why. The source is the authority: luaL_open_sandboxed_libs picks the library set,
sandbox_globals removes the base globals, and sandbox.rs builds the per-script environment.

Standard libraries

Library State Notes
base (_G) open (reduced) Core functions — but several are removed; see below.
coroutine open Full.
math open Full.
string open Full. Its shared type-metatable is sealed against cross-script poisoning.
table open Full.
utf8 open Full.
package excluded No module loading — the engine loads scripts, not require.
io excluded No file or stream access.
os excluded No process, environment, clock, or os.exit. Use Time for game time.
debug excluded No introspection / upvalue reach — that is a sandbox escape route.

Base-library globals removed

Even inside the base library, these are set to nil (or replaced) by sandbox_globals:

Global State Why
load, loadfile, dofile removed No loading arbitrary chunks at runtime.
require removed No module system (package isn’t opened).
collectgarbage removed Scripts don’t drive the shared GC.
getmetatable removed Closes the entire metatable-introspection poisoning class — scripts have no way to obtain a metatable reference.
rawget, rawset removed No raw bypass of the environment’s protective metatable.
_G resolves to nil The global table itself is unreachable, so there is no back door to shared globals.
print replaced Routed to the engine log / console (prefixed [Lua]), honoring __tostring — not raw stdout.

setmetatable stays — script-level OOP (setmetatable(obj, Class)) needs it. It is blocked only
on protected targets (a script’s _ENV, engine userdata, the string type), which raise cannot change a protected metatable.

Surviving base globals (the complete set): assert, error, pcall, xpcall, type, tostring,
tonumber, pairs, ipairs, next, select, rawequal, rawlen, setmetatable, warn,
_VERSION. (warn writes to stderr, unlike the engine-routed print.)

Per-script globals (_ENV)

Each loaded script chunk gets a fresh environment table as its _ENV. That table is the
isolation boundary:

The one shared channel: GameState

A single global table, GameState, is intentionally shared across all scripts — it is excluded
from the per-script copy, so writes to GameState.<field> are visible everywhere. It is the
sanctioned way to hand objects between scripts; use script-local or
self state for everything else.

Populate fields on it; never reassign GameState itself. GameState = {...} writes the name
into the assigning script’s private env (per the rules above) and is invisible to everyone else —
only GameState.field = ... mutates the shared table. And guard your reads: a consumer script
may run before the producer has set a field, so the shipped idiom is GameState and GameState.x.

-- Producer — the player script publishes itself:
if GameState then GameState.player_go = self.object end

-- Consumer — an orbit camera, guarded because it may load first:
local player = GameState and GameState.player_go
if player then
    -- follow player
end

The shipped scripts use exactly this: the session-setup script publishes camera_go and
editor_picker, the two player scripts publish player_go, and the camera / editor-pick / UI
scripts read them with the guarded idiom above.