# RoadEdge

> Talos next · Since 0.6

The road's edges at one distance: what {@link RoadLine.edge} returns.

Every number is metres from the centre line, always positive (the left edge is the mirror of the right).

## Signature

```ts
export interface RoadEdge {
  /** Edge of the driving surface: `at(s).halfWidth`. */
  readonly halfWidth: number
  /** Outer edge of the kerb (`halfWidth` when the road has no kerb). */
  readonly kerbOuter: number
  /** The wall here, or null when the road has none or `s` is in a `gapsAt` range. A car's centre stays inside `inner - radius`. */
  readonly barrier: {
    /** The wall's inner face, the side facing the road. */
    readonly inner: number
    /** The wall's outer face. */
    readonly outer: number
    /** Height above the road surface. */
    readonly height: number
    /** Whether the left side has a wall here. */
    readonly left: boolean
    /** Whether the right side has a wall here. */
    readonly right: boolean
  } | null
}
```

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

## Members

| Member | Signature | Description |
|---|---|---|
| `halfWidth` | `readonly halfWidth: number` | Edge of the driving surface: `at(s).halfWidth`. |
| `kerbOuter` | `readonly kerbOuter: number` | Outer edge of the kerb (`halfWidth` when the road has no kerb). |
| `barrier` | `readonly barrier: {` | The wall here, or null when the road has none or `s` is in a `gapsAt` range. A car's centre stays inside `inner - radius`. |
| `inner` | `readonly inner: number` | The wall's inner face, the side facing the road. |
| `outer` | `readonly outer: number` | The wall's outer face. |
| `height` | `readonly height: number` | Height above the road surface. |
| `left` | `readonly left: boolean` | Whether the left side has a wall here. |
| `right` | `readonly right: boolean` | Whether the right side has a wall here. |

## Example

The tested fixture `fixtures/recipes/game-over-restart/scenes/behaviors/game-over-restart.ts`.

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

/**
 * A round that ends on a results screen, whose Retry button restarts the scene.
 *
 * Space scores until the clock runs out; then the results go up with the score written into them.
 *
 * Two rules decide the shape. `push`'s props are the *reason* a screen went up, not what it
 * draws — a screen is described once, at load — so the values it shows are written after the
 * push with `ctx.ui.set("<screen>:<id>", "text", …)`. And `ctx.session.restart()` rebuilds the
 * scene at the end of the tick: every behaviour's state, the HUD and the screen stack go back to
 * how the scene starts, so nothing here has to be reset by hand.
 */
interface State {
  score: number
  left: number
  over: boolean
}

interface Props {
  /** Length of a round in seconds. Default 5. */
  roundSeconds?: number
}

const RESULTS = "results"

export default {
  init(ctx) {
    if (typeof ctx.state.score !== "number") ctx.state.score = 0
    if (typeof ctx.state.left !== "number") ctx.state.left = ctx.props.roundSeconds ?? 5
    if (typeof ctx.state.over !== "boolean") ctx.state.over = false
    ctx.events.on("ui-activated", (event) => {
      if (event.data.screen === RESULTS && event.data.id === "retry" && ctx.state.over) ctx.session.restart()
    })
  },

  update(ctx, dt) {
    if (ctx.state.over) return
    if (ctx.input !== null && ctx.input.pressed("score")) ctx.state.score += 1
    ctx.state.left = Math.max(0, ctx.state.left - dt)
    ctx.hud.score(ctx.state.score)
    ctx.hud.timer(ctx.state.left)
    if (ctx.state.left > 0) return

    ctx.state.over = true
    ctx.ui.push(RESULTS, { reason: "round-over" })
    ctx.ui.set("results:score", "text", `${ctx.state.score}`)
    ctx.events.emit("round-over", { score: ctx.state.score })
  },
} satisfies Behavior<State, Props>
```
