import type { AxisInput } from "../input/axisInput.js"; import { type DifficultyProfile } from "./difficulty.js"; /** * A difficulty-aware driver brain: pose + goal in, the shared {@link AxisInput} * (`throttle`/`brake`/`steer`/`handbrake`) out, ready for `tickDrivableVehicle` — the chase/traffic * steering loop every game with AI cars hand-rolls as `steer = error * k, throttle = 1`. A * {@link DifficultyProfile} decides how *well* it drives: reaction time trails the target, corners * shed speed instead of orbiting at full throttle, obstacles are noticed late on low tiers, the * wheel wobbles by `executionJitter`, and a stuck car backs out with counter-steer after grinding * for a tier-scaled while. State is one small serializable object; randomness is injected; every * step is allocation-light scalar math. The caller owns the sim, collision, and all spatial * queries — this module never scans the world. */ /** The driver's own vehicle this step: world position, yaw heading, signed forward speed. */ export interface DriverPose { x: number; z: number; /** Yaw with the same convention as the kinematic vehicle sim: `atan2(dx, dz)`. */ heading: number; /** Signed forward speed in world units/second (negative while reversing). */ speed: number; } /** Where the driver is trying to go this step. */ export interface DriverGoal { x: number; z: number; /** Brake to a halt inside `arriveRadius` instead of driving through; default `false`. */ stopAtGoal?: boolean; /** Cap on desired speed for this goal (a speed limit, a formation speed); default unlimited. */ speedLimit?: number; } /** Vehicle-feel numbers the game supplies; everything has a sensible default except `maxSpeed`. */ export interface DriverTuning { /** Top desired speed on a straight, world units/second. */ maxSpeed: number; /** Steering-output gain per radian of heading error; default `2`. */ steerGain?: number; /** Fraction of speed shed at a right-angle heading error (`0` never slows for corners); default `0.75`. */ cornerSlowdown?: number; /** Distance at which the goal counts as reached; default `4`. */ arriveRadius?: number; /** Meters of clearance the driver tries to keep to an obstacle ahead; default `6`. */ brakeMargin?: number; /** Seconds between steering-jitter resamples, so low tiers wobble instead of noise-shake; default `0.35`. */ jitterIntervalSeconds?: number; /** Heading-error radians one full unit of `executionJitter` adds; default `0.35`. */ jitterScaleRadians?: number; /** Below this |speed| while throttling the car counts as stuck; default `0.8`. */ stuckSpeed?: number; /** Seconds of grinding (plus the profile's `reactionSeconds`) before reversing out; default `1.5`. */ stuckSeconds?: number; /** How long a reverse-out lasts; default `1.1`. */ reverseSeconds?: number; } /** Serializable per-driver state advanced in place by {@link driveStep}. */ export interface DriverState { /** The reaction-committed target actually steered toward (the goal as of up to `reactionSeconds` ago). */ targetX: number; targetZ: number; /** Seconds until the next target resample. */ sampleCooldown: number; /** Current steering-wobble offset (radians of heading error) and time until its next resample. */ jitter: number; jitterCooldown: number; /** Seconds spent throttling without moving. */ grindSeconds: number; /** Seconds of reverse-out remaining; `> 0` means currently backing up. */ reverseRemaining: number; } /** * Fresh driver state committed to an initial target (usually the spawn-time goal or the spawn * position itself). * * @capability ai-driver-state fresh serializable state for one difficulty-aware AI driver */ export declare function createDriverState(targetX?: number, targetZ?: number): DriverState; /** {@link driveStep}'s result: the axis sample to feed the vehicle sim, plus arrival. */ export interface DriverStep extends AxisInput { /** The vehicle is inside `arriveRadius` of the *actual* goal this step. */ arrived: boolean; } /** * Advance one driving step, mutating `state`, and return the {@link AxisInput} to feed the vehicle * sim. `obstacleAhead` is the caller-measured distance to the nearest blocker in the driving * corridor this step (`null`/omitted when clear) — the driver perceives it late by * `speed * reactionSeconds`, so easy tiers brake at the last moment and expert tiers with proper * margin. The steering target is a delay-line over the goal — resampled every `reactionSeconds`, * so it trails a moving goal by up to that long, which is what makes an easy chaser visibly cut * toward where the target *was*. When throttle produces no motion for a tier-scaled while the * driver reverses out with counter-steer (brake past standstill — the shared sim's reverse). * * @capability ai-driver difficulty-aware chase/route driving step producing throttle/brake/steer for the vehicle sim */ export declare function driveStep(state: DriverState, dt: number, pose: DriverPose, goal: DriverGoal, profile: DifficultyProfile, tuning: DriverTuning, rng: () => number, obstacleAhead?: number | null): DriverStep; /** A 2D polyline point (`[x, z]`), structurally compatible with authored road path centerlines. */ export type DrivePathPoint = readonly [number, number]; /** * The pure-pursuit target for street/route following: the point `lookahead` meters further along * the polyline from wherever the vehicle currently projects onto it. Feed the result to * {@link driveStep} as the goal each tick and the driver follows the road; `loop` wraps a circuit * (traffic block, race lap) instead of pinning at the final point. Returns `null` for a * degenerate path. Give sharper tiers a longer `lookahead` to cut corners cleanly and duller * tiers a short one so they steer segment-to-segment. * * @capability ai-driver-path-target pure-pursuit lookahead target on a road/route polyline for street following */ export declare function pathTargetAhead(path: readonly DrivePathPoint[], x: number, z: number, lookahead: number, loop?: boolean): DrivePathPoint | null;