# Vehicle

> Talos 0.6 · Since 0.6

Present on `ctx.vehicle` while the entity is a running vehicle: a `vehicle` component on a dynamic body with a collider.

Null otherwise. **Who drives.** With the component's `input: "auto"` (the default) the engine drives it from the input actions `throttle`, `brake`, `steer` and `handbrake` every tick — keyboard, pad and `POST /input` alike — and a behaviour only reads it. With `input: "script"` the engine reads nothing and a behaviour drives it with {@link Vehicle.drive}; `drive` on an `auto` vehicle changes nothing and is counted on `GET /perf` → `unwired.vehicle_ignored`. **Signs.** Forward is the entity's -Z. `speed` is negative while reversing. Right is positive throughout: `steer` in `drive` is +1 right, as the action reads, and `steerAngle` is the wheels' angle in radians, positive right. `ts // A cruise control: hold 12 m/s on the straight, whatever the driver's foot says. update(ctx) { const v = ctx.vehicle! v.drive({ throttle: v.speed < 12 ? 1 : 0, brake: v.speed > 13 ? 0.3 : 0 }) ctx.hud.set("speed", Math.round(v.speed * 3.6) + " km/h") } ```

## Signature

```ts
export interface Vehicle {
  /** Holds these pedals and this steering from the next tick until the next call (`input: "script"` only). */
  drive(cmd: VehicleDrive): void
  /** Puts the vehicle back on its wheels, upright and still, where every wheel last touched the ground. */
  reset(): void
  /** `"auto"`: the engine drives it from the input actions. `"script"`: a behaviour does. */
  readonly input: "auto" | "script"
  /** m/s along the vehicle's forward (-Z); negative while reversing. */
  readonly speed: number
  /** The front wheels' angle, radians, **positive right** — the sign the `steer` action and {@link Vehicle.drive} use. */
  readonly steerAngle: number
  /** The throttle and brake in force, 0..1 (a brake at a standstill has become reverse). */
  readonly throttle: number
  readonly brake: number
  /** 1 forward, -1 reverse. */
  readonly gear: number
  readonly wheelsOnGround: number
  /** True while a drift is held: a vehicle with a `drift` block, its handbrake input down, steering above its minimum speed and angle. False always on a vehicle with no `drift` block. */
  readonly drifting: boolean
  /** The angle between where it points and where it travels while it drifts, radians, **positive right** like {@link Vehicle.steerAngle}; 0 when not drifting. */
  readonly driftAngle: number
  /** Seconds of boost-earning slide stored by the drift so far; 0 when not drifting. */
  readonly driftCharge: number
  /** The boost tier the charge has reached: 0 none, 1 the first of the vehicle's `boostTiers`, and so on. What a game turns into sparks and sounds. */
  readonly driftTier: number
  /** True while a mini-boost earned by a drift is still pushing. */
  readonly boosting: boolean
  /** Its wheels, in the vehicle's order: each one's {@link Wheel.drivenBy} is `"vehicle"`. On an `input: "script"` vehicle a write to one takes it over until the next {@link Vehicle.drive}; on an `auto` one it changes nothing. */
  readonly wheels: Wheel[]
}
```

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

## Members

| Member | Signature | Description |
|---|---|---|
| `drive` | `drive(cmd: VehicleDrive): void` | Holds these pedals and this steering from the next tick until the next call (`input: "script"` only). |
| `reset` | `reset(): void` | Puts the vehicle back on its wheels, upright and still, where every wheel last touched the ground. |
| `input` | `readonly input: "auto" \| "script"` | `"auto"`: the engine drives it from the input actions. `"script"`: a behaviour does. |
| `speed` | `readonly speed: number` | m/s along the vehicle's forward (-Z); negative while reversing. |
| `steerAngle` | `readonly steerAngle: number` | The front wheels' angle, radians, **positive right** — the sign the `steer` action and {@link Vehicle.drive} use. |
| `throttle` | `readonly throttle: number` | The throttle and brake in force, 0..1 (a brake at a standstill has become reverse). |
| `brake` | `readonly brake: number` |  |
| `gear` | `readonly gear: number` | 1 forward, -1 reverse. |
| `wheelsOnGround` | `readonly wheelsOnGround: number` |  |
| `drifting` | `readonly drifting: boolean` | True while a drift is held: a vehicle with a `drift` block, its handbrake input down, steering above its minimum speed and angle. False always on a vehicle with no `drift` block. |
| `driftAngle` | `readonly driftAngle: number` | The angle between where it points and where it travels while it drifts, radians, **positive right** like {@link Vehicle.steerAngle}; 0 when not drifting. |
| `driftCharge` | `readonly driftCharge: number` | Seconds of boost-earning slide stored by the drift so far; 0 when not drifting. |
| `driftTier` | `readonly driftTier: number` | The boost tier the charge has reached: 0 none, 1 the first of the vehicle's `boostTiers`, and so on. What a game turns into sparks and sounds. |
| `boosting` | `readonly boosting: boolean` | True while a mini-boost earned by a drift is still pushing. |
| `wheels` | `readonly wheels: Wheel[]` | Its wheels, in the vehicle's order: each one's {@link Wheel.drivenBy} is `"vehicle"`. On an `input: "script"` vehicle a write to one takes it over until the next {@link Vehicle.drive}; on an `auto` one it changes nothing. |

## 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>
```
