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

# Globals

> Events, subscriptions, timers, console and the script object.

What every script has without an import.

<Note>
  Logic goes in `tick` (128 Hz), drawing in `render` (once per overlay frame).
  `render.*` draw calls anywhere else throw.
</Note>

## Overview

<CardGroup cols={2}>
  <Card title="Events" icon="bolt" href="#events">
    `on`, `emit`, `addEventListener`, event types.
  </Card>

  <Card title="Subscriptions" icon="link-slash" href="#subscriptions">
    `Subscription`, `signal`, `once`.
  </Card>

  <Card title="Timers" icon="clock" href="#timers">
    `setTimeout`, `setInterval`.
  </Card>

  <Card title="console and script" icon="terminal" href="#console">
    Log levels, `console.inspect`, manifest data.
  </Card>
</CardGroup>

***

## Events

The global object is an `EventTarget`. `on` and `emit` are shortcuts on it; `addEventListener`
and `on<type>` properties see the same events.

### on

<br />

```ts theme={null}
on(type: string, listener: (event: Event) => void, options?: SubscribeOptions): Subscription
```

Adds a listener and returns a [Subscription](#subscriptions).

```ts theme={null}
const sub = on("keydown", (e) => {
    if (e.code === "F6" && !e.repeat) console.log("F6");
});

on("tick", () => console.log("first tick"), { once: true });
sub(); // stop listening
```

### emit

<br />

```ts theme={null}
emit(type: string, detail?: unknown): boolean
```

Dispatches a `CustomEvent` on the global object, synchronously. `detail` is `null` when omitted.
Returns `false` if a listener called `preventDefault()`.

```ts theme={null}
declare global { interface ScriptCustomEvents { scoreChanged: number } }

on("scoreChanged", (e) => console.log(e.detail)); // e.detail: number
emit("scoreChanged", 10);
```

Augmenting `ScriptCustomEvents` types `detail` for both calls.

### addEventListener and handler properties

<br />

The standard API works on the same events. A handler property holds one function; assigning
replaces it, `null` removes it.

```ts theme={null}
addEventListener("tick", () => { /* logic */ });
ontick = () => { /* logic */ };
onkeydown = (e) => console.log(e.key);
```

### Event types

<br />

| Type | Event | When |
| :- | :- | :- |
| `tick` | `Event` | 128 times a second. Put logic here. |
| `render` | `Event` | Once per overlay frame. Draw here. |
| `unload` | `Event` | Before the script is unloaded or reloaded. Not fired when it stops on an error. |
| `keydown` / `keyup` | `KeyboardEvent` | Key pressed (again while held, `repeat: true`) / released |
| `mousemove` | `MouseEvent` | The mouse moved |
| `mousedown` / `mouseup` | `MouseEvent` | Button pressed / released |
| `wheel` | `WheelEvent` | One wheel notch |
| `error` | `ErrorEvent` | A listener, timer or other callback threw, or `reportError(e)` was called |
| `unhandledrejection` | `PromiseRejectionEvent` | A rejected promise had no handler after the current task |

Everything a script owns is released after `unload`, so there's nothing to clean up by hand.

### Input event fields

<br />

Fields follow the DOM.

| Field | On | Value |
| :- | :- | :- |
| `key`, `code`, `location` | key events | UI Events values (`"a"`, `"KeyA"`) |
| `keyCode` | key events | Win32 virtual-key code |
| `repeat` | `keydown` | `true` for auto-repeat |
| `button` | `mousedown`, `mouseup` | `0` left, `1` middle, `2` right, `3` back, `4` forward |
| `buttons` | mouse events | Held buttons: `1` left, `2` right, `4` middle, `8` back, `16` forward |
| `clientX`, `clientY`, `x`, `y`, `screenX`, `screenY` | mouse events | Screen pixels |
| `movementX`, `movementY` | `mousemove` | Raw delta since the last `mousemove` |
| `deltaY` | `wheel` | `-100` per notch up, `+100` per notch down |
| `ctrlKey`, `shiftKey`, `altKey`, `metaKey` | all input | Modifier state |

`preventDefault()` on input events does nothing; the game still gets the input.

### Errors

<br />

Exceptions from listeners, timers, ui handlers and ESP evaluators land in `error`. Unhandled
rejections land in `unhandledrejection`. Both go to the log unless you call `preventDefault()`.

```ts theme={null}
on("error", (e) => {
    console.warn("handled:", e.message);
    e.preventDefault();
});

on("unhandledrejection", (e) => {
    console.warn("rejected:", e.reason);
    e.preventDefault();
});
```

***

## Subscriptions

`on` and every other subscribe-style call (ui `.on(...)`, `ui.effect`, key binds) return a
`Subscription`: a function that unsubscribes when called. `.dispose()` and `[Symbol.dispose]` do
the same. Dropping the function does not unsubscribe.

| Option | Type | Effect |
| :- | :- | :- |
| `signal` | `AbortSignal` | Unsubscribes when the signal aborts. |
| `once` | `boolean` | Unsubscribes after the first delivery. |

```ts theme={null}
const controller = new AbortController();
on("keydown", (e) => console.log(e.code), { signal: controller.signal });
on("mousedown", (e) => console.log(e.button), { signal: controller.signal });
controller.abort(); // both gone

{
    using sub = on("render", () => { /* draw */ });
} // unsubscribed here
```

***

## Timers

`setTimeout`, `setInterval`, `clearTimeout` and `clearInterval` work as in a browser. Callbacks
run at most once per tick, so `setInterval(fn, 0)` repeats every tick.

```ts theme={null}
const id = setInterval((label: string) => console.log(label), 1000, "every second");
setTimeout(() => clearInterval(id), 5000);
```

A listener or timer that runs for more than 2 seconds is stopped and the script is marked as errored.

***

## console

Output goes to the cheat log, tagged with the script name. Objects print inspected (`{ a: 1 }`)
and printf placeholders (`%s`, `%d`, `%o`, …) work.

| Method | Log level |
| :- | :- |
| `log`, `info`, `debug`, `dirxml`, `dir`, `table`, `count`, `time*`, `group*` | Info |
| `warn` | Warning |
| `error`, `trace`, a failed `assert` | Error |

### console.inspect

<br />

```ts theme={null}
console.inspect(value: any, depth?: number): string
```

Returns what `console.log(value)` would print, without printing it. `depth` defaults to `2`.

```ts theme={null}
const text = console.inspect({ a: [1, 2] }); // "{ a: [ 1, 2 ] }"
```

***

## script

Frozen metadata from [manifest.json](/api/manifest), read-only.

| Field | Type | When the manifest has none |
| :- | :- | :- |
| `name` | `string` | `"unnamed"` |
| `version` | `string` | `"1.0.0"` |
| `author` | `string` | `"unknown"` |
| `description` | `string` | `""` |
| `permissions` | `readonly ("filesystem" \| "ffi")[]` | Both, see [permissions](/api/manifest#permissions) |

```ts theme={null}
console.log(`${script.name} v${script.version} by ${script.author}`);
if (!script.permissions.includes("ffi")) console.warn("ffi disabled");
```

***

## Web runtime

Standard web APIs (`fetch`, `URL`, `TextEncoder`, `crypto`, …) are globals too, see [Web APIs](/api/web)
and [Networking](/api/networking). Storage is [localStorage](/api/localStorage).

***

## Example

A hotkey that toggles a crosshair, with the state kept across reloads.

```ts theme={null}
import render from "@native/render";

let enabled = localStorage.getItem<boolean>("crosshair") ?? true;

on("keydown", (e) => {
    if (e.code !== "F7" || e.repeat) return;
    enabled = !enabled;
    localStorage.setItem("crosshair", enabled);
    console.log(`crosshair ${enabled ? "on" : "off"}`);
});

on("render", () => {
    if (!enabled) return;
    const { x, y } = render.screenSize;
    render.line([x / 2 - 6, y / 2], [x / 2 + 6, y / 2], "#00ff80");
    render.line([x / 2, y / 2 - 6], [x / 2, y / 2 + 6], "#00ff80");
});

on("unload", () => console.log(`${script.name} unloaded`));
```
