Summer 2026 Music Kit Competition is now here! - Create a summer themed electronic music kit and submit it by - Enter Now!

Scripting
Scripting: In-Depth

This page explains how the scripting system works under the hood. You don't need any of it to get started, but you'll want it when your scripts grow beyond a single door.

Script instances and lifecycle

Every lambda_point_script entity owns its own script instance. Your file's top-level variables and every callback you register belong to that instance:

  • Two entities running the same file get completely separate variables and callbacks.
  • Removing the entity destroys its instance and releases all its callbacks.
  • Reloading (the ReloadScript input) re-runs the file from the top and clears every callback from the previous run.

OnActivate

Instance.OnActivate(callback) registers a callback that runs after the script has executed successfully, including after each successful reload. It's a good place for setup that should re-run on reload.

Keeping state across reloads

Reloading normally resets everything. Instance.OnScriptReload lets the old script hand selected values to the new one:

import { Instance } from "lambda_script/point_script";

let counter = 0;

Instance.OnScriptReload({
	before: () => ({ counter }),          // Runs in the old script; return what to keep.
	after: (memory) => {                  // Runs in the new script with what was kept.
		if (memory !== undefined)
			counter = memory.counter;
	},
});

before() runs in the old script before its callbacks are cleared, and its return value is retained. After the new file executes, its own after(memory) registration receives that value. Either callback may be omitted.

This is invaluable during development: you can iterate on a live map with ReloadScript without losing your round state.

Imports

Scripts can import from exactly one module:

"lambda_script/point_script"

No other modules, local files, npm packages, or Node.js built-ins can be imported. The built-in exports are:

import {
	DamageType,
	HitGroup,
	HUD,
	Input,
	Instance,
	PointTemplate,
	TraceMask,
} from "lambda_script/point_script";
Export What it is
Instance The main API: logging, entity I/O, searches, players, events, thinks, tracing, save data.
HUD Destinations for PrintMessage: chat, console, or centre-screen.
Input IN_* action flags for checking player button state.
DamageType / HitGroup Damage-type and hit-location constants used by damage events.
PointTemplate Runtime type marker for point_template entities; enables instanceof checks and ForceSpawn().
TraceMask Source trace-mask constants for TraceLine / TraceBox.

Optional APIs: Core, CS, and IsAvailable

Servers can expose additional APIs. The Lambda project supplies Core (build information) and, when the Counter-Strike gamemode integration is loaded, the CS round and event API with its enums (CSTeam, CSRoundEndReason, CSWeaponType, CSRoundState, CSGearSlot, CSDamageFlags):

import { Core, CS, CSTeam, CSRoundEndReason, Instance } from "lambda_script/point_script";

Instance.Msg(`Version: ${Core.GetVersion()}`);

CS.OnRoundEnd((winningTeam, reason) => {
	if (winningTeam === CSTeam.CT && reason === CSRoundEndReason.TARGET_SAVED) {
		Instance.Msg("Counter-Terrorists defused the bomb");
	}
});

These are runtime-provided: importing a name that doesn't exist on the current server fails during module loading, before any of your code runs. For a script that must also work outside that environment, import the module namespace and check availability first:

import * as Script from "lambda_script/point_script";

if (Script.Instance.IsAvailable("CS")) {
	Script.Instance.Msg(`warmup: ${Script.CS.IsWarmupPeriod()}`);
}

Project-specific extensions

A server or gamemode may provide further named classes, enums, and events, and may add extra methods to Entity or player values (for example player.SetHasHelmet(true)). These behave like ordinary entity methods but are server-specific. Consult the project's own documentation, and add their declarations to a project-specific .d.ts file for editor support. Calling a player-only extension on a non-player throws TypeError.

Project On<Event> callbacks may support returning a result object (such as { damage: number } or { abort: true }) when the project documents a result contract; returning undefined or null means "no result".

The execution model

Synchronous, with a watchdog

Exposed native functions run synchronously unless they register a callback or queue delayed entity I/O. Top-level script execution and each callback have a 100 ms watchdog limit. That is far more than map logic needs, but it means an accidental infinite loop is stopped rather than freezing the server.

Keep callbacks short. For work spread over time, use timers or the think system.

Timers

  • Instance.SimpleTimer(delay, callback) runs a callback once after a game-time delay.
  • Instance.CreateTimer({ delay, repetitions, callback }) returns a controllable Timer that can be paused, resumed, stopped, reconfigured, and removed.

Timers use server game time, so they do not progress while the server is not simulating (such as normal empty-server hibernation). Each script instance may own at most 256 timers, and every timer is cancelled when its owning script reloads or is destroyed; a Timer retained through reload memory will therefore be invalid. See the timer reference for the full Timer API.

