On this page
Atlas scripting recipes
Search the generic examples covering controller input and output, feedback, environment checks, video UI, ONNX models, native vision tools, network capture, and core language features.
Use recipes without breaking passthrough
else that writes
false, 0, or a centered stick unless you intentionally want to
suppress the physical control.
Replace generic paths such as models/object_detector.onnx and labels such as
object with values that match your own local assets. Keep controller APIs
directly inside on_input, and use these examples only for accessibility,
local testing, simulation, QA, and research.
9000 and the documentation address 192.0.2.10 with your own,
and keep each filter narrow. See Network events.
Recipe categories
Controller Input & Timing
3 recipes. Read button edges, trigger pressure, and packet timing.
Controller Output
8 recipes. Override only the controls each script intentionally owns.
Environment
3 recipes. Inspect controller families, output hosts, and protocol details.
Controller Feedback
3 recipes. Control Remote Play rumble, lightbar, and player indicators.
Video UI
2 recipes. Draw readable status information over the video feed.
Models
1 recipe. Load and query generic ONNX model assets.
Computer Vision
11 recipes. Use native color, contour, matching, OCR, code, shape, and detection tools.
Language
5 recipes. Practice arrays, structs, references, enums, functions, and runtime values.
Network
5 recipes. Generic, validated Atlas examples.
Browse checked recipes
Controller Input & Timing
3 recipesPressed, held, and released
React once when L1 is pressed or released, and hold Cross only while L1 remains down.
event on_input() {
if (input.pressed("L1")) {
system.log("L1 pressed");
}
if (input.held("L1")) {
output.set_button("cross", true);
}
if (input.released("L1")) {
system.log("L1 released");
}
}
Use a trigger pressure threshold
Read real L2 pressure from 0 to 100 and press D-pad Right only above halfway.
event on_input() {
var pressure = input.get(ps5.l2);
if (pressure > 50) {
output.set_button("right", true);
}
}
Hold a button for one second
Accumulate the smoothed controller packet interval from input.time before activating an output.
var heldMilliseconds = 0;
event on_input() {
if (input.held("L1")) {
heldMilliseconds += input.time;
if (heldMilliseconds >= 1000) {
output.set_button("cross", true);
}
} else {
heldMilliseconds = 0;
}
}
Controller Output
8 recipesHold a button without blocking passthrough
Hold Cross while L1 is down and leave physical Cross untouched when L1 is up.
event on_input() {
if (input.held("L1")) {
output.set_button("cross", true);
}
}
Remap one button to another
Turn R1 into Cross by intentionally suppressing R1 and emitting Cross while the source is held.
event on_input() {
bool sourceDown = input.held("R1");
output.set_button("R1", false);
if (sourceDown) {
output.set_button("cross", true);
}
}
Tap for one input packet
Press Cross for the current controller packet when R1 transitions from up to down.
event on_input() {
if (input.pressed("R1")) {
output.tap("cross");
}
}
Hold a trigger without blocking passthrough
Set R2 to full pressure while L1 is held and leave physical R2 untouched otherwise.
event on_input() {
if (input.held("L1")) {
output.set_trigger("R2", 100);
}
}
Curve trigger pressure
Square the live L2 pressure, clamp it to 0 through 100, and apply the curve to R2.
event on_input() {
var sourcePressure = input.get(ps5.l2);
if (sourcePressure > 0) {
var curved = math.pow(sourcePressure / 100, 2) * 100;
output.set_trigger("R2", math.clamp(curved, 0, 100));
}
}
Add gyro movement to the right stick
Add available gyro motion to the physical right-stick values and clamp both axes.
event on_input() {
if (gyro.available) {
var x = math.clamp(input.right_x + gyro.y * 12, -100, 100);
var y = math.clamp(input.right_y - gyro.x * 12, -100, 100);
output.set_stick("right", x, y);
}
}
Spoof a touch while held
Press touch point 0 at the center of the DualSense touch coordinate space while L1 is held.
event on_input() {
if (input.held("L1")) {
output.set_touch(0, 960, 540, true, 0);
}
}
Emergency neutral packet
Neutralize buttons, sticks, and triggers for the current packet when Options or Menu is pressed.
event on_input() {
if (input.pressed("options")) {
output.clear();
system.log("Controller output neutralized for this packet");
}
}
Environment
3 recipesInspect the controller family
Log the family known at Run, then inspect the live family from on_input when requested.
event on_connect() {
system.log(string.format("Controller at Run: {}", system.controller));
}
event on_input() {
if (input.pressed("options")) {
if (input.controller == controller.ps5) {
system.log("PlayStation 5 controller");
} else if (input.controller == controller.xb) {
system.log("Xbox controller");
} else {
system.log(string.format("Controller family: {}", input.controller));
}
}
}
Inspect the output host
Log the selected output destination when Options or Menu is pressed.
event on_input() {
if (input.pressed("options")) {
system.log(string.format("Output host: {}", output.host));
}
}
Inspect the Cronus protocol
Report the bridge protocol on demand when the active output host is Cronus Zen.
event on_input() {
if (output.host == host.cronuszen && input.pressed("options")) {
system.log(string.format("Cronus protocol: {}", cronuszen.protocol));
}
}
Controller Feedback
3 recipesRumble while held
Set both rumble motors while L1 is held and send one explicit clear when L1 is released.
event on_input() {
if (input.held("L1")) {
rumble.set("left", 45);
rumble.set("right", 20);
} else if (input.released("L1")) {
rumble.clear();
}
}
Set and reset the lightbar
Latch a blue lightbar with D-pad Up and return lightbar ownership to Remote Play with D-pad Down.
event on_input() {
if (input.pressed("up")) {
lightbar.set(rgb.blue);
}
if (input.pressed("down")) {
lightbar.reset();
}
}
Set the player indicator
Show player number 1 with D-pad Up and clear the indicator with D-pad Down.
event on_input() {
if (input.pressed("up")) {
ps5.player.set(1);
}
if (input.pressed("down")) {
ps5.player.clear();
}
}
Video UI
2 recipesDraw text and a status message
Render colored text at normalized coordinates and show the live frame and runtime-object age.
font hud = font.create(1.4, rgb.lime);
event on_frame() {
ui.print(hud, 0.05, 0.08, string.format("Frame {}", frame));
ui.status(string.format("Runtime age {} ms", time_ms));
}
event on_stop() {
ui.clear();
}
Draw normalized video shapes
Draw a rectangle, line, and circle using frame-relative coordinates from 0 through 1.
event on_frame() {
ui.rect(0.30, 0.30, 0.40, 0.40, rgb.cyan, 2, false);
ui.line(0.50, 0.30, 0.50, 0.70, rgb.yellow, 2);
ui.circle(0.50, 0.50, 0.10, rgb.lime, 2, false);
}
Models
1 recipeConfigure an ONNX detector
Create a generic model source, configure a center-region detector, and read its own best target.
model objectSource = models.set("models/object_detector.onnx");
model objectDetector;
const scanRadius = 0.18;
const scanRoi = [0.5 - scanRadius, 0.5 - scanRadius, scanRadius * 2, scanRadius * 2];
event on_start() {
objectDetector = vision.model(objectSource, "object", scanRoi, 0.50, true, vision.auto);
}
event on_frame() {
ui.circle(0.5, 0.5, scanRadius, rgb.cyan, 2, false);
target detectedObject = objectDetector.target;
if (detectedObject == null) {
ui.status("No object detected");
return;
}
ui.rect(detectedObject.box.x, detectedObject.box.y, detectedObject.box.width, detectedObject.box.height, rgb.lime, 3, false);
ui.status(string.format("Object {}", detectedObject.confidence));
}
Computer Vision
11 recipesFind an HSV color target
Threshold red pixels, draw the best region, and report how many regions were found.
color redPixels = vision.color([0, 90, 80], [15, 255, 255], null, 20, true);
event on_frame() {
target redTarget = redPixels.target;
if (redTarget != null) {
ui.rect(redTarget.box.x, redTarget.box.y, redTarget.box.width, redTarget.box.height, rgb.red, 2, false);
}
ui.status(string.format("Red regions: {}", redPixels.count));
}
Count contour blobs
Extract color-based contours inside a center region and display the blob count.
const centerRoi = [0.25, 0.25, 0.50, 0.50];
contour blobs = vision.contour([0, 90, 80], [15, 255, 255], centerRoi, 40, true);
event on_frame() {
ui.status(string.format("Blobs: {}", blobs.count));
}
Detect motion and edges
Run motion and edge-density tools over the same region and display both result counts.
const centerRoi = [0.25, 0.25, 0.50, 0.50];
motion movement = vision.motion(centerRoi, 0.20, 25, true);
edges edgeAreas = vision.edges(centerRoi, 0.35, 25, true);
event on_frame() {
ui.status(string.format("Motion {} edges {}", movement.count, edgeAreas.count));
}
Match template and feature images
Configure generic template, feature, and match assets and display their result counts.
template logoTemplate = vision.template("images/logo.png", 0.82, null, true);
feature iconFeatures = vision.feature("images/icon.png", 0.78, null, true);
match menuMatch = vision.match("images/menu.png", 0.78, null, true);
event on_frame() {
ui.status(string.format("Template {} feature {} match {}", logoTemplate.count, iconFeatures.count, menuMatch.count));
}
Read QR and barcode text
Check QR and barcode tools and show decoded text from the first available result.
qr qrCodes = vision.qr(null, true);
barcode barcodes = vision.barcode(null, true);
event on_frame() {
target qrResult = qrCodes.target;
target barcodeResult = barcodes.target;
if (qrResult != null) {
ui.status(string.format("QR: {}", qrResult.text));
} else if (barcodeResult != null) {
ui.status(string.format("Barcode: {}", barcodeResult.text));
} else {
ui.status("No code detected");
}
}
Read text with OCR
Run English OCR inside a generic score region and display the recognized text.
const scoreRoi = [0.75, 0.02, 0.23, 0.12];
ocr scoreText = vision.ocr(scoreRoi, "eng", true);
event on_frame() {
target textResult = scoreText.target;
if (textResult != null) {
ui.status(string.format("Text: {}", textResult.text));
} else {
ui.status("No text detected");
}
}
Detect circles and lines
Configure circle and line detectors over the full frame and display their counts.
circle circles = vision.circle(null, 0.45, 12, true);
line lines = vision.line(null, 0.45, 20, true);
event on_frame() {
ui.status(string.format("Circles {} lines {}", circles.count, lines.count));
}
Configure face and person models
Use generic ONNX paths with the face and person model helpers and report result counts.
model faceSource = models.set("models/face_detector.onnx");
model personSource = models.set("models/person_detector.onnx");
face faces = vision.face(faceSource, null, 0.60, true, vision.cpu);
person people = vision.person(personSource, null, 0.60, true, vision.auto);
event on_frame() {
ui.status(string.format("Faces {} people {}", faces.count, people.count));
}
Retune a vision tool while running
Periodically toggle and update a circle detector through its writable configuration members.
circle markers = vision.circle(null, 0.45, 12, true);
event on_frame() {
if (frame % 120 == 0) {
markers.enabled = !markers.enabled;
markers.threshold = 0.55;
markers.min_area = 20;
markers.roi = [0.20, 0.20, 0.60, 0.60];
}
ui.status(string.format("Detector enabled: {}", markers.enabled));
}
Track detections across frames
Feed a generic color detector into the tracker and display the current track ID. The tracker currently follows every pipeline detection; its source only labels output, and its ROI argument is not applied.
color candidates = vision.color([0, 90, 80], [15, 255, 255], null, 20, true);
track activeTracks = vision.track("candidate", null, true);
event on_frame() {
target activeTrack = activeTracks.target;
if (activeTrack == null) {
ui.status(string.format("Detections {} tracks 0", candidates.count));
return;
}
ui.rect(activeTrack.box.x, activeTrack.box.y, activeTrack.box.width, activeTrack.box.height, rgb.cyan, 2, false);
ui.status(string.format("Detections {} tracks {} best {}", candidates.count, activeTracks.count, activeTrack.text));
}
Draw every detection
Iterate a tool's results array and draw a box for each detection instead of reading only target.
color regions = vision.color([0, 90, 80], [15, 255, 255]);
event on_frame() {
for (var i = 0; i < regions.count; i++) {
ui.rect(regions.results[i].box.x, regions.results[i].box.y, regions.results[i].box.width, regions.results[i].box.height, rgb.red, 2, false);
}
}
Language
5 recipesUse arrays and loops
Infer a constant array size, iterate with count(), and keep the result in persistent global state.
const weights[] = { 1, 2, 3, 4 };
var total = 0;
event on_start() {
total = 0;
for (var i = 0; i < weights.count(); i++) {
total += weights[i];
}
}
event on_frame() {
ui.status(string.format("Total: {}", total));
}
Compare struct copy and reference parameters
Show that a typed struct parameter is copied unless an ampersand shares the caller's fields.
struct Counter {
var value = 0;
};
Counter score;
function editCopy(Counter data) {
data.value = 10;
}
function editReference(Counter &data) {
data.value = 20;
}
event on_start() {
score.value = 1;
editCopy(score);
system.log(string.format("After copy: {}", score.value));
editReference(score);
system.log(string.format("After reference: {}", score.value));
}
event on_frame() {
ui.status(string.format("Score: {}", score.value));
}
Create writable reference aliases
Bind aliases to a variable and an array element, then read and write through both aliases.
var total = 10;
var values[3] = { 1, 2, 3 };
event on_frame() {
var totalRef = &total;
var elementRef = &values[1];
totalRef += 1;
elementRef = total;
ui.status(string.format("Total {} element {}", totalRef, elementRef));
}
Use enums, functions, and type
Define numeric enum members, return text from a function, and inspect a runtime value with type().
enum Mode {
idle,
active = 5
};
function modeName(value) {
if (value == Mode::active) {
return "active";
}
return "idle";
}
var currentMode = Mode::active;
event on_frame() {
string numericType = type(currentMode);
ui.status(string.format("{} ({})", modeName(currentMode), numericType));
}
Convert frame dimensions to pixels
Read captured frame dimensions in pixels while drawing UI with normalized coordinates.
event on_frame() {
var centerX = system.width * 0.5;
var centerY = system.height * 0.5;
ui.status(string.format("Center pixel: {}, {}", centerX, centerY));
ui.circle(0.5, 0.5, 0.03, rgb.cyan, 2, false);
}
Network
5 recipesLog outgoing packets to one port
Capture only UDP packets sent to port 9000, log the first twenty with their addresses and sizes, and report the total when the script stops. Every packet passes unchanged.
var logged = 0;
event on_start() {
if (!network.capture("udp and udp.DstPort == 9000")) {
system.log("Network capture could not start");
}
}
event on_network_send() {
if (logged < 20) {
logged += 1;
system.log(string.format("{}:{} -> {}:{}, {} payload bytes",
packet.src_ip, packet.src_port, packet.dst_ip, packet.dst_port, packet.payload_length));
}
}
event on_stop() {
system.log(string.format("Sent {} matching packets", network.sent));
}
Block traffic to and from one address
Divert IPv4 traffic for one address and drop it in both directions. The filter keeps every other packet away from the script.
const string BLOCKED = "192.0.2.10";
event on_start() {
if (!network.capture("ip.DstAddr == 192.0.2.10 or ip.SrcAddr == 192.0.2.10")) {
system.log("Network capture could not start");
}
}
event on_network_send() {
if (packet.dst_ip == BLOCKED) {
packet.drop();
}
}
event on_network_recv() {
if (packet.src_ip == BLOCKED) {
packet.drop();
}
}
Edit one byte in matching packets
Change the second payload byte of outgoing UDP packets whose first byte is 0x01. Atlas recalculates the checksums of an edited packet before sending it.
event on_start() {
if (!network.capture("udp and udp.DstPort == 9000")) {
system.log("Network capture could not start");
}
}
event on_network_send() {
// In this example protocol, payload byte 0 is a message type.
if (packet.payload_length >= 2 && packet.byte(0) == 0x01) {
packet.set_byte(1, 0x7F);
}
}
Decode a payload into a struct
Copy the start of a UDP payload into struct fields with a layout string, set one flag bit, and copy the struct back into the packet.
struct Message {
var kind;
var sequence;
var flags;
};
const string LAYOUT = "u8 kind; u16be sequence; u16be flags";
Message message;
event on_start() {
if (!network.capture("udp and udp.DstPort == 9000")) {
system.log("Network capture could not start");
}
}
event on_network_send() {
if (packet.copy_to(message, LAYOUT) && message.kind == 0x02) {
message.flags = math.bit_or(message.flags, 0x0004);
if (!packet.copy_from(message, LAYOUT)) {
system.log("The payload no longer fits the layout");
}
}
}
Show capture status on the video
Show why capture is not running, or its packet counters once it is, in the persistent status line.
event on_start() {
if (!network.capture("udp and udp.DstPort == 9000")) {
system.log("Network capture could not start");
}
}
event on_frame() {
if (network.error != "") {
ui.status("Capture unavailable: " + network.error);
} else if (network.active) {
ui.status(string.format("Capture: {} sent, {} received, {} dropped",
network.sent, network.received, network.dropped));
} else {
ui.status("Capture is not running");
}
}
event on_network_send() {
packet.pass();
}
Compact starter examples
These smaller examples are also used by the editor when it needs a concise starting point. They preserve physical controller passthrough whenever their condition is inactive.
Hold Cross while a red marker is visible
bool redMarkerVisible;
color redMarker = vision.color([0, 90, 80], [15, 255, 255]);
event on_frame() {
redMarkerVisible = redMarker.target != null;
}
event on_input() {
if (redMarkerVisible) {
output.set_button("cross", true);
}
}
Set the left trigger while R1 is held
event on_input() {
if (input.held("R1")) {
output.set_trigger("L2", 100);
}
}
Set rumble while L1 is held
event on_input() {
if (input.held("L1")) {
rumble.set("left", 60);
rumble.set("right", 30);
} else if (input.released("L1")) {
rumble.clear();
}
}