> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/scripting/random-math-and-more.md).

# Random, Math, & More

This page is a grab-bag of “you’ll use these constantly” utilities: RNG, formatting, time, and a few core subsystems that show up in most games.

{% hint style="info" %}
If you’re looking for arrays/slices, see [Arrays and Collections](/scripting/arrays-and-collections.md).
{% endhint %}

## Random numbers (RNG)

Random uses an explicit `u64` seed. Pass the seed by `ref` so it updates.

```go
rng: u64 = rng_root_seed();
// or deterministic per-entity:
// rng: u64 = rng_seed(entity.id);

// Range values are inclusive
roll := rng_range_int(ref rng, 1, 10);
chance := rng_range_float(ref rng, 0, 1);

// Random points and shuffling
offset := rng_disk(ref rng, 0.5, 2.0);
spawn_points: [..]v2;
spawn_points.append({0, 0});
spawn_points.append({4, 0});
rng_shuffle(ref rng, spawn_points);

// Mix a value into a seed without drawing a random number.
rng_mix(ref rng, entity.id);
independent_seed := mix_u64(rng, 123); // returns the mixed seed; rng is unchanged
```

{% hint style="warning" %}
Gameplay randomness must run in the shared predicted gameplay path. Start from `rng_root_seed()` or another stable seed; do not seed gameplay from local-only data.
{% endhint %}

`mix_u64(a: u64, b: u64) -> u64` and `rng_mix(rng: ref u64, value: u64)` are intrinsics with the same deterministic mixing algorithm. `rng_mix` stores the mixed result back into `rng`; the order of repeated mixes matters.

## Math functions

These functions are declared in `core:basic`, which `core:ao` also imports. In the tables, `F` means separate `f32` and `f64` overloads; `float` is `f32`, and `int` is `s64`. Arguments in an overload use the same type. `T` denotes a generic numeric type, and `V` denotes separate `v2`, `v3`, and `v4` overloads.

### Scalar math reference

| Function signature                                                             | Behavior                                                                                                                         |
| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `ceil(x: F) -> F`                                                              | Smallest integral value greater than or equal to `x`: `ceil(1.25)` is `2.0`, `ceil(-1.25)` is `-1.0`.                            |
| `floor(x: F) -> F`                                                             | Largest integral value less than or equal to `x`: `floor(1.25)` is `1.0`, `floor(-1.25)` is `-2.0`.                              |
| `round(x: F) -> F`                                                             | Nearest integral value, with halfway cases away from zero: `round(2.5)` is `3.0`, `round(-2.5)` is `-3.0`.                       |
| `sin(v: F) -> F`                                                               | Sine of an angle in radians.                                                                                                     |
| `cos(v: F) -> F`                                                               | Cosine of an angle in radians.                                                                                                   |
| `atan2(y: F, x: F) -> F`                                                       | Angle in radians from an `(x, y)` direction. Pass **y first**.                                                                   |
| `to_degrees(radians: F) -> F`                                                  | Convert radians to degrees, preserving precision.                                                                                |
| `to_radians(degrees: F) -> F`                                                  | Convert degrees to radians, preserving precision.                                                                                |
| `pow(b: F, e: F) -> F`                                                         | Raise base `b` to exponent `e`.                                                                                                  |
| `sqrt(x: F) -> F`                                                              | Square root.                                                                                                                     |
| `Math.exp(v: F) -> F`                                                          | Natural exponential, e raised to `v`.                                                                                            |
| `Math.log(v: F) -> F`                                                          | Natural logarithm. The global `log` function is a logging alias.                                                                 |
| `abs(a: T) -> T`                                                               | Absolute value.                                                                                                                  |
| `sign(v: T) -> T`                                                              | `-1` for negative values; `1` for zero and positive values.                                                                      |
| `min(a: F, b: F) -> F`; `min(a: int, b: int) -> int`                           | Smaller of two values.                                                                                                           |
| `max(a: F, b: F) -> F`; `max(a: int, b: int) -> int`                           | Larger of two values.                                                                                                            |
| `clamp(a: F, min: F, max: F) -> F`; `clamp(a: int, min: int, max: int) -> int` | Constrain a value to inclusive bounds, checking the lower bound first.                                                           |
| `lerp(a: F, b: F, t: F) -> F`                                                  | Linear interpolation; `t` is not clamped.                                                                                        |
| `linear_step(start: float, end: float, time: float) -> float`                  | Normalize `time` to the interval and clamp to `[0, 1]`; returns `1` if the endpoints are equal.                                  |
| `next_power_of_two(n: s64) -> s64`                                             | Smallest power of two at least `n`; returns `0` for non-positive input. Inputs above `2^62` overflow to the minimum `s64` value. |

`PI` is also available as a constant.

