# InputState

> Talos 0.6 · Since 0.6

Present on `ctx.input` only for the player entity.

Action names come from the scene's action map, `meta.input` in the scene document (or the built-in map when the scene has none). A behavior reads actions and never keys, which is what makes rebinding a JSON edit and hot reload rebind a running scene. An action the live context does not bind reads neutral — never an error, so a behavior needs no guard.

Reached in a behaviour as `ctx.input`.

## Signature

```ts
export interface InputState {
  /**
   * `"move"` and `"look"` are always defined; each component is in -1..1. The mouse is folded in
   * as a rate, so for turning a view read {@link InputState.axisParts} instead (the built-in
   * camera rigs do).
   */
  axis(name: string): Vec2
  /**
   * The same axis split into its stick `rate` and its mouse `delta` (an angle in degrees). A
   * camera turns by `rate × speed × dt + delta`: the mouse then turns exactly as far as the hand
   * moved, with no top speed.
   */
  axisParts(name: string): AxisParts
  action(name: string): ActionState
  pressed(name: string): boolean
  held(name: string): boolean
  released(name: string): boolean
  /**
   * The live context of the scene's action map, e.g. `"gameplay"` or `"menu"`.
   *
   * Scene-wide, not per entity: "the menu is open" is a fact about the scene, so every
   * behavior sees the same answer.
   */
  readonly context: string
  /**
   * Switch the whole scene to another context. `false`, and nothing changes, when the map has
   * no context of that name. The switch lands on the next tick.
   */
  setContext(name: string): boolean
  /** Every action the live context binds, in name order. What a rebinding screen lists. */
  actions(): string[]
  /**
   * What the live context binds `action` to right now, or null when it binds no such action.
   *
   * Reports the map as played, the player's own overrides included, so a rebinding screen and the
   * game can never disagree about which key jump is on.
   */
  bindings(action: string): ActionBinding | null
  /**
   * Binds one action of the live context to something else, and remembers it.
   *
   * The change lands on the map at once, survives {@link SessionHandle.restart}, and is written to
   * the overrides file the runtime was launched with — `argus run --input-overrides <file>`, or
   * `<project>/.argus/input.json` under the editor's Play. A run given no file keeps the rebind
   * until the process exits and says so on stderr. The scene document is never touched: `meta.input`
   * is the author's map and a player remapping their keys is not an edit to it.
   *
   * **A name nothing answers to is refused.** A typo that bound nothing would leave the player with
   * a dead action and no way to find out why, so this throws an `Error` whose `name` is
   * `InputBindingError` and whose message opens with the reason — `input-unknown-key`,
   * `input-unknown-binding`, `input-unknown-action` or `input-binding-arity`. Nothing changes when
   * it throws.
   */
  rebind(action: string, binding: Binding): void
  /** Drops every override, going back to the scene's own map, and empties the overrides file. */
  resetBindings(): void
}
```

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

## Members

| Member | Signature | Description |
|---|---|---|
| `axis` | `axis(name: string): Vec2` | `"move"` and `"look"` are always defined; each component is in -1..1. The mouse is folded in as a rate, so for turning a view read {@link InputState.axisParts} instead (the built-in camera rigs do). |
| `axisParts` | `axisParts(name: string): AxisParts` | The same axis split into its stick `rate` and its mouse `delta` (an angle in degrees). A camera turns by `rate × speed × dt + delta`: the mouse then turns exactly as far as the hand moved, with no top speed. |
| `action` | `action(name: string): ActionState` |  |
| `pressed` | `pressed(name: string): boolean` |  |
| `held` | `held(name: string): boolean` |  |
| `released` | `released(name: string): boolean` |  |
| `context` | `readonly context: string` | The live context of the scene's action map, e.g. `"gameplay"` or `"menu"`. Scene-wide, not per entity: "the menu is open" is a fact about the scene, so every behavior sees the same answer. |
| `setContext` | `setContext(name: string): boolean` | Switch the whole scene to another context. `false`, and nothing changes, when the map has no context of that name. The switch lands on the next tick. |
| `actions` | `actions(): string[]` | Every action the live context binds, in name order. What a rebinding screen lists. |
| `bindings` | `bindings(action: string): ActionBinding \| null` | What the live context binds `action` to right now, or null when it binds no such action. Reports the map as played, the player's own overrides included, so a rebinding screen and the game can never disagree about which key jump is on. |
| `rebind` | `rebind(action: string, binding: Binding): void` | Binds one action of the live context to something else, and remembers it. The change lands on the map at once, survives {@link SessionHandle.restart}, and is written to the overrides file the runtime was launched with — `argus run --input-overrides <file>`, or `<project>/.argus/input.json` under the editor's Play. A run given no file keeps the rebind until the process exits and says so on stderr. The scene document is never touched: `meta.input` is the author's map and a player remapping their keys is not an edit to it. **A name nothing answers to is refused.** A typo that bound nothing would leave the player with a dead action and no way to find out why, so this throws an `Error` whose `name` is `InputBindingError` and whose message opens with the reason — `input-unknown-key`, `input-unknown-binding`, `input-unknown-action` or `input-binding-arity`. Nothing changes when it throws. |
| `resetBindings` | `resetBindings(): void` | Drops every override, going back to the scene's own map, and empties the overrides file. |

## Example

The tested fixture `fixtures/recipes/best-score-save/scenes/behaviors/best-score-save.ts`.

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

/**
 * A best score that outlives the run, kept with `ctx.save`.
 *
 * Space scores, Return ends the round, and the best is saved before the round restarts.
 *
 * `ctx.state` is wiped by `ctx.session.restart()`, by `loadScene` and by quitting; `ctx.save` is
 * not. So the best is read from the save in `init` — which runs again after every restart — and
 * written there the moment it is beaten, never kept only in state.
 */
interface State {
  score: number
  best: number
}

/** One store per application, so a key is worth namespacing: 1–64 of `A-Z a-z 0-9 . _ -`. */
const BEST = "best-score.best"

export default {
  init(ctx) {
    ctx.state.score = 0
    ctx.state.best = Number(ctx.save.get(BEST) ?? 0) || 0
    ctx.hud.score(0)
    ctx.hud.set("best", ctx.state.best)
  },

  update(ctx) {
    if (ctx.input !== null && ctx.input.pressed("score")) {
      ctx.state.score += 1
      ctx.hud.score(ctx.state.score)
    }
    if (ctx.input !== null && ctx.input.pressed("finish")) {
      if (ctx.state.score > ctx.state.best) {
        ctx.state.best = ctx.state.score
        ctx.save.set(BEST, ctx.state.best)
      }
      // Lands at the end of this tick; the save is written at the end of it too, first.
      ctx.session.restart()
    }
  },
} satisfies Behavior<State>
```
