Browse docsFunctions & references
On this page
Functions and references

Reuse calculations and choose when state is shared.

Atlas functions can accept untyped or typed values, return results, deep-copy a typed struct, share a struct's members with &, or work with a true writable alias created by var alias = ⌖. These mechanisms are related, but they are not interchangeable.

Function declaration and call syntax

A function is declared at the top level with the function keyword. Its parameter list is positional and its body is a block. A call supplies exactly one argument for each parameter.

Basic functions
function writeMessage(string message) {
    system.log(message);
}

function clampValue(var value, var minimum, var maximum) {
    if (value < minimum) {
        return minimum;
    }
    if (value > maximum) {
        return maximum;
    }
    return value;
}

function stateLabel(bool active) {
    if (active) {
        return "active";
    }
    return "inactive";
}

event on_frame() {
    var bounded = clampValue(125, 0, 100);
    writeMessage("value: " + bounded);
    writeMessage(stateLabel(bounded > 0));
}

One header form

Every function is declared as function name(parameters) { ... }. This is the only way to declare a function, whether it returns a value or not.

Functions have no return type. What a function returns is whatever its return statements return: stateLabel above returns text because both of its return statements return text, and writeMessage returns no value because it has no return value;. A header that starts with a type or another name instead of function, such as string stateLabel(bool active) { ... }, is the compile error atlas.typed_function_header. In the editor, its quick fix replaces the leading type with function.

Parameters

A parameter has one of these forms:

Parameter grammar fragments
value                   // untyped parameter; accepts any value kind
var amount              // explicit numeric parameter; let amount and auto amount are synonyms
bool enabled            // explicit bool parameter
string label            // explicit text parameter; char label is a synonym
font style              // explicit font parameter
model detector          // explicit model parameter
target candidate        // explicit target parameter
color marker            // any documented vision-tool type is accepted
TrackingSettings data   // typed struct, copied deeply
TrackingSettings &data  // typed struct, shared members

The vision-tool parameter types are color, contour, motion, edges, circle, line, template, feature, match, qr, barcode, ocr, track, face, and person. In a function header, types appear only on parameters, never before the function name.

  • Parameter and argument order is significant.
  • User functions do not have optional, default, named, or variadic parameters.
  • Too few or too many arguments are validation errors.
  • Parameters may be used like local variables inside the function.
  • A parameter name may not appear twice in one parameter list.
  • There is no formal array-parameter declaration syntax.
  • handle is a runtime value kind, not a declaration type; receive a handle through an untyped parameter.

Return values

Use return expression; to produce a result or return; to leave a no-result function early. Events may use only the bare form. A function declares no return type, so its return statements alone decide what it returns; the rules below keep that result consistent.

Return values and early exit
function absoluteDistance(var distance) {
    if (distance < 0) {
        return -distance;
    }
    return distance;
}

function reportIfLarge(var value) {
    if (value < 10) {
        return;
    }
    system.log("large value: " + value);
}

event on_frame() {
    var distance = absoluteDistance(-12.5);
    reportIfLarge(distance);
}

Rules for value-returning functions

RuleMeaning
One valued return makes the function value-returningIf any path contains return value;, Atlas expects a value result.
Every structural path must returnAn if needs returning branches or a later guaranteed return. Loops do not count as guaranteed because they may not execute.
Do not mix bare and valued returnsA value-returning function cannot use return; on another path.
Return kinds must agreeString, struct, array, target, and other distinct kinds cannot be mixed across paths.
Number and bool are compatibleThe analyzer permits these two kinds together because bool participates as 0/1 at a numeric boundary.
Null does not select a concrete kindA null return can coexist with a concrete optional-like result, but callers must check for null before member access.
Complete return paths
function category(var score) {
    if (score >= 90) {
        return "high";
    } else if (score >= 50) {
        return "medium";
    } else {
        return "low";
    }
}

event on_frame() {
    system.log(category(72));
}

A function with only bare returns, or with no explicit return, produces no usable value. Using such a call in an initializer, argument, condition, assignment, array index, or return expression is an atlas.void_result error.

Using a result where a type is required

