# Vehicle

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

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
  /** The tightest circle it can turn at its speed now, metres (the lock left after `steering.speedReduction`): a bend of radius under this needs it slower. `GET /entities` reports the same `minTurnRadius`. */
  readonly minTurnRadius: number
  /** Seconds since any wheel last touched the ground; 0 while one is down. For a landing effect that scales with the flight, or to ignore a hop that is too short to count. */
  readonly airTime: number
  /** The last touchdown (a flight that ended with a wheel meeting the ground), as of the last tick. All 0 before the first landing. */
  readonly landing: VehicleLanding
  /** 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 boost is still pushing: a mini-boost earned by a drift, or one {@link Vehicle.boost} started. */
  readonly boosting: boolean
  /** The slip angle the `handling` guard saw on the last tick: between where it points and where it travels, radians, **positive right**. 0 always on a vehicle with no `handling` block. */
  readonly slip: number
  /** True while any wheel is on an `offRoad` surface (as of the last tick); false always on a vehicle with no `offRoad` block. */
  readonly offRoad: boolean
  /** True for `recovery.protectFor` seconds after an automatic recovery (`vehicle-recovered`); false always on a vehicle with no `recovery` block. */
  readonly recovering: boolean
  /** The last automatic recovery: why, and the world time it happened in seconds; null before any, and always on a vehicle with no `recovery` block. */
  readonly lastRecovery: { why: VehicleRecoveryWhy; at: number } | null
  /**
   * Starts a boost: `speed` m/s on the top speed for `seconds`, with the `drift` block's `boostThrust` on the
   * drive force (1.3 on a vehicle without a `drift` block), fading out over its last 30% as a drift's
   * mini-boost does. Like that one it pushes while the throttle is held. Returns false, changing nothing,
   * when a boost already running gives more speed than this one; a drift's own boost replaces it when cashed in.
   * Both numbers must be finite and above 0, or it throws `vehicle.boost-invalid`. It boosts the wheels'
   * drive: a watercraft's water thrust is not boosted yet, so on a boat it starts (and reads `boosting`)
   * but does not push.
   *
   * ```ts
   * // A boost pad: any kart driving over it gets 6 m/s more for 1.5 s.
   * onTriggerEnter(ctx, other) {
   *   other.vehicle?.boost(6, 1.5)
   * }
   * ```
   */
  boost(speed: number, seconds: number): boolean
  /** `"arcade"` or `"simulation"`: the model driving it (the component's `model`). */
  readonly model: "arcade" | "simulation"
  /** Engine speed, rpm, through the gearbox; 0 on the arcade model, which has no engine speed. */
  readonly rpm: number
  /** True when anti-lock let a wheel's brake off on the last tick (simulation model; false on arcade). What an after-action review marks as "ABS engaged". */
  readonly absActive: boolean
  /** True when traction control cut a wheel's drive on the last tick (simulation model). */
  readonly tcActive: boolean
  /** True when stability control braked a wheel or trimmed the throttle on the last tick (simulation model). */
  readonly escActive: boolean
  /** How many wheels were locked on the last tick: on the ground, turning at under half the road speed under braking, above 3 m/s (simulation model; 0 on arcade). */
  readonly wheelsLocked: number
  /** True when the body slid on the last tick: its slip angle (heading against travel) past about 6 degrees above 5 m/s (simulation model). */
  readonly sliding: boolean
  /** Chassis-local proper acceleration in g, as of the last tick: `x` right, `y` up, `z` forward. Gravity is included, as an accelerometer reads it, so a parked vehicle reads `{ x: 0, y: 1, z: 0 }` and hard braking reads a negative `z`. */
  readonly gForce: { x: number; y: number; z: number }
  /** The driver aids in force, 0 (off) to 1 (full): the component's `assists`, or what {@link Vehicle.setAssists} switched. All 0 on the arcade model. */
  readonly aids: { abs: number; tractionControl: number; stabilityControl: number }
  /**
   * Switches the driver aids on a running simulation vehicle, from the next tick: each field 0 (off) to 1
   * (full), clamped; a field left out keeps its value. Returns false, changing nothing, on the arcade model,
   * which has no aids. A NaN or infinite value throws `vehicle.not-finite`. Works through `other.vehicle`
   * too, so a scenario's director can switch the trainee's aids. The switch is simulation state: a replay
   * or a rollback replays it.
   *
   * ```ts
   * // An instructor's "ABS failure" inject: from now on the wheels can lock.
   * ctx.events.on("abs-failure", () => ctx.world.find("medic-1")?.vehicle?.setAssists({ abs: 0 }))
   * ```
   */
  setAssists(aids: { abs?: number; tractionControl?: number; stabilityControl?: number }): 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[]
  /** What its water handling did on the last tick (a vehicle with a `water` block; `null` on every other): the hooks a wake, a bow spray or a splash sound reads. */
  readonly water: VehicleWater | null
}
```

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` |  |
| `minTurnRadius` | `readonly minTurnRadius: number` | The tightest circle it can turn at its speed now, metres (the lock left after `steering.speedReduction`): a bend of radius under this needs it slower. `GET /entities` reports the same `minTurnRadius`. |
| `airTime` | `readonly airTime: number` | Seconds since any wheel last touched the ground; 0 while one is down. For a landing effect that scales with the flight, or to ignore a hop that is too short to count. |
| `landing` | `readonly landing: VehicleLanding` | The last touchdown (a flight that ended with a wheel meeting the ground), as of the last tick. All 0 before the first landing. |
| `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 boost is still pushing: a mini-boost earned by a drift, or one {@link Vehicle.boost} started. |
| `slip` | `readonly slip: number` | The slip angle the `handling` guard saw on the last tick: between where it points and where it travels, radians, **positive right**. 0 always on a vehicle with no `handling` block. |
| `offRoad` | `readonly offRoad: boolean` | True while any wheel is on an `offRoad` surface (as of the last tick); false always on a vehicle with no `offRoad` block. |
| `recovering` | `readonly recovering: boolean` | True for `recovery.protectFor` seconds after an automatic recovery (`vehicle-recovered`); false always on a vehicle with no `recovery` block. |
| `lastRecovery` | `readonly lastRecovery: { why: VehicleRecoveryWhy; at: number } \| null` | The last automatic recovery: why, and the world time it happened in seconds; null before any, and always on a vehicle with no `recovery` block. |
| `boost` | `boost(speed: number, seconds: number): boolean` | Starts a boost: `speed` m/s on the top speed for `seconds`, with the `drift` block's `boostThrust` on the drive force (1.3 on a vehicle without a `drift` block), fading out over its last 30% as a drift's mini-boost does. Like that one it pushes while the throttle is held. Returns false, changing nothing, when a boost already running gives more speed than this one; a drift's own boost replaces it when cashed in. Both numbers must be finite and above 0, or it throws `vehicle.boost-invalid`. It boosts the wheels' drive: a watercraft's water thrust is not boosted yet, so on a boat it starts (and reads `boosting`) but does not push. `ts // A boost pad: any kart driving over it gets 6 m/s more for 1.5 s. onTriggerEnter(ctx, other) {   other.vehicle?.boost(6, 1.5) } ``` |
| `model` | `readonly model: "arcade" \| "simulation"` | `"arcade"` or `"simulation"`: the model driving it (the component's `model`). |
| `rpm` | `readonly rpm: number` | Engine speed, rpm, through the gearbox; 0 on the arcade model, which has no engine speed. |
| `absActive` | `readonly absActive: boolean` | True when anti-lock let a wheel's brake off on the last tick (simulation model; false on arcade). What an after-action review marks as "ABS engaged". |
| `tcActive` | `readonly tcActive: boolean` | True when traction control cut a wheel's drive on the last tick (simulation model). |
| `escActive` | `readonly escActive: boolean` | True when stability control braked a wheel or trimmed the throttle on the last tick (simulation model). |
| `wheelsLocked` | `readonly wheelsLocked: number` | How many wheels were locked on the last tick: on the ground, turning at under half the road speed under braking, above 3 m/s (simulation model; 0 on arcade). |
| `sliding` | `readonly sliding: boolean` | True when the body slid on the last tick: its slip angle (heading against travel) past about 6 degrees above 5 m/s (simulation model). |
| `gForce` | `readonly gForce: { x: number; y: number; z: number }` | Chassis-local proper acceleration in g, as of the last tick: `x` right, `y` up, `z` forward. Gravity is included, as an accelerometer reads it, so a parked vehicle reads `{ x: 0, y: 1, z: 0 }` and hard braking reads a negative `z`. |
| `aids` | `readonly aids: { abs: number; tractionControl: number; stabilityControl: number }` | The driver aids in force, 0 (off) to 1 (full): the component's `assists`, or what {@link Vehicle.setAssists} switched. All 0 on the arcade model. |
| `setAssists` | `setAssists(aids: { abs?: number; tractionControl?: number; stabilityControl?: number }): boolean` | Switches the driver aids on a running simulation vehicle, from the next tick: each field 0 (off) to 1 (full), clamped; a field left out keeps its value. Returns false, changing nothing, on the arcade model, which has no aids. A NaN or infinite value throws `vehicle.not-finite`. Works through `other.vehicle` too, so a scenario's director can switch the trainee's aids. The switch is simulation state: a replay or a rollback replays it. `ts // An instructor's "ABS failure" inject: from now on the wheels can lock. ctx.events.on("abs-failure", () => ctx.world.find("medic-1")?.vehicle?.setAssists({ abs: 0 })) ``` |
| `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. |
| `water` | `readonly water: VehicleWater \| null` | What its water handling did on the last tick (a vehicle with a `water` block; `null` on every other): the hooks a wake, a bow spray or a splash sound reads. |

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