import { type GravityField } from "./gravityField.js"; /** Body-frame or world-frame vector, m or N. The body frame is `[left, up, forward]` about the centre of mass. */ export type AircraftVector = readonly [number, number, number]; /** Unit quaternion `[x, y, z, w]` taking body-frame vectors to world space. */ export type AircraftQuaternion = readonly [number, number, number, number]; /** * One lifting surface: a wing panel, a tailplane, a fin, a canard. Its lift acts at `at`, so the moments that pitch, * roll and yaw the body come from where the surfaces sit, not from commanded rates. */ export interface AircraftSurface { /** Mount point relative to the centre of mass, body frame `[left, up, forward]`, m. */ at: AircraftVector; /** Direction positive lift pushes, body frame (default `[0, 1, 0]`, a horizontal surface; `[1, 0, 0]` is a fin). Kept perpendicular to the chord, which runs along body forward. */ normal?: AircraftVector; /** Planform area, m². */ area: number; /** Lift curve slope, per rad (default `5`; thin-airfoil theory gives `2π`, lower for short spans). */ liftSlope?: number; /** Angle of attack where the surface stalls, rad (default `0.28`). */ stallAngle?: number; /** Lift kept past the stall as a share of peak lift, `0..1` (default `0.6`). */ postStallLift?: number; /** Fixed mounting angle added to the local angle of attack, rad (default `0`); trims the cruise attitude. */ incidence?: number; /** Zero-lift drag coefficient `CD0` of the drag polar `CD = CD0 + k·CL²` (default `0.02`). */ cd0?: number; /** Induced drag factor `k` of the drag polar (default `0.06`). */ inducedDrag?: number; /** * Control authority: angle-of-attack change, rad, per rad of each channel's deflection. The sim signs it from the * surface's lever arm, so positive input always gives nose-up pitch, right roll and nose-right yaw. A surface on the * centreline has no roll arm, so its `roll` does nothing. */ control?: { pitch?: number; roll?: number; yaw?: number; }; } /** One control channel's actuator: how far the surfaces move and how fast. */ export interface AircraftControlChannel { /** Largest deflection, rad (default `0.35`). */ maxDeflection?: number; /** Actuator slew rate, rad/s (default `2`). */ rate?: number; } /** A thrust source fixed to the body. */ export interface AircraftEngineTuning { /** Thrust at full throttle, N. */ maxThrust: number; /** Engine spool response, 1/s: thrust follows the throttle with this first-order rate (default `1.5`; ~1.5 s to 90%). */ spoolRate?: number; /** Thrust line offset from the centre of mass, body frame m (default on the centre of mass). */ at?: AircraftVector; /** Thrust direction, body frame (default `[0, 0, 1]`, forward). */ direction?: AircraftVector; } /** Wheels or skids: what the body rests on when it touches the ground. */ export interface AircraftGearTuning { /** Height of the centre of mass above the ground when resting on the gear, m. */ height: number; /** Rolling resistance coefficient (default `0.02`). */ rollingResistance?: number; /** Deceleration at full `brake`, m/s² (default `4`). */ brakeDecel?: number; /** Sideways friction coefficient of the tyres or skids (default `0.8`). */ sideGrip?: number; /** Largest nose-up attitude the gear allows before the tail strikes, rad (default `0.25`). */ maxPitch?: number; } /** * A rocket motor: thrust follows a curve over burn time, propellant leaves at a mass flow so the body gets lighter and * easier to turn, and the nozzle gimbals on the pitch and yaw channels. Stage by `retune`-ing to the next stage's * mass and motor. */ export interface AircraftMotorTuning { /** Thrust over burn time, `[seconds since ignition, N]` points, linear between; zero past the last point. */ thrustCurve: readonly (readonly [number, number])[]; /** Propellant at ignition, kg, on top of `massKg`. */ propellantKg: number; /** Propellant mass flow at the curve's peak thrust, kg/s; it scales with thrust (constant exhaust velocity). */ massFlow: number; /** Nozzle position relative to the centre of mass, body frame m (default `[0, 0, -1]`, behind it). */ at?: AircraftVector; /** Largest nozzle gimbal angle, rad (default `0`, fixed). Pitch and yaw inputs swing it through their actuators. */ gimbal?: number; } /** * A main rotor. Collective sets blade pitch, throttle sets rotor speed, and cyclic (the pitch and roll channels) tilts * the disc. Its drag torque yaws the body the other way unless a tail rotor, a second rotor or a pedal input cancels it. */ export interface AircraftRotorTuning { /** Thrust at full collective and full rotor speed, out of ground effect and in a hover, N. */ maxThrust: number; /** Blade radius, m. Sets the disc area, so the induced velocity behind translational lift, and the ground-effect height. */ radius: number; /** Hub position relative to the centre of mass, body frame m (default `[0, 1.5, 0]`). A hub above the centre of mass turns disc tilt into pitch and roll moments. */ at?: AircraftVector; /** Rotor speed response to throttle, 1/s (default `0.5`): rotors spin up slowly. */ spoolRate?: number; /** * Drag torque on the body at full collective and full rotor speed, N·m, scaling with collective and rotor speed². * Positive yaws the nose left (a rotor turning counter-clockwise seen from above); negative yaws it right. */ torque: number; /** Disc tilt at full cyclic, rad (default `0.15`); the pitch and roll actuators set how fast it gets there. */ cyclic?: number; /** Extra thrust once the rotor moves into clean air, as a share of thrust (default `0.2`): translational lift. */ translationalLift?: number; /** Pitch and roll damping from the disc, N·m per rad/s (default `0`). */ damping?: number; /** * Tail rotor: sideways thrust at full yaw input and full rotor speed, N, acting at `at` (body frame m). Neutral pedal * gives no thrust, so holding heading takes a yaw input that matches the main rotor torque. `sideDamping`, N per m/s * of sideways air through it (default `maxThrust / 10`), is the yaw damping a spinning tail rotor adds. */ tail?: { maxThrust: number; at: AircraftVector; sideDamping?: number; }; } /** What an assist sees each substep, derived from the integrator state so it replays deterministically. */ export interface AircraftAssistContext { /** Substep, s. */ dt: number; airspeed: number; angleOfAttack: number; heading: number; pitch: number; bank: number; /** Body rates, rad/s: positive nose up, nose right, right roll. */ pitchRate: number; yawRate: number; rollRate: number; /** World velocity, m/s. */ velocity: AircraftVector; /** Horizontal velocity in the heading frame, m/s: `forward` along the nose, `right` to the right. */ forward: number; right: number; /** Load factor estimate from pitch rate and attitude, g. */ gLoad: number; heightAboveGround: number; /** The pilot's input this tick. */ pilot: RigidAircraftInput; } /** Control commands, `-1..1`, that drive the actuators after the assists. */ export interface AircraftAssistCommand { pitch: number; roll: number; yaw: number; } /** * Flight assists. Each one moves the same actuators the pilot does, so a stronger assist still flies within the * airframe's authority. Strengths are `0..1`; an axis the pilot is deflecting gets less help the further the stick is * pushed, so full stick is always the pilot's. */ export interface AircraftAssistTuning { /** * Stability augmentation per axis, `0..1`: opposes body rate and, while that axis is hands-off, holds the attitude * or heading it had when the pilot let go, learning the trim it needs. On a helicopter, `yaw` holds heading against * rotor torque. Where `autoLevel` or `hoverHold` flies pitch and roll, SAS only damps their rates. */ sas?: { pitch?: number; roll?: number; yaw?: number; }; /** Body rate at which SAS asks for full control, rad/s (default `0.5`); lower is firmer. */ sasRate?: number; /** Hands-off, flies the wings level and the nose to the horizon, `0..1`. */ autoLevel?: number; /** Largest angle of attack the pitch command may ask for, rad. */ maxAngleOfAttack?: number; /** Largest load factor the pitch command may ask for, g. */ maxG?: number; /** Hands-off cyclic flies the helicopter to a stop over the ground, `0..1`: velocity to attitude to rate. */ hoverHold?: number; /** Runs after the built-in assists and returns the command the actuators get; replace or blend any policy here. */ policy?: (context: AircraftAssistContext, command: AircraftAssistCommand) => AircraftAssistCommand; } /** * A rigid aircraft in physical units. There is no aircraft type: a jet, a glider and a paper plane differ only in these * numbers. Rotation comes from surface forces acting on the inertia tensor, so loops, rolls, stalls and weathervaning * are outcomes, not special cases. */ export interface RigidAircraftTuning { /** Mass, kg; with a `motor`, the mass without propellant. */ massKg: number; /** Principal moments of inertia about the body axes at `massKg`, kg·m²: `pitch` about left, `yaw` about up, `roll` about forward. Propellant scales them by total mass over `massKg`. */ inertia: { pitch: number; yaw: number; roll: number; }; surfaces: readonly AircraftSurface[]; engine?: AircraftEngineTuning; rotor?: AircraftRotorTuning; motor?: AircraftMotorTuning; assists?: AircraftAssistTuning; /** Actuators per channel; each channel defaults to `{ maxDeflection: 0.35, rate: 2 }`. */ controls?: { pitch?: AircraftControlChannel; roll?: AircraftControlChannel; yaw?: AircraftControlChannel; }; /** Fuselage drag coefficient × frontal area, m² (default `0`). */ dragArea?: number; /** Omit for a body that never touches the ground (it falls through). */ gear?: AircraftGearTuning; /** Integration substep, s (default `1/240`). */ maxSubstep?: number; } /** Pilot input for one tick. */ export interface RigidAircraftInput { /** `0..1`. */ throttle: number; /** `-1..1`, positive pulls the nose up. */ pitch: number; /** `-1..1`, positive rolls right (right wing down). */ roll: number; /** `-1..1`, positive yaws the nose right. */ yaw: number; /** Wheel brake, `0..1`. */ brake?: number; /** Rotor collective, `0..1` (default `0`). */ collective?: number; } /** Per-tick overrides layered over tuning: damage, icing, boost, gusts. Each scale defaults to `1`. */ export interface RigidAircraftModifiers { thrustScale?: number; liftScale?: number; dragScale?: number; /** Extra world-space force this tick, N — a tow, a gust, a tractor beam. */ force?: AircraftVector; /** Extra body-frame torque this tick, N·m `[about left, about up, about forward]`. */ torque?: AircraftVector; } /** World hooks for one aircraft instance. */ export interface RigidAircraftOptions { position?: AircraftVector; velocity?: AircraftVector; /** Spawn heading, rad, with forward `[sin h, 0, cos h]`; ignored when `orientation` is set. */ heading?: number; orientation?: AircraftQuaternion; /** Wind velocity at a point, m/s. */ wind?: (position: AircraftVector) => AircraftVector; /** Terrain height under a point; the gear rests on it. Default `0`. */ groundHeight?: (x: number, z: number) => number; /** Default uniform `9.81` m/s² down. */ gravityField?: GravityField; /** Air density at an altitude, kg/m³ (default sea level, `1.225`). */ airDensity?: (altitude: number) => number; } /** Serializable integrator state: everything `restore` needs to resume bit-for-bit. */ export interface RigidAircraftState { x: number; y: number; z: number; vx: number; vy: number; vz: number; qx: number; qy: number; qz: number; qw: number; /** Angular velocity, body frame rad/s. */ wx: number; wy: number; wz: number; pitchDeflection: number; rollDeflection: number; yawDeflection: number; /** Engine output as a share of `maxThrust`, `0..1`. */ spool: number; /** Rotor speed as a share of its rated speed, `0..1`. */ rotorSpeed: number; /** Seconds the motor has burned. */ burnTime: number; /** Propellant left, kg. */ propellantKg: number; /** SAS hold per axis: body-angle change since that axis went hands-off, rad. */ holdPitch: number; holdRoll: number; holdYaw: number; /** SAS trim per axis: the steady command it learned to hold against (rotor torque, an out-of-trim wing), rad/s. */ trimPitch: number; trimRoll: number; trimYaw: number; /** Hover hold: world drift since the cyclic went hands-off, m. */ driftX: number; driftZ: number; time: number; } /** One aircraft tick: pose plus the telemetry HUDs, camera, sound and probes read. */ export interface RigidAircraftStep { position: AircraftVector; velocity: AircraftVector; orientation: AircraftQuaternion; /** Heading, rad, forward `[sin h, 0, cos h]`; equals three.js `rotation.y` in `YXZ` order. */ heading: number; /** Nose-up attitude, rad; three.js `rotation.x = -pitch` in `YXZ` order. */ pitch: number; /** Bank, rad, positive right wing down; three.js `rotation.z` in `YXZ` order. */ bank: number; /** Body rates, rad/s: positive nose up, nose right, right roll. */ pitchRate: number; yawRate: number; rollRate: number; /** Speed relative to the air, m/s. */ airspeed: number; /** Angle of attack at the centre of mass, rad. */ angleOfAttack: number; /** Sideslip, rad, positive when the body moves to its right. */ sideslip: number; /** Load factor along the body's up axis, g; `1` in level flight, `0` in free fall. */ gLoad: number; /** The largest surface is past its stall angle. */ stalled: boolean; /** Share of surface area past its stall angle, `0..1`. */ stallFraction: number; /** Engine thrust this tick, N. */ thrust: number; /** Current actuator deflection per channel, rad. */ deflection: { pitch: number; roll: number; yaw: number; }; grounded: boolean; /** Total mass including propellant, kg. */ massKg: number; /** The command the actuators got after assists, `-1..1`; equals the pilot's input without assists. */ command: AircraftAssistCommand; /** The AoA or g limiter cut the pitch command this tick. */ limited: boolean; /** Motor telemetry; absent without a `motor` block. */ motor?: { /** Motor thrust this tick, N. */ thrust: number; burnTime: number; propellantKg: number; /** Out of propellant or past the end of the thrust curve. */ burnedOut: boolean; }; /** Rotor telemetry; absent without a `rotor` block. */ rotor?: { /** Rotor speed, share of rated, `0..1`. */ speed: number; /** Main rotor thrust, N. */ thrust: number; /** Drag torque on the body, N·m, positive nose left. */ torque: number; /** Thrust multiplier from ground effect, `≥ 1`. */ groundEffect: number; /** Thrust multiplier from translational lift, `≥ 1`. */ translationalLift: number; }; } /** A force-and-torque aircraft on the same tick/snapshot/retune contract as `VehicleDynamics`. */ export interface RigidAircraft { tick(dt: number, input: RigidAircraftInput, modifiers?: RigidAircraftModifiers): RigidAircraftStep; pose(): { position: AircraftVector; orientation: AircraftQuaternion; }; velocity(): AircraftVector; /** Adds a world-space velocity change, m/s — a hit, a catapult, an explosion. */ applyImpulse(dv: AircraftVector): void; retune(next: RigidAircraftTuning): void; tuning(): RigidAircraftTuning; snapshot(): RigidAircraftState; restore(state: RigidAircraftState): void; resetTo(position: AircraftVector, orientation?: AircraftQuaternion): void; } /** Quaternion for a heading about world up, forward `[sin h, 0, cos h]`. */ export declare function aircraftHeadingQuaternion(heading: number): AircraftQuaternion; /** * Quaternion from heading, nose-up pitch and right bank, rad — the inverse of a step's `heading`/`pitch`/`bank`. * Matches three.js `Euler(-pitch, heading, bank, "YXZ")`. */ export declare function aircraftAttitudeQuaternion(heading: number, pitch: number, bank: number): AircraftQuaternion; /** * Creates a {@link RigidAircraft}: a quaternion rigid body with a diagonal inertia tensor, pushed by lifting surfaces, * fuselage drag, an engine and gravity. Controls deflect surfaces through rate-limited actuators; nothing commands a * rotation rate, so a jet loops when its tail can push the nose around and a glider stalls when it runs out of speed. * Fixed internal substeps make it deterministic for a given `dt` sequence. * @capability rigid-aircraft force-and-torque aircraft — lifting surfaces with stall and drag polar, control surfaces, inertia, loops and rolls */ export declare function createRigidAircraft(initial: RigidAircraftTuning, options?: RigidAircraftOptions): RigidAircraft;