Elysium scripting Editor API index
Start

How scripts run

Files, lifecycle, errors and time budgets, threads, the sandbox, and how this differs from stock Lua.

This page is the model behind everything else: where scripts live, what happens when they load and unload, what stops them, which thread a handler runs on, and what a script may and may not touch. None of it is difficult, but knowing it saves you from the usual surprises.

#Files and folders

PathWhat it is
C:\elysium\scripts\*.luaThe scripts. Loaded in alphabetical order. The file name without .lua is the script's name.
C:\elysium\scripts\<name>\The script's own folder: its images, fonts, sounds and whatever files it writes. script.folder() returns it, script.path( "x.png" ) builds a path inside it. It is created when something is first written there.
C:\elysium\scripts\lib\Modules shared by all scripts, for require.
C:\elysium\scripts\.data\state.jsonWhich scripts are switched on or off, and which are trusted.
C:\elysium\scripts\.data\<name>.values.jsonThe values of the script's menu controls.
C:\elysium\scripts\.data\<name>.store.jsonWhat the script saved with store.

The name is the identity. Renaming a script file makes it a new script: its row, its saved values and its folder stay behind under the old name.

#Lifecycle

  1. Start. On the first frame after the cheat is injected every .lua file is loaded, one after the other, in alphabetical order. Each gets its own Lua state: its own globals, its own handlers, nothing shared with any other script.
  2. Load. Loading means the file runs once from top to bottom, with a budget of two seconds. Whatever it registers - handlers, controls, timers, windows - is live from that moment. A script that fails here is stopped before it did anything else.
  3. Run. From now on the script only runs when something calls one of its handlers.
  4. Unload. When the script is reloaded, switched off, or the cheat closes, its unload handlers run first (half a second at most), then the state is closed and everything the script owned goes with it: handlers, timers, controls, windows, fonts, textures, shaders and particles.

In the Scripts tab:

#Errors and time budgets

An error stops the script. An error in any handler - or in the file itself while loading - is reported in the output panel with a traceback, the row turns red, and the script stays stopped until it is reloaded. Its controls disappear, its handlers are dead. The reason is practical: a handler that fails on every frame would otherwise write a hundred errors a second and bury the first one, which is the one that matters.

Time is limited. Every call into a script has a budget:

CallBudget
Loading the file2 s
unload handlers0.5 s
Every other handler, timer and callback30 to 40 ms

A call that runs over is interrupted with the error script ran past its time budget, and the script is stopped like for any other error. While a script runs, the thread that called it waits: a slow frame handler is a slow game, a slow move handler delays the command. In practice a handler should finish in well under a millisecond; most do it in microseconds.

print( ... ) writes a line to the output panel, warn( ... ) writes one marked as a warning. The panel keeps the last 256 lines.

An error inside an unload handler is reported but does not stop the unload - the state is closed either way.

#Threads

The cheat calls scripts from several threads, because that is where the things they react to happen. Which handler runs where:

Render thread
frameespmenuafter / every / next_framecontrols: on_change, buttons, custom drawscript load and reload
Game thread
movemove_poststagestage_postgame:<event>map_load, map_unload
Window thread
input
The game's camera callback
view

Two rules make this easy to live with.

One callback at a time. However many scripts there are and whichever thread calls them, exactly one callback of one script runs at any moment; the others wait their turn. A script's own tables therefore need no locking, and two handlers of the same script - move filling a table, frame reading it - can share data freely. velocity.lua does exactly that.

A few things belong to one thread. The runtime enforces them and says so when you get it wrong:

#Sandboxed and trusted

A script is sandboxed until you decide otherwise. A sandboxed script can read the game, draw, add menu controls, change the cheat's settings, and read and write files inside its own folder. It cannot:

Needs trusted
net.get, net.postTalk to the network
mem.*Read or write memory, find signatures, call native code
clipboard.get, clipboard.setTouch the clipboard
util.open_urlOpen a web address
ent:address()See where an entity lives in memory
cheat.save_config, cheat.load_configReplace the whole state of the cheat
Files outside its folderAbsolute paths and drive letters

Calling one of these without the flag raises an error that says which, and how to turn it on. The sandboxed / trusted chip in the script's row toggles it; the script reloads when you do, because code that already ran has already been told no. The flag is stored per script name in .data\state.json, and script.trusted tells a script which it is.

#How this differs from stock Lua

The language is Lua 5.4 and everything in the standard string, table, math, utf8, coroutine and os.time/os.clock/os.date works as documented. Differences:

Every callback also runs under a hook that counts instructions, which is how the time budget is enforced. You will not notice it, apart from the error it raises when a loop does not end.

#Modules and shared code

require( "name" ) loads name.lua from the first of these places that has it:

  1. scripts\lib\name.lua
  2. scripts\name.lua
  3. scripts\<this script>\name.lua

A dotted name is a path: require( "gfx.rounded" ) loads gfx\rounded.lua. Names are plain identifiers, so a script cannot reach a file outside those folders by writing ... The result is cached per script, as usual.

Note what this does not give you: every script has its own state, so two scripts that require the same module get two separate copies of it. Scripts cannot call each other or see each other's globals, and a sandboxed script cannot read another script's files. A module is for sharing code, not state.

#Saving state

Two things are saved for you:

To reset a script to its defaults, switch it off, delete its two .data files and switch it on again.

Elysium scripting guide · built 2026-10-03