# Behavior

> Talos next · Since 0.6; changed in 0.7, next

What a behavior module exports as `default`.

Every hook is optional. A throw disables this instance and emits `script-error`; the others keep running. One exception: a stale handle — a `DespawnedEntityError`, from a handle kept past its entity — thrown from `update`, `pausedUpdate`, `onCollision`, `onTrigger`, `onEvent` or a `ctx.events.on` listener ends that call only; the instance runs again next tick and the error is reported (at most once a second) with `disabled: false`. In `init` it disables like any throw. Each hook has a wall-clock budget (2 ms by default); ten consecutive overruns are reported as a `script-error` (phase `budget`, `disabled: false`, at most once every 600 ticks after the first) and the instance keeps running. A runaway loop is still stopped and disables the instance.

## Signature

```ts
export interface Behavior<S = Record<string, any>, P = Record<string, any>> {
  /**
   * In a rollback session, run only on confirmed ticks: while the engine predicts ticks ahead of the
   * inputs it has, this module's behaviours run nothing (no `update`, no event hook, no `ctx.events`
   * listener; `init` and `dispose` still run). For what must happen once and never be taken back: a
   * score, a round ending, an AI director's decision, a purchase. It then acts a few ticks late (the
   * prediction depth, about 8 ticks at 60 Hz), on every machine at the same tick. Its `ctx.state` is
   * saved and restored with the rest. Default false: everything else runs on predicted ticks too,
   * which is what keeps movement and hits responsive. Without rollback it changes nothing.
   */
  readonly confirmedOnly?: boolean
  /** Once after attach, and again after each hot reload (with `state` preserved). */
  init?(ctx: Ctx<S, P>): void
  /** Every fixed tick; `dt` is 1/60 s. Not while the world is held (`ctx.session.pause()`, a `pausesWorld` screen). */
  update?(ctx: Ctx<S, P>, dt: number): void
  /**
   * Every fixed tick, once for the whole module, in place of each instance's `update` (which is then never
   * called): a system that steps every entity running this behaviour in one call, the cheap way to run
   * hundreds of them, above all under rollback, where a tick runs many times. `batch` lists the enabled
   * instances in attach order, and the call comes at the first one's place in that order. `ctx` is that
   * first instance's: reach the others through `batch.entities[k]` and `batch.states[k]` (each its own
   * `ctx.state`, the same object, so a write persists and rollback restores it). The budget, the hard cap
   * and the overrun count treat the call as the first instance's, and a throw or a stop from the guards
   * disables or suspends every instance in the batch. Not while the world is held.
   */
  updateAll?(ctx: Ctx<S, P>, dt: number, batch: Batch<S>): void
  /**
   * Every fixed tick while the world is held, in place of `update`: where a pause menu reads its
   * own key, since `update` is not running. `ctx.time.now` stands still; `dt` is still 1/60 s.
   */
  pausedUpdate?(ctx: Ctx<S, P>, dt: number): void
  /**
   * Before detach and before a hot reload swaps the module. `reason` says which: `"detach"` (the entity,
   * or the scene, is going) or `"reload"` (`init` follows, with `state` kept), so an effect that belongs
   * to the entity going away is not run by an edit.
   */
  dispose?(ctx: Ctx<S, P>, reason?: "detach" | "reload"): void
  /**
   * Once when this entity's body starts touching another: once per pair of
   * bodies, however many points the contact has, with the point that carried the most impulse. Not again
   * while they stay in touch; again after they have been apart for 3 ticks or more.
   */
  onCollision?(ctx: Ctx<S, P>, collision: Collision): void
  /**
   * Every tick this entity's body is touching another, the tick it started
   * included and after `onCollision`: once per pair per tick, with that tick's strongest point. For what
   * lasts as long as the contact does, such as a scrape along a wall. When it stops being called, the
   * contact has ended.
   */
  onCollisionStay?(ctx: Ctx<S, P>, collision: Collision): void
  onTrigger?(ctx: Ctx<S, P>, trigger: TriggerEvent): void
  /**
   * `other` is the other participant from this side — the volume
   * hears who entered, and the entrant hears the volume. (In {@link onTrigger}'s payload `other` is always
   * the entrant.) Each runs after `onTrigger` for the same contact.
   */
  onTriggerEnter?(ctx: Ctx<S, P>, other: EntityHandle): void
  /** The other participant left: see {@link onTriggerEnter}. */
  onTriggerExit?(ctx: Ctx<S, P>, other: EntityHandle): void
  /** Any `emit`, from any behavior or from the app. */
  onEvent?(ctx: Ctx<S, P>, event: GameEvent): void
}
```

