# Controller

> Talos next · Since 0.6

Present on `ctx.controller` when the entity has a character-controller component (kinematic capsule).

**Order within a tick.** The controller steps once per tick, after physics and before behaviours, so everything a behaviour writes here — `move`, `jump`, `setVelocity`, `addVelocity`, `gravityScale` — takes effect on the controller's step at the top of the next tick, and everything it reads describes the step that just ran. At that step, in order: moving ground carries the character, an upward velocity write lifts it off the ground, `move` accelerates the horizontal velocity towards its target, a `jump` press sets the vertical speed, gravity is applied, and the capsule is swept. **Velocity writes and `move`.** The controller's velocity is one number that everything shares. `setVelocity`/`addVelocity` change it; `move(dir, speed)` then accelerates the horizontal part of it towards `dir * speed` at the ground acceleration (50 m/s²) or the air acceleration (12 m/s²). So a sideways shove on the ground is braked within a few ticks, and a launch's horizontal speed bleeds towards whatever the stick asks for while it is in the air. A game that wants a launch to keep its speed keeps asking `move` for that speed until it lands. **Who else writes it.** With the component's `input: "auto"` (the default) the ENGINE reads the device and drives this controller itself: at the top of every tick, before the controller steps and before any behaviour runs, it writes the player's `move` stick (keys, pad or a `POST /input` hold) as the movement, jumps on the jump button at the component's `jump` height, latches crouch and turns the heading with `look`. A `move` from here is written after that step, for the next one, and the next tick's stick overwrites it: **while the stick is pushed, the stick wins**, and the engine jumps whatever this behaviour does. `inputBasis` picks the stick's frame: `"camera"` (default: away from the drawn view, screen-right is right; world −Z with no camera), `"world"` (−Z) or `"controller"` (this heading). With `input: "script"` the engine writes nothing here; `ctx.input` still reads the device, this behaviour is the only writer, and `ctx.camera.viewYaw` gives the frame the engine would have used. See docs/API.md, "Who drives the controller".

Reached in a behaviour as `ctx.controller`.

## Signature

```ts
export interface Controller {
  /** Request movement this tick in world direction `dir` at `speed` m/s; slopes, steps and collisions resolve in Rust. */
  move(dir: Vec3, speed: number): void
  /**
   * Jump on the next tick, if grounded and standing. `height` is metres above the take-off point, reached
   * under this character's own gravity (so `gravityScale` changes how long the jump takes, not how high it
   * goes); omitted, the component's `jump` height. A press in the air does nothing and is not saved for the
   * landing: a second jump in the air is the game's, built with {@link Controller.setVelocity}.
   * Holding the press (calling this every tick) jumps once. Throws `RangeError` for a negative or non-finite
   * height.
   */
  jump(height?: number): void
  crouch(on: boolean): void
  /**
   * Replace the velocity, m/s. Visible at once through {@link Controller.velocity}; integrated from at the
   * next step. A write whose `y` is above zero lifts a grounded character off the ground without a `jump`
   * event — a jump pad, a knockback, a vehicle's collision kick — which is the only way to launch a
   * character upward. Throws `RangeError` for a NaN or infinite component.
   */
  setVelocity(v: Vec3): void
  /**
   * Add to the velocity, m/s. A velocity change rather than a force: the character has no mass. Calls in
   * one tick add up. Same ground rule and errors as {@link Controller.setVelocity}.
   */
  addVelocity(v: Vec3): void
  readonly grounded: boolean
  readonly velocity: Vec3
  /** Normal of the surface under the feet, walkable or not (a slope too steep to stand on still reports); null in the air. */
  readonly groundNormal: Vec3 | null
  /** The entity stood on while grounded — a platform, a vehicle deck, a floor whose tag picks a footstep sound; null otherwise. */
  readonly groundEntity: EntityHandle | null
  /**
   * How fast moving ground carried the character on the last step, m/s; zero on still ground, in the air and
   * with `moveWithGround` off. A rider that jumps or walks off moving ground keeps this speed on its own —
   * the jump is relative to the platform — and {@link Controller.velocity} includes it while airborne.
   */
  readonly groundVelocity: Vec3
  /**
   * A moving body closing on the character on the last step — a door coming down on its head, a lift
   * carrying it into a ceiling, a kinematic wall sliding into it — or null. The character is never pushed
   * through the floor or a wall to make room: it stays where it is, can still walk out from under the
   * mover, and the engine raises `"character-squeezed"` ({@link BusCharacterSqueezed}) once when it starts.
   */
  readonly squeezedBy: EntityHandle | null
  /**
   * The last surface too steep to stand on that the character walked into on the last step, as a unit
   * normal pointing out of it; null when the move touched no wall. For a wall slide, a vault, a bump sound.
   */
  readonly wallNormal: Vec3 | null
  /** The last move ran into something it could neither climb nor slide past. */
  readonly blocked: boolean
  /**
   * Multiplier on this character's gravity — the component's `gravity`, else the length of the scene's
   * `meta.gravity`; 1 by default. 0.5 for a low-gravity zone or a glide, 2 for a
   * heavier fall, 0 to hang. Throws `RangeError` below 0 or when not finite.
   */
  gravityScale: number
  /**
   * Whether moving ground carries this character — a lift, a ferry, a turntable, a tipping seesaw — by its
   * movement and turn since the last tick, before the character's own move. On by default; off for a
   * character that should slide off a moving deck.
   */
  moveWithGround: boolean
}
```

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

