import { type CollisionObstacle } from "../movement/movementModel.js"; import type { KinematicVehicleOptions } from "./kinematicVehicle.js"; /** * A single blocked-move event a car can react to (#1051): crash damage, screen shake, a metal-crunch * cue. Reported once, when a move is first vetoed, then consumed by {@link VehicleObstacleClamp.takeImpact}. */ export interface VehicleImpact { /** Speed (world units/s) the car was carrying *into* the obstacle at the moment the move was blocked. */ closingSpeed: number; /** Unit push-back direction — points from the struck surface back toward the car. */ normal: readonly [number, number]; /** Index into the obstacle set passed for the blocking tick. */ obstacleIndex: number; } /** * A planar move clamp for a kinematic car: feed {@link clampMove} to a vehicle's `clampMove` hook so * an attempted XZ displacement slides along world solids instead of driving through them, and read * {@link takeImpact} once per tick for the crash it produced. */ export interface VehicleObstacleClamp { /** Resolve an attempted XZ move; recovery endpoints carry permitted world-unit displacement in `motion`. */ clampMove: NonNullable; /** Impact recorded since last take, consumed on read (null when none). Closing speed is the velocity component into the obstacle at block time, units/s. */ takeImpact(): VehicleImpact | null; } /** * Build a slide-along move clamp for a kinematic car (#1051). `obstacles` is sampled fresh each tick — * the caller hands back the already-filtered set of solids near the car — and `dt` supplies the current * tick length so a blocked move's lost displacement converts to a closing speed. `radius` inflates each * obstacle footprint by the car's body radius (default {@link DEFAULT_VEHICLE_RADIUS}). * * The slide itself is delegated to {@link resolveObstacleStep} so cars and pedestrians share one proven * axis-separated resolver: a shallow graze keeps its tangential run and only the into-wall component is * shed, a head-on zeroes forward progress. When any component is shed the largest such block this tick is * kept as a {@link VehicleImpact}; {@link VehicleObstacleClamp.takeImpact} returns and clears it so a hit * fires exactly once. The happy path (no solids near) is a zero-allocation passthrough returning `to`. */ export declare function createVehicleObstacleClamp(options: { /** Solids near the car this tick (already filtered to the relevant, solid set). */ obstacles: () => readonly CollisionObstacle[]; /** Vehicle body radius (units). Default {@link DEFAULT_VEHICLE_RADIUS}. */ radius?: number; /** Current tick dt (seconds) — the divisor turning shed displacement into closing speed. */ dt: () => number; }): VehicleObstacleClamp;