import type { AxisInput } from "../input/axisInput.js"; import type { AircraftVector, RigidAircraft } from "./aircraftDynamics.js"; /** The slice of a vehicle sim {@link measureHandling} drives: `VehicleDynamics` and `KinematicVehicle` both fit. */ export interface HandlingSubject { tick(dt: number, input: AxisInput): { forwardSpeed: number; lateralSpeed: number; yawRate: number; }; velocity(): readonly [number, number]; } /** Scenario settings for {@link measureHandling}; every field has a default. */ export interface HandlingProbeOptions { /** Fixed tick, s (default `1/60`). */ dt?: number; /** Entry speed for the step-steer, lift-off and handbrake tests, m/s (default `25`). */ cornerSpeed?: number; /** Steer input for the step-steer test (default `1`: a held keyboard key, the harshest common case). */ stepSteer?: number; /** Steer input held through the lift-off test (default `0.3`). */ liftOffSteer?: number; } /** Deterministic feel metrics, in the units drivers and reviewers use. `Infinity` means the target was never reached. */ export interface HandlingReport { /** Standing start to 100 km/h, s. */ zeroTo100: number; /** Speed after 40 s of full throttle, m/s. */ topSpeed: number; /** Full brake from 100 km/h to rest, m. */ brake100To0: number; /** Peak lateral acceleration (perpendicular to travel) on a slow steer ramp at 20 m/s while sideslip stays under 12°, g. */ maxLateralG: number; /** Seconds from a step steer until yaw rate first reaches 90% of its peak — turn-in latency. */ turnIn: number; /** Peak |sideslip| during the step steer, degrees. */ stepPeakSideslipDeg: number; /** Yaw rate at the end of the 3 s step steer, rad/s. */ stepSteadyYawRate: number; /** Share of entry speed left after the 3 s step steer. */ stepSpeedRetained: number; /** The step steer ended with sideslip over 60°: the car spun. */ spun: boolean; /** Yaw rate 0.5 s after lifting off mid-corner divided by the yaw rate before: `>1` tucks in, `<1` pushes wide. */ liftOffYawGain: number; /** Peak |sideslip| from a 0.6 s steer-plus-handbrake pull, degrees. */ handbrakePeakSideslipDeg: number; /** Seconds after releasing that pull, with no input, until sideslip stays under 5° for 0.3 s; `Infinity` if it never does within 3 s. */ handbrakeRecoverySeconds: number; /** Peak |sideslip| holding full throttle and full steer for 2 s from 12 m/s — the keyboard player's corner, degrees. */ powerSteerPeakSideslipDeg: number; /** The full-throttle full-steer hold ended with sideslip over 60°. */ powerSteerSpun: boolean; } /** * Drives fresh instances from `create` through fixed scenarios (launch, top speed, braking, a slow steer * ramp, a step steer, a mid-corner lift-off, a handbrake pull, full throttle with full steer) and reports the feel metrics a test can * assert. Deterministic: the same subject and options always produce the same report, so a tuning change * shows up as a number moving, not an opinion. * @capability handling-metrics measure a vehicle's feel as numbers — 0–100, braking, lateral g, turn-in, sideslip, spin */ export declare function measureHandling(create: () => HandlingSubject, options?: HandlingProbeOptions): HandlingReport; /** The slice of a sprung vehicle sim {@link measureRide} drives; a `VehicleDynamics` with `suspension` fits. */ export interface RideSubject { tick(dt: number, input: AxisInput): { position: readonly [number, number, number]; forwardSpeed: number; bodyPitch: number; bodyRoll: number; lateralAccel: number; airborne: boolean; }; applyImpulse(dvx: number, dvz: number, dvy?: number): void; } /** Deterministic ride metrics for a sprung vehicle. */ export interface RideReport { /** Seconds after a 1 m/s upward kick until the body stays within 5 mm of its rest height. */ settleSeconds: number; /** Largest drop below rest height after that kick, divided by the largest rise above it. */ heaveOvershoot: number; /** Peak nose-down pitch under full braking from 25 m/s, degrees. */ brakeDiveDeg: number; /** Body roll per g of lateral acceleration in a steady half-lock corner at 20 m/s, degrees/g. */ rollGradient: number; /** Times the car leaves the ground again after landing from a 4 m/s vertical launch. */ landingBounces: number; } /** * Drives fresh sprung vehicles from `create` through a vertical kick, a hard stop, a steady corner and a * launch-and-land, and reports how the body moves: how fast it settles, how much it dives and rolls, and * whether a landing bounces. * @capability ride-metrics measure a sprung vehicle's ride — settle time, dive, roll per g, landing bounce */ export declare function measureRide(create: () => RideSubject, options?: { dt?: number; }): RideReport; /** The slice of a jumping vehicle sim {@link measureAir} drives; a `VehicleDynamics` with `suspension`, `jump` and `air` fits. */ export interface AirSubject { tick(dt: number, input: AxisInput, modifiers?: { air?: { pitch: number; yaw: number; roll: number; }; }): { position: readonly [number, number, number]; airborne: boolean; }; jump(): boolean; snapshot(): { pitchRate: number; yawRate: number; rollRate: number; }; } /** Deterministic jump and air-control metrics. */ export interface AirReport { /** Peak height above the resting height after one standing jump, m. */ jumpApexHeight: number; /** Seconds from the jump to that peak. */ jumpApexTime: number; /** Peak height with a second jump pressed at the first jump's apex, m (equals `jumpApexHeight` without a double jump). */ doubleJumpApexHeight: number; /** Angular rate reached after 0.4 s of full input in the air, rad/s, per axis. */ airPitchRate: number; airYawRate: number; airRollRate: number; } /** * Jumps a fresh vehicle from rest and holds full air input on each axis, reporting jump height and timing, * double-jump height, and how fast the body rotates in the air. * @capability air-metrics measure a vehicle's jumps and air control — apex height and time, double jump, air rotation rates */ export declare function measureAir(create: () => AirSubject, options?: { dt?: number; }): AirReport; /** A vehicle sim {@link measureCourse} can drive along a line: a {@link HandlingSubject} that also reports its pose and front-wheel angle. */ export interface CourseSubject { tick(dt: number, input: AxisInput): { forwardSpeed: number; lateralSpeed: number; yawRate: number; steerAngle: number; }; velocity(): readonly [number, number]; pose(): { position: readonly [number, number, number]; heading: number; }; } /** Settings for {@link measureCourse}; every field but `wheelbase` has a default. */ export interface CourseProbeOptions { /** Wheelbase, m — needed to separate geometric steer from understeer. */ wheelbase: number; dt?: number; /** Skidpad radius, m (default `30`). */ skidpadRadius?: number; /** Distance between slalom cones, m (default `18`). */ slalomSpacing?: number; /** How far the weaving line swings either side of the cones, m (default `3.5`). */ slalomOffset?: number; } /** Deterministic driven-course metrics. */ export interface CourseReport { /** * Understeer gradient, degrees of extra front-wheel angle per g of lateral acceleration, from a slow steer * ramp at 20 m/s between 0.1 and 0.4 g. Positive understeers, negative oversteers, near 0 is neutral. */ understeerGradient: number; /** Highest lateral acceleration held on a circle of `skidpadRadius`, g. */ skidpadG: number; /** * Fastest entry speed at which a pure-pursuit driver weaves 8 centre-line cones, passing each on the * correct side with at least 1.2 m of clearance and without sliding past 30°, m/s (`0` if none). */ slalomSpeed: number; } /** * Drives a fresh vehicle from `create` through a steer ramp, a skidpad and a slalom with simple * deterministic drivers, and reports understeer gradient, skidpad g and the fastest clean slalom. * @capability course-metrics measure a vehicle on driven tests — understeer gradient, skidpad g, slalom speed */ export declare function measureCourse(create: () => CourseSubject, options: CourseProbeOptions): CourseReport; /** The slice of a single-track sim {@link measureLean} drives; a `VehicleDynamics` with a `lean` block fits. */ export interface LeanSubject { tick(dt: number, input: AxisInput): { forwardSpeed: number; lean: number; }; } /** Deterministic lean metrics for a motorcycle or bicycle. */ export interface LeanReport { /** Lean held after 2 s of full steer at `speed`, degrees. */ steadyLeanDeg: number; /** Seconds from full steer until the lean first reaches 90% of that steady lean. */ timeToLean: number; /** Largest lean the wrong way in the first 0.4 s — the countersteer tip-in, degrees (`0` if none). */ counterLeanDeg: number; } /** * Brings a fresh bike to `speed` and holds full steer, reporting how far and how fast it leans and how much it * tips the other way first. * @capability lean-metrics measure a motorcycle's lean — steady lean, time to lean, countersteer tip-in */ export declare function measureLean(create: () => LeanSubject, options?: { dt?: number; speed?: number; }): LeanReport; /** Where {@link measureFlight} spawns each fresh aircraft: level, heading `0` (forward `+z`). */ export interface FlightSpawn { position: AircraftVector; velocity: AircraftVector; } /** Scenario settings for {@link measureFlight}. */ export interface FlightProbeOptions { /** Fixed tick, s (default `1/60`). */ dt?: number; /** Entry airspeed for the fixed-wing tests, m/s (default `60`). */ cruiseSpeed?: number; /** Spawn altitude, m (default `1000`, or `50` for a rotorcraft). */ altitude?: number; /** Collective that hovers a rotorcraft (default weight over rotor `maxThrust`). */ hoverCollective?: number; } /** * Deterministic flight metrics. Fields that don't apply to the aircraft are `NaN`: turn and stall need wings, * hover drift needs a rotor. */ export interface FlightReport { /** Peak roll rate under full roll input from cruise (or a hover), deg/s. */ rollRateDeg: number; /** Best turn rate held at full throttle without losing altitude or speed, deg/s. */ sustainedTurnRateDeg: number; /** Slowest airspeed that still holds altitude at idle, wings level, m/s. */ stallSpeed: number; /** Steady climb at full throttle holding cruise speed (full collective from a hover for a rotorcraft), m/s. */ climbRate: number; /** Seconds from idle to 90% of full thrust after the throttle (and collective) go to full. */ throttleResponse: number; /** Horizontal drift after 10 s hands-off at hover collective, m. */ hoverDriftMeters: number; } /** * Flies fresh aircraft from `create` through fixed scenarios with simple deterministic autopilots: a full-aileron * roll, banked turns at full throttle, an idle deceleration holding altitude, a full-throttle climb at cruise * speed, a throttle step from idle, and a hands-off hover. `create` gets a spawn to pass straight into * `createRigidAircraft`. Deterministic, so a tuning change shows up as a number moving. * @capability flight-metrics measure an aircraft's feel as numbers — roll rate, sustained turn, stall speed, climb, throttle response, hover drift */ export declare function measureFlight(create: (spawn: FlightSpawn) => RigidAircraft, options?: FlightProbeOptions): FlightReport;