import { type LaneGraph } from "./laneGraph.js"; import type { Waypoint } from "./pathFollow.js"; /** One car's live state, as a renderer, a game or the devtools overlay reads it. */ export interface TrafficCarView { id: string; laneId: string; /** Arc, in metres, along the lane. */ arc: number; position: Waypoint; /** Yaw of travel, matching engine `rotationY`. */ heading: number; /** World units per second. */ speed: number; state: "driving" | "boxed" | "parked"; /** Parking spot held or reserved, while parking. */ spotId: string | null; /** Stable integer for the game's model or colour choice; derived from the injected RNG. */ variant: number; } /** Caller-supplied exit choice at a junction: return one of `options`, or `null` to leave the graph. */ export type TrafficRoutePolicy = (context: { carId: string; laneId: string; nodeId: string; /** Outbound lanes that are not the lane the car arrived on, sorted by id. */ options: readonly string[]; }) => string | null; /** Every per-car behaviour number, retunable in play. */ export interface TrafficTuning { /** Body length, metres; sets the spacing a follower keeps. Default 4.4. */ carLength: number; /** Body width, metres — one lane-change blend and the fallback lane spacing. Default 1.9. */ carWidth: number; /** Acceleration while unobstructed, world units/s². Default 2.4. */ maxAccel: number; /** Comfortable braking, world units/s². Default 3.2. */ comfortBrake: number; /** Bumper-to-bumper gap at a standstill, metres. Default 1.6. */ minGap: number; /** Time headway a follower keeps, seconds. Default 1.25. */ timeHeadway: number; /** Multiplier on every lane's speed limit. Default 1. */ speedScale: number; /** Free space the exit lane must show before a car is let into an intersection, metres. Default 6. */ boxClearance: number; /** Seconds after a car clears an intersection before the next may enter. Default 0.55. */ boxCooldownSeconds: number; /** Seconds a lane change takes to blend across. Default 1.2. */ laneChangeSeconds: number; /** Seconds a car must be held up by a slower leader before it looks for another lane. Default 1.8. */ laneChangeBlockedSeconds: number; /** Seconds between lane changes by one car. Default 4. */ laneChangeCooldown: number; /** Clear window a lane change needs, as a multiple of the car's own length, each side. Default 1.6. */ laneChangeGapFactor: number; /** Free stretch an entry lane must show before a car spawns on it, metres. Default 14. */ spawnGap: number; /** Fraction of the lane limit a spawned car starts at. Default 0.55. */ spawnSpeedFactor: number; /** Most cars spawned or retired in one tick. Default 3. */ fleetPerTick: number; /** * Seconds a car may stay stopped before the deadlock watch reports it. A red light or a queue is * under this; a permanent stop is not. Default 60. */ watchSeconds: number; /** Fraction of the lane limit a car may carry into a turn sharper than 45°. Default 0.55. */ turnSpeedFactor: number; } /** Counters a devtools probe, a HUD or a test reads. */ export interface TrafficStats { cars: number; parked: number; boxed: number; /** Cars holding at a stop line. */ waiting: number; /** Cars added by the demand controller. */ spawned: number; /** Cars that reached the end of the graph. */ exited: number; /** Cars the demand controller removed to shrink the fleet. */ retired: number; laneChanges: number; parks: number; /** Cars the deadlock watch has reported (stopped longer than `watchSeconds`). */ stalls: number; /** Longest single stop any car has made, seconds — a red light or a queue. */ maxStillSeconds: number; /** Longest stop a car made with clear road ahead — the deadlock measure. */ maxDeadlockSeconds: number; ticks: number; /** Slowest single `tick` seen so far, milliseconds. */ slowestTickMs: number; /** Mean speed of every moving car, world units per second. */ meanSpeed: number; } /** An intersection's signal right now: which phase is green and for how long. */ export interface TrafficSignalState { node: string; phase: number; state: "green" | "yellow" | "red"; /** Seconds this phase has left, including its yellow and all-red. */ remaining: number; /** In-lanes this green phase serves. */ lanes: readonly string[]; } /** Serializable traffic state: the fleet, the parking occupancy, the signal clocks and the demand. */ export interface TrafficSnapshot { tunables: TrafficTuning; demand: number; counter: number; rng: number | null; cars: { id: string; laneId: string; arc: number; speed: number; maxSpeed: number; desiredSpeed: number; state: "driving" | "boxed" | "parked"; cleared: boolean; nextLane: string | null; spotId: string | null; lateral: number; lateralRate: number; changeCooldown: number; blockedSeconds: number; stillSeconds: number; deadlockSeconds: number; waitingSeconds: number; stalled: boolean; variant: number; }[]; spots: { id: string; carId: string | null; }[]; intersections: { node: string; elapsed: number; cooldown: number; holder: string | null; }[]; counters: { spawned: number; exited: number; retired: number; laneChanges: number; parks: number; stalls: number; ticks: number; maxStillSeconds: number; maxDeadlockSeconds: number; }; } /** Everything {@link createTraffic} needs: the graph, the injected generator and the caller's policies. */ export interface TrafficOptions { graph: LaneGraph; /** Seed for the injected generator that varies cars. Same seed, same fleet. */ seed?: string | number; /** Inject a generator instead of seeding one; its cursor is snapshotted when it has one. */ rng?: () => number; tuning?: Partial; /** Exit choice at a junction. Omit for "least turn, never straight back". */ route?: TrafficRoutePolicy; /** Initial fleet target; the caller moves this with the time of day. Omit to keep the fleet caller-driven. */ demand?: number; /** Prefix for demand-spawned ids. Default `"traffic"`. */ idPrefix?: string; /** Register a devtools probe under this name so the perf overlay shows traffic counters. */ devtoolsName?: string; } /** * Traffic on an authored lane graph: cars follow lane spans with IDM car-following, hold at stop * lines, cross intersections under a signal phase or first-come right-of-way, change lanes, park in * authored spots, and grow or shrink with the caller's `demand`. Deterministic — cars advance in * insertion order, ties break on id, and the injected generator is the only source of variety. * Bounded per tick: one pass over the cars, one sorted pass per occupied lane, no path search. */ export interface Traffic { /** Add a car at the start of a lane. False when the id exists or the lane is unknown. */ add(id: string, spec: { laneId: string; arc?: number; speed?: number; }): boolean; remove(id: string): boolean; has(id: string): boolean; count(): number; ids(): readonly string[]; car(id: string): TrafficCarView | null; /** Visit every car in insertion order — one view record per car, no snapshot copy. */ forEachCar(visit: (car: Readonly) => void): void; /** Advance the whole fleet by `dt` seconds. */ tick(dt: number): void; retune(patch: Partial): void; /** * Fleet size the demand controller drives towards — a time-of-day count the caller computes, never a * built-in curve. `-1` turns the controller off and leaves the fleet entirely caller-driven. */ setDemand(count: number): void; /** Current fleet target, or `-1` when the fleet is caller-driven. */ demand(): number; /** Send a car to a parking spot on the lane it is already on. False when the spot is taken. */ park(id: string, spotId?: string): boolean; /** Give up a parked spot and rejoin the lane. */ releaseParking(id: string): boolean; /** Id of the car holding or reserving a spot, or null. */ spotOwner(spotId: string): string | null; signal(nodeId: string): TrafficSignalState | null; stats(): TrafficStats; snapshot(): TrafficSnapshot; restore(next: TrafficSnapshot): void; /** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */ dispose(): void; } /** * Traffic for an authored lane graph (see {@link createLaneGraph}). * * @capability traffic lane-graph traffic with car following, signals, right-of-way, lane changes and parking */ export declare function createTraffic(options: TrafficOptions): Traffic;