Lua/Luau Scripting Overview
DDDBrowser supports scripting with Luau, a fast, gradually-typed scripting language derived from Lua 5.1. Scripts enable interactive behaviors, animations, game logic, and dynamic content in scenes.
What is Luau?
Luau is a scripting language that:
- Is Lua 5.1 compatible - Familiar syntax for Lua developers
- Has gradual typing - Optional type annotations
- Is fast - Optimized for performance
- Is safe - Sandboxed execution environment
Scripts in DDDBrowser are executed in a secure sandbox with access to engine APIs.
Sandbox restrictions
The following Lua globals are removed (set to nil) before scripts run:
os,io,debug,package,bufferrequire,dofile,loadfile,load,loadstring
Use Engine, Scene, Gamemode, and localStorage instead of OS/filesystem APIs. There is no os.time() — persist only your own script fields in on_save.
Runtime budgets
| Limit | Default |
|---|---|
| VM heap | 64 MiB (script.execution.maxMemoryBytes) |
| Script source file | 1 MiB |
| Per-frame script budget | ~5 ms (script.execution.maxFrameTimeSeconds) |
Exceeding the heap or file size fails script load/execution; long frames are interrupted.
Entity Scripts vs Gamemode Scripts
DDDBrowser supports two types of scripts:
Entity Scripts
- Attached to: Individual instances in the scene
- Scope: Per-instance behavior
- Lifecycle:
on_start,on_update,on_interact, etc. - Access: Entity ID, asset ID, position, scale, rotation
- Use cases: Object behaviors, animations, interactions
Gamemode Scripts
- Scope: Scene-wide logic
- Lifecycle:
on_start,on_update,on_save,on_load - Features: State management, events, persistence
- Use cases: Game rules, global state, scene-wide logic
Script Structure
All scripts must return a table (object) with lifecycle methods.
Classic DDD lifecycle
local MyScript = {}
function MyScript:on_start()
-- Initialization code
self.value = 0
end
function MyScript:on_update(dt)
-- Update code (called every frame)
self.value = self.value + dt
end
return MyScript
Blazium gdclass compatibility (passthrough)
Scripts authored for Blazium Luau gdclass / hybrid classes can run with little or no rewrite. After load, if the returned table uses _ready / _process (or extends / __gdclass) and does not define classic on_start / on_update, DDDBrowser uses the gdclass adapter:
local M = gdclass("MyBehavior", "Node3D")
function M:_ready()
-- called once after attach (like on_start)
end
function M:_process(dt)
-- called every frame (like on_update)
self.position = { x = 0, y = self.position.y + dt, z = 0 }
end
return M
Supported facade on self (entity scripts): entity, asset_id, data, position / global_position, rotation / global_rotation, scale, visible, queue_free().
Stubbed / unsupported: get_node, get_tree, emit_signal, wait / await / wait_signal, ClassDB userdata, Node.new(), require(res://…), full signal graph. Globals gdclass, class, export, and signal exist as no-op/load-time helpers so Blazium scripts do not error at parse time.
Key points:
- Script must return a table
- Use
selffor instance variables - Methods use colon syntax (
:) - Lifecycle methods are optional
- Prefer classic
on_*or gdclass_ready/_process— if both classic methods exist, classic wins
Environment Variables
Scripts have access to these built-in variables:
Entity Scripts
entity(number): Unique entity IDasset_id(string): Asset ID of the instance
All Scripts
Engine(table): Engine API (see Engine API)localStorage(table): Persistent storage (see localStorage API)Gamemode(table): Gamemode API (see Gamemode API)Scene(table): Scene API (see Scene API)
Script Lifecycle
Scripts follow a lifecycle:
- Load: Script file is loaded and compiled
- on_start: Called once when script is initialized
- on_update: Called every frame (if defined)
- on_interact: Called when player interacts (entity scripts)
- on_save: Called when scene state is saved (optional)
- on_load: Called when scene state is loaded (optional)
- on_shutdown: Called when script is unloaded
See Script Lifecycle for details.
Script Attachment
Scripts are attached to instances in the scene JSON:
{
"id": "animated-object",
"asset": "my-model",
"position": {"x": 0.0, "y": 0.0, "z": 0.0},
"rotation": {"x": 0.0, "y": 0.0, "z": 0.0},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0},
"script": {
"file": "animation-script",
"data": {
"speed": 2.0
}
}
}
Script properties:
file(string): Asset ID of the scriptdata(object, optional): Initial data passed to script
The data object is available in the script as self.data or via the script's environment.
Gamemode Scripts
Gamemode scripts are defined at the scene level:
{
"gamemode": {
"file": "gamemode-script"
}
}
Gamemode scripts have access to:
- Scene-wide state management
- Event system
- Save/load functionality
- localStorage for persistence
Example Script
Here's a simple ping-pong animation script:
local PingPong = {}
function PingPong:on_start()
self.time = 0
self.amplitude = 10
self.speed = 0.5
end
function PingPong:on_update(dt)
self.time = self.time + dt
local offset = math.sin(self.time * self.speed) * self.amplitude
if Engine and Engine.setEntityPosition then
Engine.setEntityPosition(self.entity, 0, offset, 0)
end
end
return PingPong
This script:
- Initializes animation parameters in
on_start - Updates position every frame in
on_update - Uses
Engine.setEntityPositionto move the entity - Uses
self.entityto reference the entity ID
Security and Sandboxing
Scripts run in a secure sandbox:
- No file system access: Cannot read/write files (
os/io/ etc. banned) - No arbitrary sockets: Network is only via
Engine.httpRequest— async HTTPS GET, subject to Settings Network Policy host allowlist and private-IP blocks (native client, not browser CORS) - No OS access: Cannot execute system commands
- Travel Allow HTTP is separate: Confirming Allow HTTP for scene travel does not enable plain HTTP for
Engine.httpRequest - Script origins: Executable
.luauhosts are gated separately (same-origin + Settings → Script origins)
See Engine API — HTTP and Application Settings.
Error Handling
If a script encounters an error:
- The error is logged
- The script is disabled (no further calls)
- Other scripts continue to run
- The scene continues to function
Always check for API availability:
if Engine and Engine.setEntityPosition then
Engine.setEntityPosition(self.entity, x, y, z)
end
Performance
Scripts are executed every frame, so:
- Keep updates fast: Avoid expensive operations in
on_update - Use delta time: Use
dtfor frame-rate independent updates - Cache values: Store frequently accessed values
- Limit updates: Only update when necessary
Next Steps
- Script Lifecycle - Learn about lifecycle methods
- Engine API - Access engine functionality
- Gamemode API - Scene-wide scripting
- Examples - See example scripts