## Members

| Member | Signature | Description |
|---|---|---|
| `move` | `move(dir: Vec3, speed: number): void` | Request movement this tick in world direction `dir` at `speed` m/s; slopes, steps and collisions resolve in Rust. |
| `jump` | `jump(height?: number): void` | Jump on the next tick, if grounded and standing. `height` is metres above the take-off point, reached under this character's own gravity (so `gravityScale` changes how long the jump takes, not how high it goes); omitted, the component's `jump` height. A press in the air does nothing and is not saved for the landing: a second jump in the air is the game's, built with {@link Controller.setVelocity}. Holding the press (calling this every tick) jumps once. Throws `RangeError` for a negative or non-finite height. |
| `crouch` | `crouch(on: boolean): void` |  |
| `setVelocity` | `setVelocity(v: Vec3): void` | Replace the velocity, m/s. Visible at once through {@link Controller.velocity}; integrated from at the next step. A write whose `y` is above zero lifts a grounded character off the ground without a `jump` event — a jump pad, a knockback, a vehicle's collision kick — which is the only way to launch a character upward. Throws `RangeError` for a NaN or infinite component. |
| `addVelocity` | `addVelocity(v: Vec3): void` | Add to the velocity, m/s. A velocity change rather than a force: the character has no mass. Calls in one tick add up. Same ground rule and errors as {@link Controller.setVelocity}. |
| `grounded` | `readonly grounded: boolean` |  |
| `velocity` | `readonly velocity: Vec3` |  |
| `groundNormal` | `readonly groundNormal: Vec3 \| null` | Normal of the surface under the feet, walkable or not (a slope too steep to stand on still reports); null in the air. |
| `groundEntity` | `readonly groundEntity: EntityHandle \| null` | The entity stood on while grounded — a platform, a vehicle deck, a floor whose tag picks a footstep sound; null otherwise. |
| `groundVelocity` | `readonly groundVelocity: Vec3` | How fast moving ground carried the character on the last step, m/s; zero on still ground, in the air and with `moveWithGround` off. A rider that jumps or walks off moving ground keeps this speed on its own — the jump is relative to the platform — and {@link Controller.velocity} includes it while airborne. |
| `squeezedBy` | `readonly squeezedBy: EntityHandle \| null` | A moving body closing on the character on the last step — a door coming down on its head, a lift carrying it into a ceiling, a kinematic wall sliding into it — or null. The character is never pushed through the floor or a wall to make room: it stays where it is, can still walk out from under the mover, and the engine raises `"character-squeezed"` ({@link BusCharacterSqueezed}) once when it starts. |
| `wallNormal` | `readonly wallNormal: Vec3 \| null` | The last surface too steep to stand on that the character walked into on the last step, as a unit normal pointing out of it; null when the move touched no wall. For a wall slide, a vault, a bump sound. |
| `blocked` | `readonly blocked: boolean` | The last move ran into something it could neither climb nor slide past. |
| `gravityScale` | `gravityScale: number` | Multiplier on this character's gravity — the component's `gravity`, else the length of the scene's `meta.gravity`; 1 by default. 0.5 for a low-gravity zone or a glide, 2 for a heavier fall, 0 to hang. Throws `RangeError` below 0 or when not finite. |
| `moveWithGround` | `moveWithGround: boolean` | Whether moving ground carries this character — a lift, a ferry, a turntable, a tipping seesaw — by its movement and turn since the last tick, before the character's own move. On by default; off for a character that should slide off a moving deck. |

## Example

The tested fixture `fixtures/recipes/drone-search/scenes/behaviors/carrier.ts`.

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

/** Moves a kinematic body round `props.path` (world [x, z] points) at `props.speed` m/s, facing where it goes: the
 * stand-in for a boat or a truck the drone launches from, and for the contact it looks for. */
interface Props {
  path: [number, number][]
  speed: number
}

interface State {
  next: number
}

export default {
  init(ctx) {
    ctx.state.next ??= 0
  },
  update(ctx, dt) {
    const path = ctx.props.path
    const goal = path[ctx.state.next % path.length]
    if (goal === undefined) return
    const p = ctx.entity.position
    const to: [number, number, number] = [goal[0], p[1], goal[1]]
    ctx.entity.lookAt(to)
    if (ctx.entity.moveToward(to, ctx.props.speed * dt)) ctx.state.next = (ctx.state.next + 1) % path.length
  },
} satisfies Behavior<State, Props>
```
