Summer 2026 Music Kit Competition is now here! - Create a summer themed electronic music kit and submit it by - Enter Now!
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.
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:
ReloadScript input) re-runs the file from the top and clears every callback from the previous run.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.
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.
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. |
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()}`);
}
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".
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.
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.
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.
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" });
});
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.
Entity values are handles, not the entities themselves. The handle is re-resolved on every use:
entity.IsValid() returns false, and every other entity function throws ReferenceError. IsValid() is the only safe call on a dead handle.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.*").
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.Instance.DebugScreenText, Instance.DebugLine, and Instance.DebugBox visualises positions, traces, and volumes in the world.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.point_script.d.ts and // @ts-check (setup) means most typos never make it to the game.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:
scripts/vscripts/ are extracted.| 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.