Because a function has no return type, Atlas reads a call's type from the function's return statements. A return's type is known when it returns:

  • a literal (a number, a string, true or false), an enum member (a number), or null;
  • a name declared as var, string, bool, a struct, a vision type, target, or model, including a typed parameter; a var alias = &target; reference has its target's type;
  • a struct field, such as profile.name or holder.inner, or an element of an array or an array field, such as points[i] or holder.list[1]: the element's type;
  • arithmetic (-, *, /, %, unary minus, ++, --) or + of numbers: a number; + with a string: a string; a comparison, !, &&, or ||: a bool;
  • a conditional (ready ? a : b): the types of both results;
  • a vision.<type>(...) call; a built-in call with a known result, such as math.abs(...) (a number), string.format(...) (a string), or vision.target(...) (a target); .count() (a number); type(value) (a string); or another function's result.

A name is read in the block where the return is: a declaration inside an inner block or a for header does not reach a return after it. A struct, vision-type, or target declaration, an assignment to a struct variable, a struct field, or an element of a struct array field, and a typed struct parameter accept the call when every return whose type is known has the required type, also through parentheses or a conditional. A vision-type or target declaration also accepts a null result, as it accepts null itself; a struct does not. A call whose returns are all of unknown type, such as a function that returns its untyped parameter, is accepted too. Otherwise the declaration or assignment is atlas.type_mismatch and the argument is atlas.argument_type.

The same rule works the other way: Atlas reports a struct or vision value stored where a var, string, or bool is declared, so var kept = makeProfile(); is atlas.type_mismatch when every return path of makeProfile names that type. A function that can also return a number or null, or that has one path Atlas cannot type, is not reported — while a declaration of that struct's own type still accepts it (see what is not reported). Keep a returned struct in a variable of its own type, such as Profile kept = makeProfile();, and it returns as that struct again.

Return a struct or a vision tool and use it as a typed value
struct Profile {
    string name = "Default";
    var gains[2] = { 1.0, 1.0 };
};

function makeProfile(string name) {
    Profile result;
    result.name = name;
    return result;
}

function makeMarker() {
    return vision.color([0, 90, 80], [15, 255, 255]);
}

Profile active;
color marker = makeMarker();

event on_start() {
    Profile spare = makeProfile("Spare");
    active = makeProfile("Replacement"); // replaces the whole value with a copy
    system.log(active.name + " " + spare.name);
}

Storing a struct always stores a deep copy of it, including its nested structs and arrays: a declaration, an assignment to a struct variable, to a struct field (holder.inner = makeProfile("A");), to an array element (list[0] = spare;), or through a reference (slot = spare; with var slot = &list[1];), and each struct in an array literal. Later writes to the stored copy never reach the value the function returned, or the variable it was copied from. A copy follows at most 128 levels of nested structs and arrays; a deeper value, which only a script storing a struct inside an untyped field of itself over and over can build, stops the event with the runtime error Struct value is nested too deeply or refers to itself.

Calls, forward references, and recursion

Atlas collects function names before compiling bodies, so a function may call a helper declared later in the file. Functions may also call themselves, subject to the runtime call-depth limit.

Forward call
function prepare() {
    writeReadyMessage();
}

function writeReadyMessage() {
    system.log("ready");
}

event on_start() {
    prepare();
}
Bounded recursion
function factorial(var value) {
    if (value <= 1) {
        return 1;
    }
    return value * factorial(value - 1);
}

event on_frame() {
    system.log(factorial(5));
}

The VM permits at most 40 active user-function calls. Exceeding that depth reports a runtime error in the session log instead of overflowing the native stack. Prefer loops for ordinary repeated work, and always give recursion a reachable base case.

Function names use a namespace separate from variables, so a function and variable may share a spelling. Calls resolve the function; ordinary reads resolve the variable. Overloading is not supported, and duplicate function declarations are not a portable way to replace a function.

Keep controller I/O directly in on_input

Controller namespace validation is lexical. A function is analyzed as a function, not as the event that might call it, so input, output, rumble, lightbar, gyro, accel, and platform controller roots cannot appear inside a helper function. input.time is the only input.* property exempt from this restriction.

Pass already-read scalar values into pure helpers, then apply the decision directly in on_input:

Valid controller helper pattern
function exceedsThreshold(var value, var threshold) {
    return value >= threshold;
}

