# RoadLine

> Talos 0.7 · Since 0.6; changed in 0.7

A road's centre line, from `ctx.world.road(id)`.

Read-only. Positions are in the road entity's own frame (add the entity's transform for the world's). The engine builds it once per road, so calling `ctx.world.road(id)` again is cheap and returns the same object; keeping it from `init` is still fine. @example const road = ctx.world.road("circuit")! const here = road.closest(ctx.entity.position, ctx.state.s) // lap progress, off-road distance const ahead = road.at(here.s + 20) // a point to steer toward if (Math.abs(here.offset) > ahead.halfWidth) ctx.hud.prompt("road.off") @example // Laps and race position, kept by the engine: no lap counter of your own. const me = road.progress(ctx.entity)! ctx.hud.set("lap", me.lap + 1) ctx.hud.set("place", me.place) if (me.lap >= 3) ctx.events.emit("finished", { place: me.place })

## Signature

```ts
export interface RoadLine {
  /** Metres once around a closed road, end to end on an open one. */
  readonly length: number
  /** True for a loop (a lap circuit), false for an open road; `at` wraps on a closed road and clamps on an open one. */
  readonly closed: boolean
  /** Width of the kerb strip outside each edge, metres; 0 for a road with no `kerb`. `halfWidth` does not include it. */
  readonly kerbWidth: number
  /** Height of the kerb strip, metres; 0 with no `kerb`. */
  readonly kerbHeight: number
  /** The wall the file asked for, or null for a road with no `barrier`. */
  readonly barrier: RoadBarrier | null
  /** The road's trigger gates, in order along it: the child entity's scene id (`ctx.world.find(id)`) and its distance along the road. Gate 0 is the start line. */
  readonly gates: readonly { readonly id: string; readonly s: number }[]
  /** The centre line at distance `s`. */
  at(s: number): RoadSample
  /**
   * The nearest point on the centre line to `pos`. With `hintS` only samples within `windowM` (default
   * 40 m) of it are searched, so a car asking every tick cannot jump to a crossing section of road.
   */
  closest(pos: Vec3, hintS?: number, windowM?: number): RoadClosest
  /**
   * Where the kerb and the wall stand at distance `s`, as lateral distances from the centre line. The wall
   * is `halfWidth + kerbWidth + offsetM` out: keep a car's centre inside `barrier.inner` minus its radius
   * and it stays on the road side of it.
   */
  edge(s: number): RoadEdge
  /** The centre line as points `stepM` apart (default the road's sample step): a minimap polyline. */
  polyline(stepM?: number): Vec3[]
  /**
   * How far `entity` (a handle or scene id) has come along this road, lap by lap; null when it is not
   * alive. The first call starts tracking it on this road, where it is now; from then on the engine
   * steps it every tick (after physics, before behaviours), so every behaviour reads the same answer
   * in a tick and a rival nobody asks about still has a place. There is no other call to start or
   * stop it: a despawned entity leaves the standings by itself.
   *
   * Laps count at checkpoints: the road's gates in order (gate 0 is the start line), or every quarter
   * of a road with no gates. Reversing over a checkpoint takes it back, and one skipped (a cut across
   * the infield, a teleport past it) has to be crossed before any after it counts, so neither earns a
   * lap. An entity first seen in the last quarter of the lap before the line is on the grid: crossing
   * the line starts its first lap. Forward is the way `s` grows.
   */
  progress(entity: EntityHandle | string): RoadProgress | null
  /** The entities tracked on this road (see {@link RoadLine.progress}), in race order: 1st first. */
  standings(): RoadStanding[]
}
```

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

## Members

| Member | Signature | Description |
|---|---|---|
| `length` | `readonly length: number` | Metres once around a closed road, end to end on an open one. |
| `closed` | `readonly closed: boolean` | True for a loop (a lap circuit), false for an open road; `at` wraps on a closed road and clamps on an open one. |
| `kerbWidth` | `readonly kerbWidth: number` | Width of the kerb strip outside each edge, metres; 0 for a road with no `kerb`. `halfWidth` does not include it. |
| `kerbHeight` | `readonly kerbHeight: number` | Height of the kerb strip, metres; 0 with no `kerb`. |
| `barrier` | `readonly barrier: RoadBarrier \| null` | The wall the file asked for, or null for a road with no `barrier`. |
| `gates` | `readonly gates: readonly { readonly id: string; readonly s: number }[]` | The road's trigger gates, in order along it: the child entity's scene id (`ctx.world.find(id)`) and its distance along the road. Gate 0 is the start line. |
| `at` | `at(s: number): RoadSample` | The centre line at distance `s`. |
| `closest` | `closest(pos: Vec3, hintS?: number, windowM?: number): RoadClosest` | The nearest point on the centre line to `pos`. With `hintS` only samples within `windowM` (default 40 m) of it are searched, so a car asking every tick cannot jump to a crossing section of road. |
| `edge` | `edge(s: number): RoadEdge` | Where the kerb and the wall stand at distance `s`, as lateral distances from the centre line. The wall is `halfWidth + kerbWidth + offsetM` out: keep a car's centre inside `barrier.inner` minus its radius and it stays on the road side of it. |
| `polyline` | `polyline(stepM?: number): Vec3[]` | The centre line as points `stepM` apart (default the road's sample step): a minimap polyline. |
| `progress` | `progress(entity: EntityHandle \| string): RoadProgress \| null` | How far `entity` (a handle or scene id) has come along this road, lap by lap; null when it is not alive. The first call starts tracking it on this road, where it is now; from then on the engine steps it every tick (after physics, before behaviours), so every behaviour reads the same answer in a tick and a rival nobody asks about still has a place. There is no other call to start or stop it: a despawned entity leaves the standings by itself. Laps count at checkpoints: the road's gates in order (gate 0 is the start line), or every quarter of a road with no gates. Reversing over a checkpoint takes it back, and one skipped (a cut across the infield, a teleport past it) has to be crossed before any after it counts, so neither earns a lap. An entity first seen in the last quarter of the lap before the line is on the grid: crossing the line starts its first lap. Forward is the way `s` grows. |
| `standings` | `standings(): RoadStanding[]` | The entities tracked on this road (see {@link RoadLine.progress}), in race order: 1st first. |

## Example

The tested fixture `fixtures/app/agent.ts`.

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

// Sends a nav agent to `props.target` once, then reports arrival through ctx.state so the
// test can read it without polling the crowd directly.
export default {
  init(ctx) {
    ctx.state.arrived = false
    ctx.nav?.goTo(ctx.props.target as [number, number, number])
  },
  update(ctx) {
    if (ctx.nav?.arrived) ctx.state.arrived = true
  },
} satisfies Behavior
```