Import: `import type { Behavior } from "@argus/api"`

## Members

| Member | Signature | Description |
|---|---|---|
| `confirmedOnly` | `readonly confirmedOnly?: boolean` | In a rollback session, run only on confirmed ticks: while the engine predicts ticks ahead of the inputs it has, this module's behaviours run nothing (no `update`, no event hook, no `ctx.events` listener; `init` and `dispose` still run). For what must happen once and never be taken back: a score, a round ending, an AI director's decision, a purchase. It then acts a few ticks late (the prediction depth, about 8 ticks at 60 Hz), on every machine at the same tick. Its `ctx.state` is saved and restored with the rest. Default false: everything else runs on predicted ticks too, which is what keeps movement and hits responsive. Without rollback it changes nothing. |
| `init` | `init?(ctx: Ctx<S, P>): void` | Once after attach, and again after each hot reload (with `state` preserved). |
| `update` | `update?(ctx: Ctx<S, P>, dt: number): void` | Every fixed tick; `dt` is 1/60 s. Not while the world is held (`ctx.session.pause()`, a `pausesWorld` screen). |
| `updateAll` | `updateAll?(ctx: Ctx<S, P>, dt: number, batch: Batch<S>): void` | Every fixed tick, once for the whole module, in place of each instance's `update` (which is then never called): a system that steps every entity running this behaviour in one call, the cheap way to run hundreds of them, above all under rollback, where a tick runs many times. `batch` lists the enabled instances in attach order, and the call comes at the first one's place in that order. `ctx` is that first instance's: reach the others through `batch.entities[k]` and `batch.states[k]` (each its own `ctx.state`, the same object, so a write persists and rollback restores it). The budget, the hard cap and the overrun count treat the call as the first instance's, and a throw or a stop from the guards disables or suspends every instance in the batch. Not while the world is held. |
| `pausedUpdate` | `pausedUpdate?(ctx: Ctx<S, P>, dt: number): void` | Every fixed tick while the world is held, in place of `update`: where a pause menu reads its own key, since `update` is not running. `ctx.time.now` stands still; `dt` is still 1/60 s. |
| `dispose` | `dispose?(ctx: Ctx<S, P>, reason?: "detach" \| "reload"): void` | Before detach and before a hot reload swaps the module. `reason` says which: `"detach"` (the entity, or the scene, is going) or `"reload"` (`init` follows, with `state` kept), so an effect that belongs to the entity going away is not run by an edit. |
| `onCollision` | `onCollision?(ctx: Ctx<S, P>, collision: Collision): void` | Once when this entity's body starts touching another: once per pair of bodies, however many points the contact has, with the point that carried the most impulse. Not again while they stay in touch; again after they have been apart for 3 ticks or more. |
| `onCollisionStay` | `onCollisionStay?(ctx: Ctx<S, P>, collision: Collision): void` | Every tick this entity's body is touching another, the tick it started included and after `onCollision`: once per pair per tick, with that tick's strongest point. For what lasts as long as the contact does, such as a scrape along a wall. When it stops being called, the contact has ended. |
| `onTrigger` | `onTrigger?(ctx: Ctx<S, P>, trigger: TriggerEvent): void` |  |
| `onTriggerEnter` | `onTriggerEnter?(ctx: Ctx<S, P>, other: EntityHandle): void` | `other` is the other participant from this side — the volume hears who entered, and the entrant hears the volume. (In {@link onTrigger}'s payload `other` is always the entrant.) Each runs after `onTrigger` for the same contact. |
| `onTriggerExit` | `onTriggerExit?(ctx: Ctx<S, P>, other: EntityHandle): void` | The other participant left: see {@link onTriggerEnter}. |
| `onEvent` | `onEvent?(ctx: Ctx<S, P>, event: GameEvent): void` | Any `emit`, from any behavior or from the app. |

## Example

The tested fixture `fixtures/app/agent.ts`.

```ts
import type { Behavior } from "@argus/api"

// Sends a nav agent to `props.target` once, then reports arrival through ctx.state so the
// test can read it without polling the crowd directly.
export default {
  init(ctx) {
    ctx.state.arrived = false
    ctx.nav?.goTo(ctx.props.target as [number, number, number])
  },
  update(ctx) {
    if (ctx.nav?.arrived) ctx.state.arrived = true
  },
} satisfies Behavior
```