event on_input() {
    var pressure = input.get(ps5.r2);
    bool shouldPress = exceedsThreshold(pressure, 50);
    if (shouldPress) {
        output.set_button("cross", true);
    }
}
Timing helpers are allowed. A function may read input.time because it is a timing value rather than a live controller-state read. Every other controller read or write should remain in on_input. When the condition above is false, the script leaves Cross untouched so the physical button continues to pass through.

The packet object follows the same lexical rule: it may appear only directly inside on_network_recv or on_network_send. Read the packet values you need in the event and pass them to a helper as ordinary arguments. network.* capture control may be used in any function.

What pass by value means

An ordinary function call copies each argument's VM value into a new local parameter slot. What that means for later mutation depends on the value category.

Argument kindOrdinary parameter behaviorRecommended contract
Number, bool, nullInline scalar value is copied. Reassigning the parameter cannot change the caller.Use an explicit var or bool parameter when practical.
StringThe VM copies a shared string payload, but the language exposes no string mutation operation.Treat it as an immutable value.
Explicit typed structThe VM recursively deep-copies the struct, nested structs, and nested arrays.Use StructName value for a real independent working copy.
Untyped parameter receiving a structNo struct-type metadata requests a deep copy, so the implementation can share the object.Never rely on this; explicitly type every struct parameter.
ArrayArrays are shared heap payloads in the VM, and there is no formal typed array parameter.Pass a specific element, or put the array in a typed struct and choose copy/reference semantics explicitly.
Font, model, target, vision tool, handleThe handle value is copied while the runtime-owned/shared payload remains shared.Treat these as handles, not deep-copied resources.

Wrap an array in a struct for clear copy semantics

Array copied through a typed struct
struct SampleBuffer {
    var values[4] = { 1, 2, 3, 4 };
};

function sumBuffer(SampleBuffer buffer) {
    var total = 0;
    for (var index = 0; index < buffer.values.count(); index++) {
        total += buffer.values[index];
    }
    buffer.values[0] = 99; // changes only the deep copy
    return total;
}

SampleBuffer samples;

event on_frame() {
    system.log(sumBuffer(samples));
    system.log(samples.values[0]); // still 1
}

Struct parameters: deep copy or shared members

Struct parameters are the one place where Atlas exposes an explicit copy-versus-share choice. A typed struct declared with & is passed by reference in the limited sense that its object and members are shared; assigning the whole parameter still rebinds only the callee's local slot. The marker appears in the declaration, never at the call site.

Copy and shared struct parameters
struct Profile {
    string name = "Default";
    var sensitivity = 1.0;
    var gains[3] = { 1.0, 1.0, 1.0 };
};

function editCopy(Profile profile) {
    profile.name = "Copy";
    profile.sensitivity = 2.0;
    profile.gains[1] = 1.5;
}

function editShared(Profile &profile) {
    profile.name = "Shared";
    profile.sensitivity = 2.0;
    profile.gains[1] = 1.5;
}

Profile active;

event on_start() {
    editCopy(active);   // active is unchanged
    editShared(active); // active fields are changed; no & here
}

Profile profile

The callee receives a recursive deep copy. Field and array-element writes remain local to the function.

Profile &profile

The callee receives the same struct object. Field and nested-array-element writes are visible to the caller.

  • A reference parameter must explicitly name a declared struct type.
  • var &value, string &text, and other scalar reference parameters are rejected.
  • The call is editShared(active), not editShared(&active).
  • The argument must be a compatible struct variable, struct field, or array element rather than a temporary expression.

Whole-parameter replacement is not caller-slot replacement

A shared struct parameter suppresses the deep copy; it does not become a reference to the caller's variable slot. Member writes reach the caller because both values point at the same object. Assigning the parameter itself replaces only the callee's local binding.

Shared member identity, local parameter binding
struct Profile {
    string name = "Default";
};

function replaceLocally(Profile &profile) {
    Profile replacement;
    replacement.name = "Replacement";
    profile = replacement; // only this function's local binding is replaced
}

Profile active;

event on_start() {
    replaceLocally(active);
    system.log(active.name); // still "Default"
}

If a function must update the caller through a shared struct parameter, mutate fields: profile.name = "Replacement";. If whole-variable replacement is required, return a struct and assign the result in the caller (see using a result where a type is required), or use a true alias where appropriate.

True writable aliases with var alias = &target;

