# AiDriver
An AI driver for the car it sits on. Put it on a car's chassis: a torqueCars `Car` (driven through `Car:setIntent`) or a builtin `VehicleController` (driven through its own `setIntent`, throttle -1..1). It only thinks on the peer that owns the car (`entity:isLocal()`), at the personality's rate (15-40 Hz), and keeps its inputs between ticks.
What it does each tick: finds where it is along its line (nearest point, searched in a window around the last one; a whole-path search only after a knock), steers by pure pursuit at a lookahead that grows with speed plus yaw-rate feedback (damps the weave and catches oversteer), takes a target speed from the curvature ahead (`v = sqrt(latG * 9.81 / k)`, pulled down before corners by its braking deceleration), keeps a gap to the car ahead (a cone in front of it, against a car list shared by every driver and refreshed once a second), overtakes or goes round a stopped car, gets itself unstuck (under 1.5 m/s for 2 s, for any reason: 1.6 s reverse with the nose swinging toward the path, the other lock on each retry; still slow at 5 s, or 8 s without gaining 6 m along the path, 4 s when far off the road: respawned on its line at the nearest clear point ahead, no other car within 8 m, drift or tourist manoeuvre cancelled), resets upright when flipped for 2 s, and drives back to its line after being knocked off. It learns the car: the braking it plans for drops to what it measures, and the cornering g drops a little each time it runs wide.
**Fields:** `personality` (commuter, aggressive, racer, drifter, tourist), `path` (a roadPieces road, or an entity whose children are waypoints in order), `loop`, `autoPath` (follow the nearest roadPieces road when nothing else is given), `roadWidth`, `laneOffset` (-1 = the personality's lane), `speedLimit` (m/s, 0 = none), `driving`, `seed`.
**Methods:** `setPath(points, loop)`, `clearPath()`, `setPersonality(name)` (any name defined in `aiDrivers.personalities`), `laps()` → `{ count, last, best, times, current }` (physics seconds, start-line crossing to crossing), `state()` (mode, index, offPath, speed, targetSpeed, pedals, drift phase, body `slip`, `rearSlipAngle`/`rearSlipMean`/`rearSlipMax`, tourist state, stuck/reset counts, `slowTime`/`noProgressTime`/`reverseTries`/`lastRecovery`, learned `planGrip`/`planDecel`), `stats()` → `{ avgTickMs, maxTickMs, ticks, rate, msPerSecond }`, `resetStats()`, `linePoints()`, `respawnOnPath()` (nearest clear point on its line).
**Values it publishes** on the chassis: `ai.hazard` (stopped or lost; replicated), `ai.lap`.
```luau
local Assemble = require("torqueCars.assemble")
local car = Assemble.spawn({ kind = "coupe", position = vector.create(0, 0.3, 0), control = "script" })
local ai = car.component.add("AiDriver", { personality = "racer", roadWidth = 9 })
ai:setPath({ vector.create(0, 0, 0), vector.create(0, 0, -120), vector.create(60, 0, -150), vector.create(80, 0, -20) }, true)
```
# AiDriversShowcase
Five AI drivers, one per aiDrivers personality, lapping a 1.2 km test loop on a grass field: a main straight past a grandstand where the player spawns, a right-hander into esses, a long sweeper, two hairpins and a fast kink, with red and white kerbs on the corners and a chequered start line. `build.luau` lays out the track (via `aiDrivers.track`), lights, sky, the player and `aiPath`, the loop's control points as waypoint children. On the host, the entrypoint waits for the ground's collider, then lines up a racer (coupe), an aggressive driver (muscle car), a drifter (drift car), a commuter (hatchback) and a tourist (van) on the grid, each with an `AiDriver` following `aiPath` and a floating name tag. Use it to watch the personalities side by side or to test a change to the driver.
# aiDrivers.paths
The route geometry AI drivers drive by, computed once per path: `resample(points, loop, spacing)` (even spacing; sparse waypoints are joined with a Catmull-Rom spline), `normals` (right of travel), `curvatures` (signed 1/m, positive turning right, smoothed), `racingLine(points, loop, halfWidth)` (each point slid sideways within the road to minimise curvature: outside on entry, apex clipped, outside on exit), `offset` (a lane), `speedProfile(points, loop, curvature, { latG, maxSpeed, decel })` (cornering limit `sqrt(latG*g/k)` with braking points worked backwards from every corner), `distances` and `nearest`. Pure functions over `{vector}`; `AiDriver` calls them, and anything else that needs a racing line or a speed plan (a ghost car, a minimap line, a track editor) can too.
```luau
local Paths = require("aiDrivers.paths")
local pts = Paths.resample(waypoints, true, 2)
local line = Paths.racingLine(pts, true, 3.5)
local v = Paths.speedProfile(line, true, Paths.curvatures(line, true), { latG = 0.9, maxSpeed = 60, decel = 8 })
```
# aiDrivers.personalities
How each kind of AI driver drives, as plain data tables that `AiDriver` reads every tick: target speed scale and cap, cornering g, planned braking deceleration, pedal and steering gains and smoothing, pure-pursuit lookahead, following gap, randomness, lane, how much of the road a racing line may use, and switches for overtaking, leaning on cars, drifting (target slip angle) and the tourist's moods (wander, missed turns, random stops, hesitations), plus the brain's tick rate.
Built in: `commuter` (careful, modest speed, early braking, big gap, smooth), `aggressive` (fast, late braking, tailgates, overtakes, leans on cars), `racer` (racing line, brakes as late as grip allows, flat out on exits), `drifter` (handbrake flick into corners, holds the slide on countersteer and throttle; turns the car's assists off), `tourist` (slow, wanders, misses turns and U-turns back, stops for no reason).
Add one with `define(name, table)`, usually from `extend(base, changes)`; `AiDriver:setPersonality(name)` then drives by it.
```luau
local P = require("aiDrivers.personalities")
P.define("granny", P.extend("commuter", { maxSpeed = 8, followGap = 4, label = "Granny" }))
car.component.get("AiDriver"):setPersonality("granny")
```
# aiDrivers
Checks the AI drivers' data, the path maths they plan by and the component's API:
- **Personalities**: five built-ins in order, each a complete table. They differ as described: the commuter is slower, brakes earlier, keeps a lane and leaves a bigger gap; the racer uses the full road; the drifter drifts with assists off; the tourist misses turns and stops; the aggressive driver tailgates. `extend` copies without touching the base, and an unknown base raises.
- **Paths**: `resample` makes even 2 m spacing on an open line (51 points over 100 m) and a splined loop (no repeated start). `distances` measures loop and open lengths. `normals` point right of travel and `offset` slides lanes. `curvatures` are ±1/R on circles (sign follows direction) and 0 on a line. `nearest` returns the closest index and squared distance. `speedProfile` respects the cornering limit √(latG·g/|k|), `maxSpeed` and the braking constraint, and ends an open route nearly stopped. `racingLine` stays within ±halfWidth, uses the width and lowers Σk² and peak curvature on the test track.
- **Track**: 36 control points, a ~1.2 km closed centreline, an origin shift and alternating grid slots.
- **AiDriver**: on a script-controlled torqueCars hatchback (skipped without torqueCars): `setPersonality` switches the driver and refuses unknown names, `setPath` / `clearPath` take a route, `laps()` starts empty, `stats().rate` follows the personality and `state()` gives the full read-out.
It leaves alone actual driving (steering, pedals, overtaking, drifting, recovery, lap timing), which needs physics running in play, as well as the showcase scene and `define()`. `define()` registers a personality for the rest of the session, which a repeatable suite must not do. The test car is spawned far below the play area and despawned after the test.
Run it in edit mode:
```luau
return asset.resolve("aiDrivers", "testSuite"):run()
```
# aiDrivers.track
A ready-made ~1.2 km closed test loop for AI drivers and its builder. `points(origin)` gives the centreline control points in driving order (main straight, a right-hander into esses, a long sweeper, two hairpins, a fast kink), `centreline(origin, spacing)` the same splined and evenly spaced, `gridSlot(origin, slot)` a grid position behind the start line, `build({ origin, roadWidth, tile, synced, parent })` draws it (a 420 m grass ground box that is the only collider, flat asphalt tiles, a centre dash, red and white kerbs on the corners, a chequered start line) and `waypoints({ origin, parent, synced })` spawns the control points as an `aiPath` entity of waypoint children, ready for an AiDriver's `path` field. Used by the AiDriversShowcase scene and handy for testing any driving AI.
# aiDrivers.traffic
The car list every AI driver on a peer shares, so seven drivers make one list a second between them instead of seven: `cars()` returns `{ id, ref, kind }` for every torqueCars `Car` chassis (from `torqueCars.cars`) plus every builtin `VehicleController` chassis (found by a scene walk every 5 s), rebuilt at most once a second. `invalidate()` forces the next call to rebuild it, for code that just spawned cars. `AiDriver` reads it for following distance, overtaking and leaning; a traffic manager or a minimap can read it too.
# roads (roadPieces.roads)
Procedural road and track pieces with real geometry: straight, curve, T and X junctions (filleted corners, stop lines), roundabout (kerbed island, arms clipped to the ring, give-way lines), ramp, kicker jump with table-top landing, banked turn (inside kerb, outside wall), guardrail, concrete wall, tyre wall, and a Catmull-Rom spline road through any list of points that follows their heights. Three styles switched by one name: country (narrow two-lane tarmac, faded dashed centre line, grass verges, marker posts), highway (four lanes, edge lines, double centre line, concrete barriers) and dirt (rutted dirt track with gravel shoulders and a grass crown, no markings). `build(spec)` makes the mesh data, `realize(root, built, style)` stands it up as temporary children with static exact MeshColliders and driveSurfaces Surfaces, `spawn(kind, opts)` and `spline(points, opts)` place a RoadPiece, `pathOf(road, offset?)` returns its centreline in world space for AI drivers, and `gallery(parent)` lays out every piece in every style.
# RoadPiece
Turns its entity into a road or track piece: `kind` (straight, curve, tJunction, xJunction, roundabout, ramp, kicker, bankedTurn, guardrail, wall, tyreWall, spline), `style` (country, highway, dirt) and size fields (`length`, `radius`, `angle`, `bank`, `slope`, `height`, `gap`, `width`, `arms`, `verge`, `rails`, `points`, `loop`), all synced. It builds the geometry locally on every peer as temporary child entities (one per material, each with a static exact MeshCollider, verges/kerbs/walls with their own driveSurfaces Surface) and rebuilds whenever a field changes, so one setting restyles the piece. It also gives the entity a Surface matching the style (tarmac or dirt). Inside a scene build it records itself and builds right after, so scenes store only the fields. `:path(offset?)` returns the centreline in world space.
# Car
The driver of a torqueCars car. Sits on the chassis `torqueCars.assemble` builds (with its `Physics`, `CompoundCollider`, `ShaftNetwork`, four `WheelCollider` children and the `SoftBody` body). Each physics step on the owning peer it runs the engine (throttle, idle governor, turbo, rev limiter), the automatic clutch, the gearbox (auto or manual shifts, reverse), brakes with ABS, traction control, speed-sensitive steering, per-wheel ground grip from driveSurfaces, drag and rolling resistance, and counts crashes. It publishes the driving contract's named values every frame (see the package's CONTRACT.md).
Fields: `kind`, `look`, `paint`, `engine`, `gearbox`, `diff`, `tyres` (changing any rebuilds the car in place), `control` (player / script / none), `autoShift`, `assists`, and the script intent fields.
Methods: `setIntent{throttle, brake, steer, handbrake, reverse}`, `shiftUp()`, `shiftDown()`, `setPart(slot, name)`, `reset()`, `respawn(pos?, yaw?)`, `setSpawn(pos, yaw)`, `gearLabel()`, `info()`, `telemetry()`.
```luau
local car = require("torqueCars.assemble").spawn({ kind = "drift", position = vector.create(0, 0.2, 0), control = "script" })
car.component.get("Car"):setIntent({ throttle = 0.5, steer = 0.3 })
```
# torqueCars.assemble
Builds a whole car under one chassis entity from a kind and part names: dynamic chassis with its centre of mass at the kind's height and weight split, four `WheelCollider` wheels with the tyre set's grip curve and drawn wheels, the `ShaftNetwork` drivetrain for the drive layout, and the plastic `SoftBody` shell held along the cabin floor. Idempotent: running `build` again updates the car in place (a live part swap). `spawn` builds a car and adds the `Car` component (synced in play).
```luau
local Assemble = require("torqueCars.assemble")
local car = Assemble.spawn({ kind = "pickup", look = "toy", position = vector.create(0, 0.2, 0), yaw = 0 })
```
# torqueCars.cars
The live torqueCars cars on this peer: `all()` returns every chassis id, `localPlayer()` the chassis this peer's player drives. The `Car` component registers and unregisters itself; gauges, cameras, the garage and AI use it to find cars cheaply.
```luau
local Cars = require("torqueCars.cars")
print(#Cars.all(), Cars.localPlayer())
```