On this page
Copy, understand, adapt

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

Untouched controller values pass through. Most recipes write an output only while their condition is active. Avoid adding an idle else that writes false, 0, or a centered stick unless you intentionally want to suppress the physical control.
Two deliberate exceptions: a true remap must suppress its source button, and the emergency-neutral recipe intentionally neutralizes buttons, sticks, and triggers for the current input packet. Normal passthrough resumes on the next untouched packet.

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.

Network recipes need setup. They capture packets with WinDivert, which you install yourself, and ask for administrator approval when they start. Replace the example port 9000 and the documentation address 192.0.2.10 with your own, and keep each filter narrow. See Network events.

Recipe categories

Controller Output

8 recipes. Override only the controls each script intentionally owns.

Environment

3 recipes. Inspect controller families, output hosts, and protocol details.

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

Pressed, held, and released

React once when L1 is pressed or released, and hold Cross only while L1 remains down.

Pressed, held, and released
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.

Use a trigger pressure threshold
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.

Hold a button for one second
var heldMilliseconds = 0;

event on_input() {
    if (input.held("L1")) {
        heldMilliseconds += input.time;
        if (heldMilliseconds >= 1000) {
            output.set_button("cross", true);
        }
    } else {
        heldMilliseconds = 0;
    }
}

Hold a button without blocking passthrough

Hold Cross while L1 is down and leave physical Cross untouched when L1 is up.

Hold a button without blocking passthrough
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.

Remap one button to another
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.

Tap for one input packet
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.

Hold a trigger without blocking passthrough
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.

Curve trigger pressure
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.

Add gyro movement to the right stick
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.

Spoof a touch while 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.

Emergency neutral packet
event on_input() {
    if (input.pressed("options")) {
        output.clear();
        system.log("Controller output neutralized for this packet");
    }
}

Environment

3 recipes

Inspect the controller family

Log the family known at Run, then inspect the live family from on_input when requested.

Inspect the controller family
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.

Inspect the output host
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.

Inspect the Cronus protocol
event on_input() {
    if (output.host == host.cronuszen && input.pressed("options")) {
        system.log(string.format("Cronus protocol: {}", cronuszen.protocol));
    }
}

Rumble while held

Set both rumble motors while L1 is held and send one explicit clear when L1 is released.

Rumble while held
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.

Set and reset the lightbar
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.

Set the player indicator
event on_input() {
    if (input.pressed("up")) {
        ps5.player.set(1);
    }
    if (input.pressed("down")) {
        ps5.player.clear();
    }
}

Video UI

2 recipes

Draw text and a status message

Render colored text at normalized coordinates and show the live frame and runtime-object age.

Draw text and a status message
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.

Draw normalized video shapes
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 recipe

Configure an ONNX detector

Create a generic model source, configure a center-region detector, and read its own best target.

Configure an ONNX detector
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 recipes

Find an HSV color target

Threshold red pixels, draw the best region, and report how many regions were found.

Find an HSV color target
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.

Count contour blobs
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.

Detect motion and edges
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.

Match template and feature images
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.

Read QR and barcode text
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.

Read text with OCR
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.

Detect circles and lines
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.

Configure face and person models
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.

Retune a vision tool while running
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.

Track detections across frames
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.

Draw every detection
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 recipes

Use arrays and loops

Infer a constant array size, iterate with count(), and keep the result in persistent global state.

Use arrays and loops
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.

Compare struct copy and reference parameters
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.

Create writable reference 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().

Use enums, functions, and 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.

Convert frame dimensions to pixels
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 recipes

Log 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.

Log outgoing packets to one port
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.

Block traffic to and from one address
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.

Edit one byte in matching packets
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.

Decode a payload into a struct
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.

Show capture status on the video
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.

Vision + Controller

Hold Cross while a red marker is visible

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);
    }
}
Controller Output

Set the left trigger while R1 is held

Set the left trigger while R1 is held
event on_input() {
    if (input.held("R1")) {
        output.set_trigger("L2", 100);
    }
}
Controller Feedback

Set rumble while L1 is held

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();
    }
}
Recipes are loaded from the Atlas language schema shared with the application. Every sample is intended to remain generic, portable, and valid under the editor diagnostics in this source snapshot.