An alias is a fixed path back to writable storage. Reading the alias automatically reads its target; assigning the alias writes the target. It is different from a struct & parameter.

Variable, field, element, and chained aliases
struct Settings {
    var threshold = 0.5;
    var weights[3] = { 1.0, 1.0, 1.0 };
};

var total = 10;
Settings settings;

event on_frame() {
    var totalRef = &total;
    var fieldRef = &settings.threshold;
    var elementRef = &settings.weights[1];
    var chainedRef = &fieldRef;

    totalRef += 5;
    fieldRef = 0.75;
    elementRef++;
    chainedRef += 0.05;

    system.log(total);              // 15
    system.log(settings.threshold); // 0.8
    system.log(settings.weights[1]); // 2
}

Supported targets

TargetExampleBinding behavior
Variablevar ref = &count;Reads and writes the variable slot.
Struct fieldvar ref = &settings.threshold;Retains a path through the base variable slot to that member.
Array elementvar ref = &values[index];Captures the numeric index at the moment the alias is bound.
Existing aliasvar second = &first;Keeps the original binding rather than referring to the first alias's private slot.

Operations through an alias

Plain assignment, compound assignment, and increment/decrement write through. Ordinary expression use reads through. An alias inherits enough target information for string and struct field access to work naturally.

String alias
string status = "before";

event on_frame() {
    var statusRef = &status;
    statusRef = "after";

    if (statusRef == status) {
        system.log(status);
    }
}

A member alias follows replacement of its base variable

The alias stores a reference path, not a raw pointer into one temporary struct payload. If the whole base struct variable is replaced later, the member alias follows the slot and writes the same member of the replacement.

Path remains attached to the base slot
struct Settings {
    var threshold = 0.5;
};

Settings current;

event on_frame() {
    var thresholdRef = &current.threshold;

    Settings replacement;
    replacement.threshold = 0.9;
    current = replacement;

    thresholdRef = 0.7;
    system.log(current.threshold); // 0.7
}

Alias restrictions

  • The declaration must use var, even when the target currently contains a string or struct.
  • An alias declaration cannot be const and cannot declare array dimensions.
  • The target must be a writable script variable, field, or array element.
  • Literals, temporary expressions, call results, enum members, constants, functions, namespace roots, and readonly API properties cannot be targets.
  • A reference cannot bind to itself.
  • & is allowed only directly in an alias initializer. It cannot be used in a later assignment, argument, return, or general expression.
  • Assigning an alias writes through; it does not rebind the alias. Atlas has no alias-reseat operation.
  • A local alias is useful only while its containing function or event call is active.
Invalid reference fragments
var badLiteral = &5;
const LIMIT = 5;
var badConst = &LIMIT;
string badDeclaration = &status;
status = &anotherStatus;

Choose the right sharing mechanism

MechanismDeclarationWhat is shared?Whole-value assignment
Ordinary scalar parametervar valueNothing mutable; the scalar binding is copied.Changes only the callee local.
Typed struct value parameterSettings valueNothing; struct, nested structs, and arrays are deep-copied.Changes only the deep copy.
Typed struct shared parameterSettings &valueThe struct object and its members.Replaces only the callee binding; mutate fields to affect caller.
True aliasvar ref = &target;The target variable slot or captured member/index path.Writes through and can replace the target's whole value.

Function design checklist

  • Declare every function with function; put types on parameters, never before the name.
  • Give every user call exactly the declared argument count.
  • Explicitly type struct parameters so copy behavior is intentional.
  • Use StructName &value only for member mutation, and call it without &.
  • Return a value on every path once any path returns a value.
  • Keep all return kinds compatible; a function returns whatever its return statements return.
  • Keep controller reads and writes directly inside on_input, and packet directly inside the network events; pass scalar data to pure helpers.
  • Keep recursion bounded and event work small.
  • Use a true alias only when a fixed writable binding is clearer than direct assignment.

Continue the guide

Language

Review declarations, types, arrays, structs, operators, control flow, scope, and limits.

Events

Learn exactly when each callback, including the network packet events, runs and what context it may access.

Recipes

Use complete generic examples built around current event and API behavior.

Diagnostics

Resolve parameter, return, type, reference, and runtime failures.

Atlas functions and references (source snapshot). Copy and sharing behavior is written from native compiler lowering, VM call-frame behavior, and reference storage.