diff --git a/shared/atc/phases/approach.ts b/shared/atc/phases/approach.ts new file mode 100644 index 0000000..b67e6fa --- /dev/null +++ b/shared/atc/phases/approach.ts @@ -0,0 +1,67 @@ +import type { Phase } from '../types' + +export const approachPhase: Phase = { + id: 'approach', + name: 'Approach Control', + frequency: '119.100', + unit: 'APP', + nextPhase: 'landing', + interactions: [ + { + id: 'contact_approach', + type: 'pilot_initiates', + pilotIntent: 'Pilot makes initial contact with approach control', + pilotExample: '{callsign}, descending flight level {flight_level}, information {atis_code}', + atcResponse: '{callsign}, radar contact. Expect {approach_type} approach runway {arrival_runway}.', + }, + { + id: 'descend_instruction', + type: 'atc_initiates', + pilotIntent: 'ATC instructs pilot to descend to an assigned altitude', + atcResponse: '{callsign}, descend altitude {assigned_alt} feet, QNH {qnh}.', + readback: { + required: ['assigned_alt', 'qnh'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, descend altitude {assigned_alt} feet, QNH {qnh}.', + }, + }, + { + id: 'cleared_approach', + type: 'atc_initiates', + pilotIntent: 'ATC clears pilot for the instrument approach', + atcResponse: '{callsign}, cleared {approach_type} approach runway {arrival_runway}.', + readback: { + required: ['approach_type', 'arrival_runway'], + atcConfirm: 'Correct. Contact tower on {tower_freq}.', + atcCorrect: 'Negative, cleared {approach_type} approach runway {arrival_runway}.', + }, + handoff: { toPhase: 'landing', say: 'Contact tower on {tower_freq}.' }, + }, + { + id: 'request_alternate_approach', + type: 'pilot_initiates', + pilotIntent: 'Pilot requests a different approach type than assigned', + pilotExample: '{callsign}, request {requested_approach} approach runway {arrival_runway}', + atcResponse: '{callsign}, approved, expect {requested_approach} approach runway {arrival_runway}.', + updates: { approach_type: '{requested_approach}' }, + }, + { + id: 'go_around', + type: 'pilot_initiates', + pilotIntent: 'Pilot executes a go-around and reports to approach', + pilotExample: '{callsign}, going around', + atcResponse: '{callsign}, roger, climb altitude {initial_alt} feet, fly runway heading, contact approach on {approach_freq}.', + }, + { + id: 'speed_instruction', + type: 'atc_initiates', + pilotIntent: 'ATC instructs pilot to reduce or maintain a specific speed', + atcResponse: '{callsign}, reduce speed {assigned_speed} knots.', + readback: { + required: ['assigned_speed'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, reduce speed {assigned_speed} knots.', + }, + }, + ], +} diff --git a/shared/atc/phases/clearance.ts b/shared/atc/phases/clearance.ts new file mode 100644 index 0000000..68301da --- /dev/null +++ b/shared/atc/phases/clearance.ts @@ -0,0 +1,32 @@ +import type { Phase } from '../types' + +export const clearancePhase: Phase = { + id: 'clearance', + name: 'Clearance Delivery', + frequency: '121.900', + unit: 'DEL', + nextPhase: 'ground', + interactions: [ + { + id: 'request_clearance', + type: 'pilot_initiates', + pilotIntent: 'Pilot requests IFR clearance to destination', + pilotExample: '{callsign}, request IFR clearance to {dest}, information {atis_code}', + atcResponse: '{callsign}, cleared to {dest} via {sid} departure, runway {runway}, climb initially {initial_alt} feet, expect flight level {flight_level}, squawk {squawk}', + readback: { + required: ['dest', 'sid', 'runway', 'squawk', 'initial_alt'], + atcConfirm: 'Readback correct. Contact ground on {ground_freq}.', + atcCorrect: 'Negative, I say again: cleared to {dest} via {sid}, runway {runway}, climb initially {initial_alt} feet, squawk {squawk}.', + }, + updates: { clearance_received: 'true' }, + handoff: { toPhase: 'ground', say: 'Contact ground on {ground_freq}.' }, + }, + { + id: 'request_information', + type: 'pilot_initiates', + pilotIntent: 'Pilot requests ATIS information or QNH', + pilotExample: '{callsign}, request ATIS information', + atcResponse: 'Information {atis_code} is current, QNH {qnh}.', + }, + ], +} diff --git a/shared/atc/phases/departure.ts b/shared/atc/phases/departure.ts new file mode 100644 index 0000000..a7ae6b8 --- /dev/null +++ b/shared/atc/phases/departure.ts @@ -0,0 +1,61 @@ +import type { Phase } from '../types' + +export const departurePhase: Phase = { + id: 'departure', + name: 'Departure Control', + frequency: '125.100', + unit: 'DEP', + nextPhase: 'enroute', + autoAdvance: { + parameter: 'altitude_ft', + operator: '>=', + value: 18000, + }, + interactions: [ + { + id: 'contact_departure', + type: 'pilot_initiates', + pilotIntent: 'Pilot makes initial contact with departure after handoff from tower', + pilotExample: '{callsign}, passing {altitude} feet, climbing {initial_alt}', + atcResponse: '{callsign}, radar contact. Climb flight level {flight_level}.', + readback: { + required: ['flight_level'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, climb flight level {flight_level}.', + }, + }, + { + id: 'climb_instruction', + type: 'atc_initiates', + pilotIntent: 'ATC instructs pilot to climb to a new flight level', + atcResponse: '{callsign}, climb flight level {assigned_fl}.', + readback: { + required: ['assigned_fl'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, climb flight level {assigned_fl}.', + }, + }, + { + id: 'turn_instruction', + type: 'atc_initiates', + pilotIntent: 'ATC instructs pilot to turn to a specific heading', + atcResponse: '{callsign}, turn {turn_direction} heading {assigned_heading}.', + readback: { + required: ['turn_direction', 'assigned_heading'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, turn {turn_direction} heading {assigned_heading}.', + }, + }, + { + id: 'direct_to', + type: 'atc_initiates', + pilotIntent: 'ATC clears pilot to proceed direct to a waypoint', + atcResponse: '{callsign}, proceed direct {waypoint}.', + readback: { + required: ['waypoint'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, proceed direct {waypoint}.', + }, + }, + ], +} diff --git a/shared/atc/phases/emergency.ts b/shared/atc/phases/emergency.ts new file mode 100644 index 0000000..3a3778f --- /dev/null +++ b/shared/atc/phases/emergency.ts @@ -0,0 +1,65 @@ +import type { Phase } from '../types' + +/** + * Emergency phase -- can be activated from ANY other phase. + * + * When the engine detects a MAYDAY or PAN PAN call it should: + * 1. Save `flags.previousPhase` with the current phase id + * 2. Set `flags.emergencyActive = true` + * 3. Transition to this phase + * + * When the emergency is cancelled the engine should: + * 1. Set `flags.emergencyActive = false` + * 2. Restore `currentPhase` to `flags.previousPhase` + * 3. Clear `flags.previousPhase` + */ +export const emergencyPhase: Phase = { + id: 'emergency', + name: 'Emergency', + frequency: '121.500', + unit: 'EMG', + nextPhase: null, + interactions: [ + { + id: 'declare_mayday', + type: 'pilot_initiates', + pilotIntent: 'Pilot declares a MAYDAY emergency (distress)', + pilotExample: 'MAYDAY MAYDAY MAYDAY, {callsign}, {emergency_nature}, {pob} persons on board, fuel remaining {fuel_remaining} minutes', + atcResponse: '{callsign}, MAYDAY acknowledged. Roger {emergency_nature}. All stations, stop transmitting, MAYDAY in progress. {callsign}, say intentions.', + updates: { emergency_type: 'mayday', emergency_active: 'true' }, + }, + { + id: 'declare_panpan', + type: 'pilot_initiates', + pilotIntent: 'Pilot declares a PAN PAN (urgency)', + pilotExample: 'PAN PAN PAN PAN PAN PAN, {callsign}, {emergency_nature}', + atcResponse: '{callsign}, PAN PAN acknowledged. Roger {emergency_nature}. Say intentions.', + updates: { emergency_type: 'panpan', emergency_active: 'true' }, + }, + { + id: 'cancel_emergency', + type: 'pilot_initiates', + pilotIntent: 'Pilot cancels the emergency, situation resolved', + pilotExample: '{callsign}, cancel {emergency_type}, situation resolved', + atcResponse: '{callsign}, roger, {emergency_type} cancelled. Resume normal operations. Contact {resume_freq}.', + updates: { emergency_active: 'false' }, + }, + { + id: 'emergency_landing', + type: 'atc_initiates', + pilotIntent: 'ATC provides vectors for an emergency landing', + atcResponse: '{callsign}, turn {turn_direction} heading {assigned_heading}, vectors for {arrival_runway}. Cleared {approach_type} approach runway {arrival_runway}, emergency services standing by.', + readback: { + required: ['assigned_heading', 'arrival_runway'], + atcConfirm: 'Correct, emergency services standing by.', + atcCorrect: 'Negative, turn {turn_direction} heading {assigned_heading}, vectors for {arrival_runway}.', + }, + }, + { + id: 'souls_on_board', + type: 'atc_initiates', + pilotIntent: 'ATC requests souls on board and fuel remaining for emergency coordination', + atcResponse: '{callsign}, say souls on board and fuel remaining.', + }, + ], +} diff --git a/shared/atc/phases/enroute.ts b/shared/atc/phases/enroute.ts new file mode 100644 index 0000000..b7ec105 --- /dev/null +++ b/shared/atc/phases/enroute.ts @@ -0,0 +1,54 @@ +import type { Phase } from '../types' + +export const enroutePhase: Phase = { + id: 'enroute', + name: 'Center / En-Route Control', + frequency: '132.600', + unit: 'CTR', + nextPhase: 'approach', + interactions: [ + { + id: 'contact_center', + type: 'pilot_initiates', + pilotIntent: 'Pilot makes initial contact with center after handoff from departure', + pilotExample: '{callsign}, flight level {flight_level}', + atcResponse: '{callsign}, radar contact. Maintain flight level {flight_level}.', + }, + { + id: 'maintain_level', + type: 'atc_initiates', + pilotIntent: 'ATC instructs pilot to maintain current flight level', + atcResponse: '{callsign}, maintain flight level {flight_level}.', + readback: { + required: ['flight_level'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, maintain flight level {flight_level}.', + }, + }, + { + id: 'request_level_change', + type: 'pilot_initiates', + pilotIntent: 'Pilot requests a different flight level due to weather or ride comfort', + pilotExample: '{callsign}, request flight level {requested_fl}', + atcResponse: '{callsign}, climb flight level {requested_fl}.', + readback: { + required: ['requested_fl'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, climb flight level {requested_fl}.', + }, + alternatives: [ + { + intent: 'Request denied due to traffic', + atcResponse: '{callsign}, unable flight level {requested_fl} due traffic. Maintain flight level {flight_level}.', + }, + ], + }, + { + id: 'position_report', + type: 'pilot_initiates', + pilotIntent: 'Pilot reports current position over a waypoint or fix', + pilotExample: '{callsign}, position {waypoint}, flight level {flight_level}, estimating {next_waypoint} at {est_time}', + atcResponse: '{callsign}, roger, report {next_waypoint}.', + }, + ], +} diff --git a/shared/atc/phases/ground.ts b/shared/atc/phases/ground.ts new file mode 100644 index 0000000..50d1a3d --- /dev/null +++ b/shared/atc/phases/ground.ts @@ -0,0 +1,59 @@ +import type { Phase } from '../types' + +export const groundPhase: Phase = { + id: 'ground', + name: 'Ground Control', + frequency: '121.700', + unit: 'GND', + nextPhase: 'tower', + interactions: [ + { + id: 'request_pushback', + type: 'pilot_initiates', + pilotIntent: 'Pilot requests pushback and start-up clearance', + pilotExample: '{callsign}, stand {stand}, request pushback and start-up', + atcResponse: '{callsign}, pushback approved, face {push_direction}.', + readback: { + required: ['push_direction'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, pushback approved, face {push_direction}.', + }, + updates: { pushback_approved: 'true' }, + }, + { + id: 'request_taxi', + type: 'pilot_initiates', + when: 'vars.pushback_approved', + pilotIntent: 'Pilot requests taxi clearance to the runway', + pilotExample: '{callsign}, request taxi', + atcResponse: '{callsign}, taxi to holding point {runway} via {taxi_route}.', + readback: { + required: ['runway', 'taxi_route'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, taxi to holding point {runway} via {taxi_route}.', + }, + updates: { taxi_clearance_received: 'true' }, + }, + { + id: 'report_holding_short', + type: 'pilot_initiates', + when: 'vars.taxi_clearance_received', + pilotIntent: 'Pilot reports holding short of the runway', + pilotExample: '{callsign}, holding short runway {runway}', + atcResponse: '{callsign}, contact tower on {tower_freq}.', + handoff: { toPhase: 'tower', say: 'Contact tower on {tower_freq}.' }, + }, + { + id: 'request_cross_runway', + type: 'pilot_initiates', + pilotIntent: 'Pilot requests permission to cross an active runway', + pilotExample: '{callsign}, request cross runway {cross_runway}', + atcResponse: '{callsign}, cross runway {cross_runway}, report vacated.', + readback: { + required: ['cross_runway'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, cross runway {cross_runway}, report vacated.', + }, + }, + ], +} diff --git a/shared/atc/phases/index.ts b/shared/atc/phases/index.ts new file mode 100644 index 0000000..40a41ca --- /dev/null +++ b/shared/atc/phases/index.ts @@ -0,0 +1,48 @@ +import type { Phase } from '../types' +import { clearancePhase } from './clearance' +import { groundPhase } from './ground' +import { towerPhase } from './tower' +import { departurePhase } from './departure' +import { enroutePhase } from './enroute' +import { approachPhase } from './approach' +import { landingPhase } from './landing' +import { taxiInPhase } from './taxiIn' +import { emergencyPhase } from './emergency' + +const phases: Phase[] = [ + clearancePhase, + groundPhase, + towerPhase, + departurePhase, + enroutePhase, + approachPhase, + landingPhase, + taxiInPhase, + emergencyPhase, +] + +const phaseMap = new Map(phases.map(p => [p.id, p])) + +export function getPhase(id: string): Phase | undefined { + return phaseMap.get(id) +} + +export function getPhaseOrder(): string[] { + return phases.filter(p => p.id !== 'emergency').map(p => p.id) +} + +export function getAllPhases(): Phase[] { + return [...phases] +} + +export { + clearancePhase, + groundPhase, + towerPhase, + departurePhase, + enroutePhase, + approachPhase, + landingPhase, + taxiInPhase, + emergencyPhase, +} diff --git a/shared/atc/phases/landing.ts b/shared/atc/phases/landing.ts new file mode 100644 index 0000000..a6882e6 --- /dev/null +++ b/shared/atc/phases/landing.ts @@ -0,0 +1,45 @@ +import type { Phase } from '../types' + +export const landingPhase: Phase = { + id: 'landing', + name: 'Tower (Landing)', + frequency: '118.500', + unit: 'TWR', + nextPhase: 'taxiIn', + autoAdvance: { + parameter: 'on_ground', + operator: '==', + value: 1, + }, + interactions: [ + { + id: 'report_established', + type: 'pilot_initiates', + pilotIntent: 'Pilot reports established on the final approach', + pilotExample: '{callsign}, established {approach_type} runway {arrival_runway}', + atcResponse: '{callsign}, roger, continue approach runway {arrival_runway}.', + }, + { + id: 'cleared_land', + type: 'atc_initiates', + pilotIntent: 'ATC clears pilot to land', + atcResponse: '{callsign}, wind {wind}, runway {arrival_runway}, cleared to land.', + readback: { + required: ['arrival_runway'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, runway {arrival_runway}, cleared to land.', + }, + }, + { + id: 'go_around_instruction', + type: 'atc_initiates', + pilotIntent: 'ATC instructs pilot to go around due to traffic or runway obstruction', + atcResponse: '{callsign}, go around, I say again go around. Climb altitude {go_around_alt} feet, fly runway heading.', + readback: { + required: ['go_around_alt'], + atcConfirm: 'Correct, contact approach on {approach_freq}.', + atcCorrect: 'Negative, go around, climb altitude {go_around_alt} feet, fly runway heading.', + }, + }, + ], +} diff --git a/shared/atc/phases/taxiIn.ts b/shared/atc/phases/taxiIn.ts new file mode 100644 index 0000000..58880e1 --- /dev/null +++ b/shared/atc/phases/taxiIn.ts @@ -0,0 +1,42 @@ +import type { Phase } from '../types' + +export const taxiInPhase: Phase = { + id: 'taxiIn', + name: 'Ground Control (Arrival)', + frequency: '121.700', + unit: 'GND', + nextPhase: null, + interactions: [ + { + id: 'contact_ground_arrival', + type: 'pilot_initiates', + pilotIntent: 'Pilot contacts ground after vacating the runway', + pilotExample: '{callsign}, runway {arrival_runway} vacated, request taxi to stand', + atcResponse: '{callsign}, taxi to stand {arrival_stand} via {arrival_taxi_route}.', + readback: { + required: ['arrival_stand', 'arrival_taxi_route'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, taxi to stand {arrival_stand} via {arrival_taxi_route}.', + }, + }, + { + id: 'taxi_to_gate', + type: 'atc_initiates', + pilotIntent: 'ATC provides taxi instructions to the assigned stand', + atcResponse: '{callsign}, taxi to stand {arrival_stand} via {arrival_taxi_route}.', + readback: { + required: ['arrival_stand', 'arrival_taxi_route'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, taxi to stand {arrival_stand} via {arrival_taxi_route}.', + }, + }, + { + id: 'report_at_gate', + type: 'pilot_initiates', + pilotIntent: 'Pilot reports on stand with engines shut down', + pilotExample: '{callsign}, on stand {arrival_stand}, engines shut down', + atcResponse: '{callsign}, roger, welcome to {dest}. Frequency change approved.', + updates: { session_complete: 'true' }, + }, + ], +} diff --git a/shared/atc/phases/tower.ts b/shared/atc/phases/tower.ts new file mode 100644 index 0000000..1c7f715 --- /dev/null +++ b/shared/atc/phases/tower.ts @@ -0,0 +1,59 @@ +import type { Phase } from '../types' + +export const towerPhase: Phase = { + id: 'tower', + name: 'Tower', + frequency: '118.500', + unit: 'TWR', + nextPhase: 'departure', + autoAdvance: { + parameter: 'altitude_ft', + operator: '>=', + value: 1000, + }, + interactions: [ + { + id: 'report_ready_departure', + type: 'pilot_initiates', + pilotIntent: 'Pilot reports ready for departure', + pilotExample: '{callsign}, ready for departure runway {runway}', + atcResponse: '{callsign}, hold position, standby.', + }, + { + id: 'lineup_and_wait', + type: 'atc_initiates', + pilotIntent: 'ATC instructs pilot to line up on the runway and wait', + atcResponse: '{callsign}, runway {runway}, line up and wait.', + readback: { + required: ['runway'], + atcConfirm: 'Correct.', + atcCorrect: 'Negative, runway {runway}, line up and wait.', + }, + }, + { + id: 'cleared_takeoff', + type: 'atc_initiates', + pilotIntent: 'ATC clears pilot for takeoff', + atcResponse: '{callsign}, wind {wind}, runway {runway}, cleared for takeoff.', + readback: { + required: ['runway'], + atcConfirm: 'Correct, contact departure on {departure_freq} when airborne.', + atcCorrect: 'Negative, runway {runway}, cleared for takeoff.', + }, + updates: { takeoff_clearance: 'true' }, + handoff: { toPhase: 'departure', say: 'Contact departure on {departure_freq}.' }, + }, + { + id: 'cancel_takeoff', + type: 'atc_initiates', + pilotIntent: 'ATC cancels takeoff clearance', + atcResponse: '{callsign}, cancel takeoff, I say again cancel takeoff. Hold position.', + readback: { + required: [], + atcConfirm: 'Correct, hold position.', + atcCorrect: 'Negative, cancel takeoff, hold position.', + }, + updates: { takeoff_clearance: 'false' }, + }, + ], +} diff --git a/shared/atc/telemetryWatcher.ts b/shared/atc/telemetryWatcher.ts new file mode 100644 index 0000000..cd09dd1 --- /dev/null +++ b/shared/atc/telemetryWatcher.ts @@ -0,0 +1,61 @@ +import type { Phase, TelemetryCondition, TelemetryState } from './types' + +export interface TelemetryEvent { + type: 'phase_advance' | 'atc_interrupt' + toPhase?: string + interactionId?: string + trigger: { + parameter: string + condition: string + value: number | boolean + } +} + +/** + * Evaluates a single telemetry condition against current telemetry state. + * Returns true if the condition is met, false otherwise. + */ +export function evaluateCondition( + telemetry: TelemetryState, + condition: TelemetryCondition +): boolean { + const actual = telemetry[condition.parameter] + if (actual === undefined) return false + + const val = condition.value + switch (condition.operator) { + case '>': return (actual as number) > val + case '>=': return (actual as number) >= val + case '<': return (actual as number) < val + case '<=': return (actual as number) <= val + case '==': return actual == val // loose equality for boolean/number comparison (on_ground == 1) + case '!=': return actual != val + default: return false + } +} + +/** + * Evaluates telemetry against the current phase's autoAdvance condition. + * If the condition is met and nextPhase is set, returns a phase_advance event. + * Otherwise returns null. + */ +export function evaluateTelemetry( + telemetry: TelemetryState, + currentPhase: Phase +): TelemetryEvent | null { + if (currentPhase.autoAdvance && currentPhase.nextPhase) { + if (evaluateCondition(telemetry, currentPhase.autoAdvance)) { + return { + type: 'phase_advance', + toPhase: currentPhase.nextPhase, + trigger: { + parameter: currentPhase.autoAdvance.parameter, + condition: `${currentPhase.autoAdvance.parameter} ${currentPhase.autoAdvance.operator} ${currentPhase.autoAdvance.value}`, + value: telemetry[currentPhase.autoAdvance.parameter] as number, + }, + } + } + } + + return null +} diff --git a/shared/atc/templateRenderer.ts b/shared/atc/templateRenderer.ts new file mode 100644 index 0000000..a067b00 --- /dev/null +++ b/shared/atc/templateRenderer.ts @@ -0,0 +1,11 @@ +/** + * Simple template renderer that replaces {variable} placeholders with values. + * Renders a template string like "{callsign}, cleared to {dest} via {sid}" + * by replacing {key} placeholders with values from the vars object. + * Unreplaced placeholders are left as-is (useful for debugging). + */ +export function renderTemplate(template: string, vars: Record): string { + return template.replace(/\{(\w+)\}/g, (match, key) => { + return vars[key] ?? match + }) +} diff --git a/shared/atc/types.ts b/shared/atc/types.ts new file mode 100644 index 0000000..6a39b64 --- /dev/null +++ b/shared/atc/types.ts @@ -0,0 +1,191 @@ +// Phase definition +export interface Phase { + id: string + name: string + frequency: string + unit: string + interactions: Interaction[] + autoAdvance?: TelemetryCondition + nextPhase: string | null +} + +export interface Interaction { + id: string + type: 'pilot_initiates' | 'atc_initiates' | 'readback_check' + when?: string // Condition expression + pilotIntent: string // For LLM prompt + pilotExample?: string // Template with {vars} + atcResponse: string // Template with {vars} + readback?: ReadbackSpec + updates?: Record + handoff?: { toPhase: string; say?: string } + alternatives?: AlternativeResponse[] +} + +export interface ReadbackSpec { + required: string[] + atcConfirm: string + atcCorrect: string +} + +export interface AlternativeResponse { + intent: string + atcResponse: string + updates?: Record +} + +export interface TelemetryCondition { + parameter: string + operator: '>' | '>=' | '<' | '<=' | '==' | '!=' + value: number + holdMs?: number +} + +// Flight variables +export interface FlightVars { + callsign: string + aircraft: string + dep: string + dest: string + stand: string + runway: string + sid: string + squawk: string + atis_code: string + initial_alt: string + flight_level: string + qnh: string + taxi_route: string + ground_freq: string + tower_freq: string + departure_freq: string + approach_freq: string + center_freq: string + atis_freq: string + wind: string + arrival_runway: string + arrival_stand: string + arrival_taxi_route: string + star: string + approach_type: string + [key: string]: string +} + +// Telemetry from SimBridge +export interface TelemetryState { + altitude_ft: number + speed_kts: number + groundspeed_kts: number + vertical_speed_fpm: number + heading_deg: number + latitude_deg: number + longitude_deg: number + on_ground: boolean + [key: string]: number | boolean +} + +// Engine state +export interface EngineState { + currentPhase: string + currentInteraction: string | null + waitingFor: 'pilot' | 'readback' | 'none' + vars: FlightVars + flags: { + inAir: boolean + emergencyActive: boolean + previousPhase: string | null + } + telemetry: TelemetryState + sessionId: string + transmissions: Transmission[] +} + +// Transmission with debug info +export interface Transmission { + id: string + timestamp: Date + speaker: 'pilot' | 'atc' | 'system' + message: string + normalized?: string + phase: string + frequency: string + debug: TransmissionDebug +} + +export interface TransmissionDebug { + sttRaw?: string + llmRequest?: { + currentPhase: string + currentInteraction: string | null + pilotSaid: string + candidates: { id: string; intent: string }[] + contextSent: Record + } + llmResponse?: { + chosenInteraction: string + confidence: 'high' | 'medium' | 'low' + reason: string + tokensUsed: number + durationMs: number + model: string + } + engineAction?: { + templateUsed: string + variablesUpdated: Record + handoff?: { from: string; to: string } + phaseChanged?: { from: string; to: string } + } + telemetryTrigger?: { + parameter: string + condition: string + value: number + } + readbackResult?: { + complete: boolean + missing?: string[] + } +} + +// Flight plan input +export interface FlightPlan { + callsign: string + aircraft?: string + dep: string + arr: string + route?: string + altitude?: string + squawk?: string + id?: number + assignedsquawk?: string +} + +// LLM Router types +export interface RouteRequest { + pilotSaid: string + phase: string + interaction: string | null + waitingFor: 'pilot' | 'readback' | 'none' + candidates: RouteCandidate[] + vars: Record + recentTransmissions: string[] +} + +export interface RouteCandidate { + id: string + intent: string + example?: string +} + +export interface RouteResponse { + chosen: string + reason: string + pilotIntent: string + confidence: 'high' | 'medium' | 'low' + tokensUsed: number + durationMs: number + model: string + readbackResult?: { + complete: boolean + missing?: string[] + } +}