Thinks

  • Instance.SetThink(callback) stores the instance's single think callback (calling it again replaces the previous one).
  • Instance.SetNextThink(time) schedules it for an absolute game time; use Instance.GetGameTime() + seconds. The schedule clears before the callback runs, so repeating work must re-schedule itself.

Timers cover most delayed and repeating work; the think system remains useful for custom per-tick scheduling where you control the exact next execution time.

Promises and async/await

QuickJS Promise jobs run after each script entry point and remain covered by the watchdog, so async/await works when your own code resolves the Promise. Browser and Node.js timers (setTimeout, setInterval) do not exist; a timer or think callback may resolve a Promise, and its continuation then runs safely on the server thread:

import { Instance } from "lambda_script/point_script";

const wait = (seconds) =>
	new Promise((resolve) => Instance.SimpleTimer(seconds, resolve));

Instance.OnScriptInput("StartSequence", async () => {
	Instance.EntFireAtName({ name: "gate_1", input: "Open" });
	await wait(2);
	Instance.EntFireAtName({ name: "gate_2", input: "Open" });
});

QueueAfterThinks

Instance.QueueAfterThinks(callback) queues a one-shot callback for after every entity has finished its physics and think work this server tick. This is useful when an entity might currently be mid-way through handling input or movement. Callbacks run in queue order, with a safety budget of 1,024 per tick; extras roll over to the next tick.

Working with entities

Entity values are handles, not the entities themselves. The handle is re-resolved on every use:

  • If the underlying entity is deleted, entity.IsValid() returns false, and every other entity function throws ReferenceError. IsValid() is the only safe call on a dead handle.
  • Searches for the same live entity return the same JavaScript object, so first === second comparisons work and you can attach your own properties (piece.square = "e4").

Store handles only when you need to, and validate before later use:

const target = Instance.FindEntityByName("temporary_target");

Instance.SetThink(() => {
	if (target?.IsValid())
		Instance.Msg(target.GetClassName());
});

Name searches support * wildcards: Instance.FindEntitiesByName("chess.piece.*").

Debugging your scripts

  • Test locally with Lambda vScript Environment. Content Authoring Tools installs this local gamemode so you can run your scripts on a singleplayer version of Garry's Mod, without needing a Lambda server.
  • The server console is your log. Instance.Msg output and every uncaught exception (with its JavaScript stack trace) land there.
  • OnScriptError output on the entity fires whenever loading or a script input fails. Wire it to a game_text during development so failures are visible in-game.
  • Debug drawing with Instance.DebugScreenText, Instance.DebugLine, and Instance.DebugBox visualises positions, traces, and volumes in the world.
  • Cheat commands via Instance.RegisterCheatCommand("name", callback) give you console commands to trigger script paths by hand (requires sv_cheats 1).
  • ReloadScript on the entity re-runs your file without restarting the map; combine with OnScriptReload to keep state.
  • Editor checking with point_script.d.ts and // @ts-check (setup) means most typos never make it to the game.

Shipping your scripts in the BSP

You can ship scripts as loose addon files, but packing them into the map's BSP means servers and players need nothing extra.

The easiest way to pack is the Lambda vScript IDE included with Content Authoring Tools, which can export your .js files directly into a map's BSP and follows the rules below for you. If you pack manually instead, use Assembly (or another pakfile tool) to add each script to the BSP pakfile using its full virtual path:

scripts/vscripts/security_door.js
scripts/vscripts/my_map/round_logic.js

When Lambda vScript loads, it extracts the pakfile's scripts/vscripts/ tree into a temporary directory and mounts it at the front of the game's search path, so a packed script takes priority over a loose or addon file with the same path. The temporary directory is removed again at shutdown.

Packing rules:

  • Only files below scripts/vscripts/ are extracted.
  • Use lowercase paths.
  • Store script entries without per-file ZIP compression (the BSP pakfile lump itself may still use Source's normal LZMA compression).
  • A BSP may contain at most 1,024 mounted scripts and 16 MiB of script data.

Runtime limits

Limit Value
Top-level execution / each callback 100 ms watchdog
JavaScript memory 32 MiB
Stack 512 KiB
Script file size 1 MiB
Scripts mounted from a BSP 1,024 files, 16 MiB total
Delayed entity inputs queued per instance 1,024
Timers per script instance (including simple timers) 256
Callbacks per event type per instance 256
Save data 1 MiB per map

Exceptions and stack traces are written to the server console. Entity values are handle-based and re-checked whenever they are used.