# Wheel

> Talos next · Since 0.6; changed in next

Present on `ctx.wheel` while the entity is a running wheel: a `wheel` component on an entity with a dynamic body above it in the hierarchy.

Null otherwise. Also reached as `ctx.body.wheels` and `ctx.vehicle.wheels` from the body's own behaviour. **Who drives.** A wheel no `vehicle` composes is driven by behaviours: {@link Wheel.setMotorTorque}, {@link Wheel.setBrakeTorque} and {@link Wheel.setSteer} hold until the next call, and each tick the wheel's spring and tyre push its body — a cart, a trailer, a robot on wheels. A wheel a vehicle composes is driven by the vehicle. On an `input: "script"` vehicle, each field a behaviour writes takes that wheel over from the vehicle's command until the vehicle's next {@link Vehicle.drive} (torque vectoring, a spun-up wheel); on an `auto` vehicle a write changes nothing and is counted on `GET /perf` → `unwired.vehicle_ignored`. **Signs.** Positive motor torque rolls it along its forward (its composed -Z). Steer is **positive right**, like the `steer` action and {@link Vehicle.steerAngle}. `ts // A cart: drive every wheel, steer the ones named "front-…", and brake when nothing is held. update(ctx) { const go = ctx.input?.action("throttle").held ?? false for (const w of ctx.body!.wheels) { w.setMotorTorque(go ? 40 : 0) w.setBrakeTorque(go ? 0 : 200) if (w.entity.id.startsWith("front-")) w.setSteer(0.3 * (ctx.input?.axis("steer")[0] ?? 0)) } } ```

## Signature

```ts
export interface Wheel {
  /** Drive torque at the axle, N·m, from the next tick until the next call. Positive rolls it forward. */
  setMotorTorque(nm: number): void
  /** Brake torque, N·m, 0 or more, from the next tick until the next call. */
  setBrakeTorque(nm: number): void
  /** Steer angle about the body's up, radians, **positive right**, from the next tick until the next call. */
  setSteer(radians: number): void
  /** The wheel's own entity. */
  readonly entity: EntityHandle
  /** `"script"`: behaviours drive it. `"vehicle"`: its vehicle does. */
  readonly drivenBy: "script" | "vehicle"
  readonly grounded: boolean
  /** Where it touches the ground; null in the air. */
  readonly contact: WheelContact | null
  /** Slip ratio (`long`; 0 on a `clamp` tyre, which rolls at road speed) and slip angle (`lat`), radians. */
  readonly slip: { long: number; lat: number }
  readonly rpm: number
  /** N the spring carries. */
  readonly load: number
  /** 0 at the spring's rest length or longer, 1 against the bump stop. */
  readonly compression: number
  /** Radians, **positive right**. */
  readonly steerAngle: number
  /** N·m: the torques in force — a behaviour's, or what its vehicle asked of it. */
  readonly motorTorque: number
  readonly brakeTorque: number
  /** Metres. */
  readonly radius: number
  /** True when it was locked on the last step: on the ground, turning at under half the road speed under braking, above 3 m/s (a `curve` tyre; the arcade model's `clamp` tyre cannot lock). */
  readonly locked: boolean
  /** What the ground did to its grip on the last step, as a multiplier on the tyre's own: 1 on dry asphalt, about 0.7 soaked, 0.12 on ice; 0 in the air. */
  readonly grip: number
  /** Its tyre's condition (see {@link Wheel.setTyre}): multipliers on its grip and rolling resistance, 1 and 1 new. */
  readonly tyre: { grip: number; rolling: number }
  /**
   * Puts the tyre in a condition until set again, from the next tick: multipliers on its grip and its rolling
   * resistance, 0 or more; a field left out keeps its value. Works on any wheel, an `input: "auto"` vehicle's
   * too: a blowout is something that happens to a tyre, not a control. Returns false when the wheel has gone; a
   * NaN throws `wheel.not-finite`, a negative value `wheel.tyre-invalid`. It is simulation state: a replay or a
   * rollback replays it.
   *
   * ```ts
   * // An instructor's "tyre blowout" inject on the front left.
   * ctx.events.on("blowout", () => ctx.vehicle!.wheels[0].setTyre({ grip: 0.3, rolling: 10 }))
   * ```
   */
  setTyre(tyre: { grip?: number; rolling?: number }): boolean
}
```

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

## Members

| Member | Signature | Description |
|---|---|---|
| `setMotorTorque` | `setMotorTorque(nm: number): void` | Drive torque at the axle, N·m, from the next tick until the next call. Positive rolls it forward. |
| `setBrakeTorque` | `setBrakeTorque(nm: number): void` | Brake torque, N·m, 0 or more, from the next tick until the next call. |
| `setSteer` | `setSteer(radians: number): void` | Steer angle about the body's up, radians, **positive right**, from the next tick until the next call. |
| `entity` | `readonly entity: EntityHandle` | The wheel's own entity. |
| `drivenBy` | `readonly drivenBy: "script" \| "vehicle"` | `"script"`: behaviours drive it. `"vehicle"`: its vehicle does. |
| `grounded` | `readonly grounded: boolean` |  |
| `contact` | `readonly contact: WheelContact \| null` | Where it touches the ground; null in the air. |
| `slip` | `readonly slip: { long: number; lat: number }` | Slip ratio (`long`; 0 on a `clamp` tyre, which rolls at road speed) and slip angle (`lat`), radians. |
| `rpm` | `readonly rpm: number` |  |
| `load` | `readonly load: number` | N the spring carries. |
| `compression` | `readonly compression: number` | 0 at the spring's rest length or longer, 1 against the bump stop. |
| `steerAngle` | `readonly steerAngle: number` | Radians, **positive right**. |
| `motorTorque` | `readonly motorTorque: number` | N·m: the torques in force — a behaviour's, or what its vehicle asked of it. |
| `brakeTorque` | `readonly brakeTorque: number` |  |
| `radius` | `readonly radius: number` | Metres. |
| `locked` | `readonly locked: boolean` | True when it was locked on the last step: on the ground, turning at under half the road speed under braking, above 3 m/s (a `curve` tyre; the arcade model's `clamp` tyre cannot lock). |
| `grip` | `readonly grip: number` | What the ground did to its grip on the last step, as a multiplier on the tyre's own: 1 on dry asphalt, about 0.7 soaked, 0.12 on ice; 0 in the air. |
| `tyre` | `readonly tyre: { grip: number; rolling: number }` | Its tyre's condition (see {@link Wheel.setTyre}): multipliers on its grip and rolling resistance, 1 and 1 new. |
| `setTyre` | `setTyre(tyre: { grip?: number; rolling?: number }): boolean` | Puts the tyre in a condition until set again, from the next tick: multipliers on its grip and its rolling resistance, 0 or more; a field left out keeps its value. Works on any wheel, an `input: "auto"` vehicle's too: a blowout is something that happens to a tyre, not a control. Returns false when the wheel has gone; a NaN throws `wheel.not-finite`, a negative value `wheel.tyre-invalid`. It is simulation state: a replay or a rollback replays it. `ts // An instructor's "tyre blowout" inject on the front left. ctx.events.on("blowout", () => ctx.vehicle!.wheels[0].setTyre({ grip: 0.3, rolling: 10 })) ``` |

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