Scalar helpers and `dot` execute as VM math instructions. Vector `lerp` lowers directly to vector arithmetic instructions. They require no foreign calls. The composite helpers `in_range` and `normalize_vector_to_radius` remain CSL code built from these primitives.

Floating-point `min` and `max` return the second argument when the values compare equal or either is NaN. `clamp` preserves a NaN input. `abs` preserves signed zero and NaN; integer `abs` preserves the signed minimum value on overflow. These are the same behaviors as the original CSL helpers.

### Rounding

`ceil`, `floor`, and `round` return a floating-point value of the input precision. Exact integers, infinities, and signed zero are unchanged; NaN produces NaN. Rounding up means toward positive infinity, so `ceil(-1.25)` is `-1.0`. `round` uses halfway-away-from-zero rounding, rather than ties-to-even.

```go
slots := ceil(2.25).(int);       // 3
cell := floor(-2.25).(int);      // -3
nearest := round(-2.5).(int);    // -3
truncated := (-2.25).(int);      // -2: casts truncate toward zero
precise: f64 = 1.0000000000000002;
rounded := ceil(precise);        // 2.0, still f64
```

Only cast the result to `int` when it is finite and fits in `int`. There is no `trunc` function. For decimal display formatting, use `format_float(value, decimals=2)`; it does not change the numeric value.

### Vector math reference

| Function signature                | Behavior                                                 |
| --------------------------------- | -------------------------------------------------------- |
| `length(v: V) -> float`           | Vector length.                                           |
| `length_squared(v: V) -> float`   | Squared length, avoiding a square root.                  |
| `normalize(v: V) -> V`            | Normalize a vector.                                      |
| `dot(a: V, b: V) -> float`        | Dot product.                                             |
| `lerp(a: V, b: V, t: float) -> V` | Component-wise linear interpolation; `t` is not clamped. |

### Examples

```go
angle_sin := sin(x);
angle_cos := cos(x);
angle := atan2(dir.y, dir.x);
degrees := to_degrees(angle);
radians := to_radians(degrees);

result := pow(2.0, 3.0);   // 8.0
root := sqrt(16.0);        // 4.0

value := lerp(0.0, 100.0, 0.5);  // 50.0
clamped := clamp(value, 0.0, 10.0);
stepped := linear_step(0.0, 1.0, value);

absolute := abs(-5);
minimum := min(5, 10);
maximum := max(5, 10);
direction_sign := sign(-12);

len := length(v);
len_sq := length_squared(v);
normalized := normalize(v);
facing := dot(normalized, {1, 0});
```

## String formatting

Use `%` placeholders with an argument array:

```go
format_string("Value: %", {42});
format_string("health: 100%");
```

If you need an argument adjacent to a percent sign (or next to another argument), use `%0` as an alias for `%`:

```go
hp := 67;
format_string("health: %0%%", {hp}); // "health: 67%"
```

For decimal rounding, use `format_float`:

```go
value := 3.14159;
format_string("pi: %", {format_float(value, decimals=2)}); // "pi: 3.14"
```

Common string helpers:

```go
clean := string_trim("  hello  ");
parts := string_split("a,b,c", ",");
name_lower := to_lower(player.get_username());
name_upper := to_upper("ready");
short := string_substring("abcdef", 1, 3); // "bcd"
```

## Logging

Logging follows the same formatting rules:

```go
log_info("Name: %, age: %", {player.get_username(), 12});
```

## Time

```go
current_time := get_time();       // float seconds since game start
frame := get_frame_number();      // current frame number
now_ns := get_nanoseconds_since_epoch();
utc := get_utc_datetime();
```

## SFX

```go
sound := get_asset(SFX_Asset, "sfx/click.wav");

desc := SFX.default_sfx_desc();
desc.entity_to_follow = entity.id;
desc.delay = 0.25;

sound_id := SFX.play(sound, desc);
SFX.stop(sound_id);
```

{% hint style="warning" %}
Call gameplay sounds from the shared gameplay path. The engine reconciles predicted playback. For a sound that only one player should hear, set `desc.specific_to_player = player`; do not wrap `SFX.play` in `is_local()`.
{% endhint %}

## Economy quick start (currencies)

Economy currencies are **per-player** and **automatically persisted**.

```go
ao_before_scene_load :: proc() {
    coin_icon := get_asset(Texture_Asset, "ui/coin.png");
    Economy.register_currency("Coins", coin_icon);
}

Economy.deposit_currency(player, "Coins", 10);
coins := Economy.get_balance(player, "Coins");

COST :: 50;
if Economy.can_withdraw_currency(player, "Coins", COST) {
    Economy.withdraw_currency(player, "Coins", COST);
}
```

Deposit and withdrawal amounts must be non-negative.

See [Economy](/data-and-persistence/economy.md) for the full guide.
