> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rebind.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# rb.math

> Draw random numbers and reshape a macro's mouse path and timing.

`rb.math` draws random numbers and reshapes macros. A script reaches for it to
vary a macro between plays, to smooth or resample its mouse path, or to give the
path the timing of a recorded mouse with [`rb.math.humanize`](#rb-math-humanize).

This script plays a short mouse pattern on F6, a little larger or smaller and a
little faster or slower each time, as a path resampled every 5 ms:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.9.0 name=example version=1.0.0
local pattern = {
  { x = 0, y = 12, delay = 90 },
  { x = -3, y = 9, delay = 90 },
  { x = 4, y = 7, delay = 90 },
}
rb.bind("F6", function()
  local size = rb.math.random(0.9, 1.1)
  local take = rb.math.scale(pattern, size, size)
  take = rb.math.time_comp(take, math.floor(rb.math.gaussian(270, 15)))
  rb.macro.play(rb.math.interpolate(take, 5))
end)
```

| Signature | Returns |
| - | - |
| `rb.math.random(min: number, max: number) -> number` | uniform in `[min, max]`; raises when `min > max` |
| `rb.math.gaussian(mean: number, std_dev: number) -> number` | normal distribution; raises when `std_dev < 0` |
| `rb.math.scale(macro: { MacroAction }, x_factor: number, y_factor: number) -> { MacroAction }` | moves multiplied |
| `rb.math.spline(macro: { MacroAction }, tension: number) -> { MacroAction }` | Catmull-Rom smoothing of moves; `tension` 0 to 1 |
| `rb.math.resample(macro: { MacroAction }, interval_ms: number) -> { MacroAction }` | every delay set to `interval_ms` |
| `rb.math.time_comp(macro: { MacroAction }, target_ms: number) -> { MacroAction }` | delays scaled so the total is `target_ms` |
| `rb.math.interpolate(macro: { MacroAction }, interval_ms: number, mode: string?) -> { MacroAction }` | the mouse path resampled every `interval_ms` |
| `rb.math.humanize(macro: { MacroAction }, options: HumanizeOptions?) -> { MacroAction }` | the mouse path as 1 ms reports; `min_sdk` 3.16.0 |
| `rb.math.glide(macro: { MacroAction }, options: GlideOptions?) -> { MacroAction }` | smooth speed through each move's end point; `min_sdk` 3.17.0 |

Every transform returns a new table. `interpolate` keeps only movement: clicks,
keys, scrolls and sleeps are dropped, and the total duration and end position are
kept. `mode = "linear"` draws straight segments; anything else, the default,
follows a curve.

`spline` leaves a list with fewer than 4 moves, or a `tension` of 0 or less,
unchanged. An `interval_ms` or `target_ms` of 0 returns the list unchanged, and
so does `time_comp` on a list whose delays add up to 0. Every action needs a
`delay`, or the call raises. `gaussian` also raises when `mean` is not finite.

## rb.math.humanize

**Needs:** `min_sdk=3.16.0`.

Runs the [ABCurves](https://github.com/optima-manent/ABCurves) Renderer over the
moves as one stream. A smooth path, such as `rb.math.interpolate(macro, 1)`,
comes back as whole-count reports with the timing of a recorded mouse.

* `options.profile`: a 1 kHz recording that sets the Renderer's starting state.
  Its first 256 ms of movement with no pause of 50 ms or more are used. Without
  one, a bundled sample from the ABCurves dataset is used.
* `options.seed`: a whole number from 0 to 2^53. The same profile and seed give
  the same result on the same platform; without a seed, every call differs.

What it keeps and changes:

* The total displacement is the original's, rounded.
* Every other action keeps its time. The cursor when it fires can be a few
  counts from where the original put it, more during a fast flick, because the
  Renderer releases counts on its own schedule.
* At most one report goes out per millisecond, so a take recorded faster than
  1 kHz comes back at 1 kHz.
* The take may end up to 17 ms after its last move while the last counts are
  released.

Apply it last and play the result at speed 1: a later `scale`, or another speed,
changes the reports it produced.

Errors, each starting `Math.Humanize: `:

* `seed (<seed>) must be a whole number from 0 to 2^53`
* `the profile needs 256 ms of mouse movement with no pause of 50 ms or more`
* `the profile must be a 1 kHz recording: some milliseconds hold more than one move`
* `the profile must be a recording: its moves are whole counts`
* `the profile has no mouse movement`
* `more than 10 minutes of mouse movement to render (a pause counts up to 50 ms)`

This script resamples a short pattern every 1 ms, humanizes it once at load with
seed 7, and plays the result on F6:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.16.0 name=example version=1.0.0
local pattern = {
  { x = 0, y = 12, delay = 90 },
  { x = -3, y = 9, delay = 90 },
  { x = 4, y = 7, delay = 90 },
}
local take = rb.math.humanize(rb.math.interpolate(pattern, 1), { seed = 7 })
rb.bind("F6", function()
  rb.macro.play(take)
end)
```

## rb.math.glide

**Needs:** `min_sdk=3.17.0`.

Shapes the speed of the mouse movement so it changes smoothly instead of staying
constant inside each move. Every move still ends at its own point at its own
time, and the path never passes a point and comes back.

* Consecutive moves are shaped as one stroke that starts and ends at rest. A
  sleep or gap, a move of 0 with a delay, a move with no delay, or a mouse
  button going down or up ends the stroke. A single move can hold still for part
  of its time, as a hand does.
* `options.seed`: a whole number from 0 to 2^53 that picks the profiles. The same
  seed gives the same result; without one, every call differs.

What it keeps:

* The cursor is on each move's end point, rounded to whole counts, when that
  move ends.
* The total displacement is the original's, rounded. Every other action keeps
  its time, and at the same time it stays in the order it was written.
* A move with a delay of 0 stays one jump.

A move over time is a one-move macro: `rb.macro.play(rb.math.glide({ { x = 120,
y = -40, delay = 300 } }))` moves 120 counts right and 40 up over 300 ms, and
`rb.hid.move` stays a single report.

For the Renderer's timing as well, apply `rb.math.humanize` after it. The
result can then pass a point by a count or two before settling; the total stays
exact.

Errors, each starting `Math.Glide: `: `seed (<seed>) must be a whole number
from 0 to 2^53` and `more than 10 minutes of mouse movement to shape`.

This script glides a short pattern, applies `rb.math.humanize` after it, and
plays the result on F6:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.17.0 name=example version=1.0.0
local pattern = {
  { x = 0, y = 12, delay = 90 },
  { x = -3, y = 9, delay = 90 },
  { x = 4, y = 7, delay = 90 },
}
local take = rb.math.humanize(rb.math.glide(pattern, { seed = 7 }), { seed = 7 })
rb.bind("F6", function()
  rb.macro.play(take)
end)
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.