Skip to content

radarcontrol.io ATC - API Reference

Complete reference for radarcontrol.io's ATC API.

Sandbox

Your code runs in a sandboxed environment. Only the documented APIs below are available.

Table of Contents


Event Handlers

onTick(callback)

Called every simulation tick (~0.5 seconds by default).

Parameters:

  • callback: (ctx: APIContext) => void - Function called each frame

Context Object (ctx):

javascript
{
  time: number,        // Simulation time (seconds)
  dt: number,          // Time step (seconds, usually 0.5)
  traffic: TrafficView,
  weather: WeatherView,
  sectors: SectorsView,
  fixes: FixesView
}

Example:

javascript
onTick(({ traffic, time }) => {
  const flights = traffic.all();
  log(`Time: ${time}s, Aircraft: ${flights.length}`);

  flights.forEach(ac => {
    // Check altitude and issue climb clearance
    if (ac.altFL < 300) {
      ac.climb(fl(350)); // Climb to FL350
    }
  });
});

onSpawn(callback)

Called when a new aircraft enters your sector.

Parameters:

  • callback: (aircraft: Aircraft) => void

Example:

javascript
onSpawn((aircraft) => {
  log(`${aircraft.cs} (${aircraft.type}) at FL${aircraft.altFL}`);
  log(`Exit: ${aircraft.plan.exitPoint} at FL${aircraft.plan.exitFL}`);

  // Initial clearance
  aircraft.speed(400);

  // Climb to cruise altitude if needed
  if (aircraft.plan.cruiseFL > aircraft.altFL) {
    aircraft.climb(fl(aircraft.plan.cruiseFL));
  }

  // Set up handover at exit fix
  aircraft.over(aircraft.plan.exitPoint, (ac) => {
    ac.handover();
  });

  // Ensure aircraft reaches exit altitude before exit fix
  if (aircraft.plan.exitAltitude) {
    aircraft.reach(aircraft.plan.exitPoint).altitude(aircraft.plan.exitAltitude);
  }
});

onConflict(callback)

Called when two aircraft are predicted to lose separation (< 5nm lateral or < 1000ft vertical).

Parameters:

  • callback: (pair: ConflictEvent) => void

ConflictEvent Object:

javascript
{
  a: string,              // First aircraft callsign
  b: string,              // Second aircraft callsign
  predMinSepNm: number,   // Predicted minimum separation
  timeToCPA: number,      // Time to closest point (seconds)
  severity: string,       // "critical" | "warning" | "caution"
  cpaVerticalFt: number,  // Vertical separation at CPA
  advisories: ConflictAdvisories  // Resolution advisories (optional)
}

ConflictAdvisories Object:

javascript
{
  forA: ResolutionAdvisory[],      // Advisories for aircraft A
  forB: ResolutionAdvisory[],      // Advisories for aircraft B
  recommended: {                   // Best overall resolution
    callsign: string,
    advisory: ResolutionAdvisory
  } | null,
  available: boolean               // Whether any resolution is possible
}

ResolutionAdvisory Object:

javascript
{
  callsign: string,           // Aircraft this applies to
  resolutionType: string,     // "vertical" | "lateral" | "speed"
  maneuver: ResolutionManeuver,
  effectiveness: number,      // 0-1 score (1 = fully resolves)
  projectedSeparationNm: number,
  projectedSeparationFt: number,
  timeToEffect: number        // Seconds until separation improves
}

ResolutionManeuver Types:

javascript
// Vertical
{ type: "climb", targetAltFt: number }
{ type: "descend", targetAltFt: number }

// Lateral
{ type: "turn", direction: "left" | "right", degrees: number, targetHdgDeg: number }

// Speed
{ type: "speed", targetKts: number }

Example - Basic conflict handling:

javascript
onConflict((pair) => {
  log(`Conflict: ${pair.a} and ${pair.b}`);
  log(`Min separation: ${pair.predMinSepNm.toFixed(1)}nm in ${pair.timeToCPA}s`);

  // Resolve with vertical separation
  const acA = traffic.byCallsign(pair.a);
  const acB = traffic.byCallsign(pair.b);

  if (acA && acB) {
    const verticalSep = Math.abs(acA.altFt - acB.altFt);

    if (verticalSep < 1000) {
      // Climb the higher aircraft
      if (acA.altFt > acB.altFt) {
        acA.climb(acA.altFt + 2000);
      } else {
        acB.climb(acB.altFt + 2000);
      }
    }
  }
});

Example - Using resolution advisories:

javascript
onConflict((pair) => {
  // Check if advisories are available
  if (!pair.advisories?.available) {
    log(`Conflict ${pair.a}/${pair.b} - no resolution available`);
    return;
  }

  // Get the recommended resolution
  const rec = pair.advisories.recommended;
  if (rec) {
    const { callsign, advisory } = rec;
    const ac = traffic.byCallsign(callsign);
    if (!ac) return;

    // Apply the recommended maneuver
    switch (advisory.maneuver.type) {
      case 'climb':
        ac.climb(advisory.maneuver.targetAltFt);
        break;
      case 'descend':
        ac.descend(advisory.maneuver.targetAltFt);
        break;
      case 'turn':
        ac.heading(advisory.maneuver.targetHdgDeg);
        break;
      case 'speed':
        ac.speed(advisory.maneuver.targetKts);
        break;
    }

    log(`Applied ${advisory.resolutionType} resolution to ${callsign}`);
    log(`Projected separation: ${advisory.projectedSeparationNm.toFixed(1)}nm`);
  }
});

Example - Auto-resolution with priority:

javascript
// Automatically resolve conflicts, preferring vertical separation
onConflict((pair) => {
  if (!pair.advisories?.available) return;

  // Find best vertical advisory (most effective)
  const allAdvisories = [
    ...pair.advisories.forA,
    ...pair.advisories.forB
  ];

  const verticalAdvisories = allAdvisories
    .filter(a => a.resolutionType === 'vertical')
    .sort((a, b) => b.effectiveness - a.effectiveness);

  if (verticalAdvisories.length > 0) {
    const best = verticalAdvisories[0];
    const ac = traffic.byCallsign(best.callsign);
    if (ac && best.maneuver.type === 'climb') {
      ac.climb(best.maneuver.targetAltFt);
    } else if (ac && best.maneuver.type === 'descend') {
      ac.descend(best.maneuver.targetAltFt);
    }
  }
});

onMeterAlert(callback) Not Implemented

Called when demand is high at a metering fix.

Real-world usage: Traffic Management Initiatives (TMI) use metering to control arrival/departure rates and manage sector demand. When too many aircraft converge on a fix, controllers implement miles-in-trail restrictions, speed control, or reroutes to smooth the flow. This can apply to arrival fixes at busy airports, sector boundaries, or any congested waypoint.

Example:

javascript
onMeterAlert((fixName) => {
  log(`High demand at ${fixName} - implementing metering`);

  // Get all aircraft heading to this fix
  const inbound = traffic.all().filter(ac =>
    ac.plan.route.includes(fixName)
  );

  // Implement 10nm miles-in-trail restriction
  inbound.sort((a, b) => {
    const distA = distance(a.pos, fixes.get(fixName).pos);
    const distB = distance(b.pos, fixes.get(fixName).pos);
    return distA - distB;
  });

  inbound.forEach((ac, i) => {
    if (i > 0) {
      const lead = inbound[i - 1];
      const spacing = distance(ac.pos, lead.pos);

      if (spacing < 10) {
        // Reduce speed to achieve 10nm spacing
        ac.speed(Math.max(lead.targetIASKts - 40, 250));
      }
    }
  });
});

onHandoffRequest(callback) Not Implemented

Called when an aircraft needs handoff to next sector.

Real-world usage: Handoffs are coordinated between controllers, often with verbal or automated coordination. The receiving controller must accept the handoff before the transferring controller switches the aircraft to the new frequency. Controllers verify the aircraft meets all crossing restrictions and separation standards before handoff.

Example:

javascript
onHandoffRequest((req) => {
  log(`${req.cs} requesting handoff to ${req.nextSector}`);
  const ac = traffic.byCallsign(req.cs);

  if (!ac) return;

  // Check aircraft is stable and meets requirements
  const isStable = ac.altFt === ac.targetAltFt;
  const meetsAltitude = !ac.plan.exitFL || ac.altFL === ac.plan.exitFL;
  const atExitFix = ac.plan.exitPoint &&
    distance(ac.pos, fixes.get(ac.plan.exitPoint).pos) < 5;

  if (isStable && meetsAltitude && atExitFix) {
    ac.handover();
    log(`${ac.cs} handed to ${req.nextSector}`);
  } else {
    log(`${ac.cs} not ready - waiting for compliance`);

    // Ensure aircraft meets exit requirements
    if (!meetsAltitude && ac.plan.exitFL) {
      if (ac.altFL < ac.plan.exitFL) {
        ac.climb(fl(ac.plan.exitFL));
      } else {
        ac.descend(fl(ac.plan.exitFL));
      }
    }
  }
});

onDepartureControllable(callback)

Called when a departure aircraft reaches 10,000 feet and becomes controllable.

Real-world usage: In the real world, departure aircraft are handed off from tower to approach/departure control, then to center as they climb. Aircraft below 10,000 feet are typically under local approach control and follow standard departure procedures. Once reaching a certain altitude (often around 10,000 feet), they're handed off to en-route (center) control where they can receive direct routing and altitude changes.

Parameters:

  • callback: (aircraft: Aircraft) => void - Function called when a departure aircraft reaches 10,000ft

Example:

javascript
onDepartureControllable((aircraft) => {
  log(`${aircraft.cs} now controllable at FL${aircraft.altFL}`);

  // Issue initial en-route clearance
  aircraft.climb(fl(aircraft.plan.cruiseFL));
  aircraft.speed(420);

  // Direct to first waypoint if available
  if (aircraft.plan.route.length > 0) {
    aircraft.direct(aircraft.plan.route[0]);
  }

  // Set up handover at exit fix
  aircraft.over(aircraft.plan.exitPoint, (ac) => {
    ac.handover();
  });

  // Ensure aircraft reaches exit altitude
  if (aircraft.plan.exitAltitude) {
    aircraft.reach(aircraft.plan.exitPoint).altitude(aircraft.plan.exitAltitude);
  }
});

Departure aircraft

Departure aircraft spawn on airport runways and progress through flight phases:

  1. Takeoff - Accelerating on runway
  2. Departure - Climbing (NOT controllable until 10,000ft)
  3. Cruise - Normal en-route (controllable)

During the takeoff and departure phases (below 10,000ft), aircraft:

  • Are dimmed in the flight strip panel
  • Cannot receive altitude, speed, heading, or route clearances
  • Automatically maintain runway heading until 3,000ft
  • Follow standard speed restrictions (250 knots below 10,000ft)

Once reaching 10,000ft, the onDepartureControllable event fires and the aircraft transitions to cruise phase.

Airport departure cooldown

Airports have a 2-minute cooldown between departures. This simulates realistic runway usage and departure sequencing.


Aircraft Control API

Aircraft objects provide methods for issuing clearances. All methods return this for chaining.

aircraft.climb(altitude)

Issue climb clearance.

Real-world usage: Controllers use climb clearances to establish vertical separation, allow aircraft to reach more efficient cruise altitudes, or clear aircraft above weather. Standard phraseology: "Climb and maintain FL350."

Parameters:

  • altitude: number - Target altitude in feet OR use fl() helper for flight levels

Returns: this (for method chaining)

Examples:

javascript
// Using feet
aircraft.climb(35000);

// Using flight level helper
aircraft.climb(fl(350));  // FL350 = 35,000ft

// Climb to cruise altitude
aircraft.climb(fl(aircraft.plan.cruiseFL));

// Establish vertical separation in conflict
onConflict((pair) => {
  const acA = traffic.byCallsign(pair.a);
  const acB = traffic.byCallsign(pair.b);
  if (acA && acB && Math.abs(acA.altFt - acB.altFt) < 1000) {
    // Climb higher aircraft to establish 2000ft separation
    if (acA.altFt > acB.altFt) {
      acA.climb(acA.altFt + 2000);
    }
  }
});

// Method chaining
aircraft.climb(fl(350)).speed(420);

aircraft.descend(altitude)

Issue descent clearance.

Real-world usage: Controllers issue descent clearances to prepare aircraft for arrival, establish vertical separation below conflicting traffic, or comply with airspace restrictions. Standard phraseology: "Descend and maintain FL240."

Parameters:

  • altitude: number - Target altitude (feet or use fl() helper)

Returns: this (for method chaining)

Examples:

javascript
aircraft.descend(24000);
aircraft.descend(fl(240));  // FL240

// Descend to exit altitude for next sector
if (aircraft.plan.exitFL) {
  aircraft.descend(fl(aircraft.plan.exitFL));
}

// Descend below conflicting traffic
onConflict((pair) => {
  const acA = traffic.byCallsign(pair.a);
  const acB = traffic.byCallsign(pair.b);
  if (acA && acB) {
    // Descend lower aircraft below the conflict
    if (acA.altFt < acB.altFt) {
      acA.descend(acA.altFt - 2000);
    }
  }
});

aircraft.speed(speed)

Issue speed restriction.

Real-world usage: Speed control is one of the most important tools for maintaining separation. Controllers use speed adjustments to manage in-trail spacing, sequence arrivals, or slow aircraft before a turn. Standard phraseology: "Reduce speed to 320 knots" or "Maintain maximum forward speed."

Parameters:

  • speed: number - Target indicated airspeed in knots (180-450 typical range)

Returns: this (for method chaining)

Examples:

javascript
// Standard cruise speed
aircraft.speed(400);

// Maintain in-trail separation (common technique)
const lead = traffic.byCallsign("AAL123");
const trail = traffic.byCallsign("DAL456");

if (lead && trail) {
  const dist = distance(lead.pos, trail.pos);

  // Target 8-10nm spacing
  if (dist < 8) {
    // Slow trailing aircraft to open spacing
    trail.speed(Math.max(lead.targetIASKts - 30, 250));
  } else if (dist > 12) {
    // Speed up to close gap
    trail.speed(Math.min(lead.targetIASKts + 20, 450));
  }
}

// Speed restriction before waypoint (arrival sequencing)
aircraft.before("MERGE", 40, (ac) => {
  ac.speed(280); // Reduce to approach speed
});

aircraft.direct(...fixes)

Clear aircraft direct to waypoint(s), rerouting the flight plan.

Real-world usage: Direct routings are issued to shorten flight paths, expedite traffic flow, or provide separation through lateral spacing. This is one of the most common pilot requests and controller efficiency tools. Standard phraseology: "Proceed direct KMART" or "When able, direct ROBUC."

Parameters:

  • fixes: string[] - One or more waypoint names

Returns: this (for method chaining)

Behavior:

The direct() method intelligently modifies the aircraft's flight plan based on whether the waypoint(s) are in the current route:

  • Single waypoint in route: If the waypoint is already in the aircraft's route, truncates the route from that waypoint onward
    • Route [A, B, C, D]direct(C)[C, D]
  • Single waypoint not in route: Replaces entire route with just that waypoint
    • Route [A, B, C, D]direct(E)[E]
  • Multiple waypoints: Replaces entire route with specified waypoints
    • Route [A, B, C, D, E]direct(C, E)[C, E]
    • Route [A, B, C, D]direct(E, F)[E, F]

Examples:

javascript
// Direct to waypoint - truncates route from that point
aircraft.direct("KMART");

// Direct to exit fix - useful for shortcuts
aircraft.direct(aircraft.plan.exitPoint);

// Multiple waypoints - creates custom route
aircraft.direct("KMART", "ROBUC", "HIBER");

// Provide lateral separation via different routing
onConflict((pair) => {
  const acA = traffic.byCallsign(pair.a);
  const acB = traffic.byCallsign(pair.b);

  if (acA && acB) {
    // Route one aircraft to alternate fix for lateral separation
    const altFix = "ROBUC";
    if (distance(acA.pos, fixes.get(altFix).pos) < 30) {
      acA.direct(altFix);
      log(`${acA.cs} direct ${altFix} for separation`);
    }
  }
});

// Shortcut when aircraft is close to downstream fix
const targetFix = fixes.get("ROBUC");
if (targetFix) {
  const dist = distance(aircraft.pos, targetFix.pos);
  if (dist < 50) {
    aircraft.direct("ROBUC");
    log(`${aircraft.cs} cleared direct ROBUC`);
  }
}

// Send all aircraft direct to exit, bypassing intermediate fixes
onSpawn((aircraft) => {
  aircraft.direct(aircraft.plan.exitPoint);
});

Important Notes:

  • Clears any heading vector (returns aircraft to route following)
  • Resets active leg index to 0
  • The route displayed on the radar will update to show the new flight plan
  • The aircraft will fly the new route, with turn anticipation if enabled

aircraft.offset(offsetNm)

Apply lateral offset from route centerline.

Real-world usage: Strategic lateral offsets (SLO) are used in oceanic and high-altitude airspace to reduce the probability of collision, provide wake turbulence avoidance, and maintain separation on parallel tracks. Controllers may also use offsets to laterally separate aircraft on the same route. Standard phraseology: "Offset 2 miles right of course."

Parameters:

  • offsetNm: number - Offset in nautical miles (positive = right, negative = left)

Returns: this (for method chaining)

Examples:

javascript
// Offset right for lateral separation on same route
aircraft.offset(2);

// Offset left for wake turbulence avoidance
onSpawn((aircraft) => {
  const ahead = traffic.all().find(ac =>
    ac.wake === "H" && // Heavy aircraft ahead
    distance(ac.pos, aircraft.pos) < 10
  );

  if (ahead) {
    aircraft.offset(-2); // Offset to avoid wake
    log(`${aircraft.cs} offset for wake turbulence`);
  }
});

// Return to centerline when clear
aircraft.offset(0);

// Parallel routing for multiple aircraft on same airway
const flights = traffic.all()
  .filter(ac => ac.plan.route.includes("KMART"))
  .sort((a, b) => a.altFL - b.altFL);

flights.forEach((ac, i) => {
  ac.offset(i * 2); // 0, 2, 4, 6 nm lateral spacing
});

aircraft.handover()

Clear aircraft for handoff to next sector. Validates altitude is within exit fix constraints.

Real-world usage: Handoffs (or handovers) transfer control and communication of an aircraft between sectors or facilities. Controllers coordinate to ensure the receiving controller accepts the aircraft and that all altitude/routing requirements are met. Standard phraseology: "Contact [next sector] on [frequency]."

Returns: this (for method chaining)

Examples:

javascript
// Handover when aircraft reaches exit fix (standard practice)
onSpawn((aircraft) => {
  aircraft.over(aircraft.plan.exitPoint, (ac) => {
    // Ensure aircraft meets exit requirements
    if (ac.altFt === ac.targetAltFt) {
      ac.handover();
      log(`${ac.cs} handed to next sector at ${aircraft.plan.exitPoint}`);
    }
  });
});

// Ensure aircraft is stable before handoff
const ac = traffic.byCallsign("AAL123");
if (ac) {
  const stable = ac.altFt === ac.targetAltFt &&
                 ac.gsKts === ac.targetIASKts;

  if (stable && ac.plan.exitPoint) {
    const exitFix = fixes.get(ac.plan.exitPoint);
    if (exitFix && distance(ac.pos, exitFix.pos) < 5) {
      ac.handover();
    }
  }
}

aircraft.turn.right(degrees)

Turn aircraft right by specified degrees.

Real-world usage: Heading assignments (vectors) are used for separation, sequencing, weather avoidance, and intercepts. Controllers typically use 10-30 degree turns for small adjustments and up to 90 degrees for significant lateral separation. Standard phraseology: "Turn right heading 090" or "Turn right 30 degrees."

Parameters:

  • degrees: number - Degrees to turn right

Returns: Aircraft (for method chaining)

Examples:

javascript
// Small turn for lateral separation
aircraft.turn.right(20);

// Larger turn to avoid traffic
aircraft.turn.right(45);

// 90-degree turn for significant separation
onConflict((pair) => {
  const acA = traffic.byCallsign(pair.a);
  if (acA) {
    acA.turn.right(90);
    log(`${acA.cs} turn right 90 for traffic`);
  }
});

// Turn and climb together
aircraft.turn.right(30).climb(fl(370));

aircraft.turn.left(degrees)

Turn aircraft left by specified degrees.

Real-world usage: Left turns follow the same principles as right turns - used for separation, sequencing, and traffic flow. Controllers consider wind, aircraft performance, and traffic when choosing turn direction. Standard phraseology: "Turn left heading 270" or "Turn left 20 degrees."

Parameters:

  • degrees: number - Degrees to turn left

Returns: Aircraft (for method chaining)

Examples:

javascript
// Small adjustment for spacing
aircraft.turn.left(15);

// Turn for separation
aircraft.turn.left(30);

// Downwind turn for sequencing
aircraft.turn.left(45).speed(280);

// Turn away from traffic
onConflict((pair) => {
  const acA = traffic.byCallsign(pair.a);
  const acB = traffic.byCallsign(pair.b);

  if (acA && acB) {
    // Turn aircraft in opposite directions
    acA.turn.left(30);
    acB.turn.right(30);
    log(`${acA.cs} and ${acB.cs} diverging turns for separation`);
  }
});

aircraft.turn.rightTo(heading)

Turn aircraft right to specified heading.

Real-world usage: When controllers need to specify both the turn direction and final heading, they use phraseology like "Turn right heading 090" to ensure the aircraft turns the intended direction (useful when the heading could be reached by either direction).

Parameters:

  • heading: number - Target heading in degrees (0-360)

Returns: Aircraft (for method chaining)

Examples:

javascript
// Turn right to heading 090
aircraft.turn.rightTo(90);

// Ensure right turn for traffic flow
aircraft.turn.rightTo(180);

// Method chaining
aircraft.turn.rightTo(270).climb(fl(370));

aircraft.turn.leftTo(heading)

Turn aircraft left to specified heading.

Real-world usage: Like rightTo, but explicitly specifies a left turn. Used when the turn direction matters for separation or traffic flow. Standard phraseology: "Turn left heading 270."

Parameters:

  • heading: number - Target heading in degrees (0-360)

Returns: Aircraft (for method chaining)

Examples:

javascript
// Turn left to heading 270
aircraft.turn.leftTo(270);

// Ensure left turn for separation
aircraft.turn.leftTo(180);

// Diverging turns with explicit directions
onConflict((pair) => {
  const acA = traffic.byCallsign(pair.a);
  const acB = traffic.byCallsign(pair.b);

  if (acA && acB) {
    acA.turn.leftTo(270);
    acB.turn.rightTo(090);
    log(`${acA.cs} and ${acB.cs} diverging on reciprocal headings`);
  }
});

aircraft.turn.to(heading)

Turn aircraft to specified heading (shortest turn).

Real-world usage: The most common heading assignment - aircraft takes the shortest turn to reach the heading. Standard phraseology: "Fly heading 090" or "Turn heading 270."

Parameters:

  • heading: number - Target heading in degrees (0-360)

Returns: Aircraft (for method chaining)

Examples:

javascript
// Fly heading 090 (shortest turn)
aircraft.turn.to(90);

// Vector for intercept
const fix = fixes.get("KMART");
if (fix) {
  const hdg = headingTo(aircraft.pos, fix.pos);
  aircraft.turn.to(hdg);
}

// Conflict resolution
onConflict((pair) => {
  const acA = traffic.byCallsign(pair.a);
  if (acA) {
    // Turn 30 degrees left from current heading
    const newHdg = (acA.hdgDeg - 30 + 360) % 360;
    acA.turn.to(newHdg);
  }
});

// Method chaining
aircraft.turn.to(180).speed(420).climb(fl(350));

aircraft.over(waypoint, callback)

Register callback when aircraft passes over waypoint.

Parameters:

  • waypoint: string - Waypoint name
  • callback: (ac: Aircraft) => void - Function called when aircraft reaches waypoint

Returns: this (for method chaining)

Examples:

javascript
// Handover at exit fix
aircraft.over(aircraft.plan.exitPoint, (ac) => {
  ac.handover();
});

// Change altitude at waypoint
aircraft.over("KMART", (ac) => {
  ac.climb(fl(380));
});

// Log waypoint passage
aircraft.over("ROBUC", (ac) => {
  log(`${ac.cs} passed ROBUC at FL${ac.altFL}`);
});

aircraft.before(waypoint, distanceNm, callback)

Register callback before reaching waypoint.

Parameters:

  • waypoint: string - Waypoint name
  • distanceNm: number - Distance before waypoint (nm)
  • callback: (ac: Aircraft) => void - Function called when aircraft is within distance

Returns: this (for method chaining)

Examples:

javascript
// Start descent 30nm before waypoint
aircraft.before("KMART", 30, (ac) => {
  ac.descend(fl(240));
});

// Reduce speed before merge point
aircraft.before("MERGE", 20, (ac) => {
  ac.speed(320);
});

aircraft.reach(waypoint)

Build route with altitude/speed constraints at waypoint. Returns a ConstraintBuilder.

Returns: ConstraintBuilder (with .altitude() and .speed() methods)

Examples:

javascript
// Ensure aircraft reaches exit altitude before exit fix
aircraft.reach(aircraft.plan.exitPoint).altitude(aircraft.plan.exitAltitude);

// Altitude constraint at waypoint
aircraft.reach("KMART").altitude(fl(280));

// Speed constraint at waypoint
aircraft.reach("MERGE").speed(320);

// Both altitude and speed constraints (chaining)
aircraft
  .reach("KMART").altitude(fl(280))
  .reach("MERGE").speed(320);

ConstraintBuilder Methods:

.altitude(altFt)

Set altitude constraint - aircraft must reach altitude before waypoint.

Parameters:

  • altFt: number - Altitude in feet (or use fl() helper)

Returns: Aircraft (for method chaining)

.speed(kts)

Set speed constraint - aircraft must reach speed before waypoint.

Parameters:

  • kts: number - Speed in knots

Returns: Aircraft (for method chaining)


Traffic Management

Access via traffic object in onTick context.

traffic.all()

Get all aircraft in sector.

Returns: Aircraft[]

Example:

javascript
onTick(({ traffic }) => {
  const flights = traffic.all();
  log(`Managing ${flights.length} aircraft`);

  // Find highest aircraft
  const highest = flights.reduce((max, ac) =>
    ac.altFL > max.altFL ? ac : max
  );

  log(`Highest: ${highest.cs} at FL${highest.altFL}`);
});

traffic.byCallsign(callsign)

Get specific aircraft by callsign.

Returns: Aircraft | undefined

Example:

javascript
const ac = traffic.byCallsign("AAL123");
if (ac) {
  log(`${ac.cs} is at FL${ac.altFL}, ${ac.gsKts}kts`);

  // Check if climbing
  if (ac.altFL < ac.targetFL) {
    log(`Climbing to FL${ac.targetFL}`);
  }
}

Aircraft Object

Read-only Properties:

javascript
{
  cs: string,              // Callsign (e.g., "AAL123")
  type: string,            // Aircraft type ("A320", "B738", etc.)
  wake: "L"|"M"|"H"|"J",   // Wake turbulence category

  pos: {x, y},             // Position (nautical miles)
  hdgDeg: number,          // Heading 0-360
  iasKts: number,          // Indicated airspeed (what pilot flies)
  gsKts: number,           // Ground speed (affected by wind)

  altFt: number,           // Current altitude (feet)
  altFL: number,           // Current altitude (flight level)
  vsFpm: number,           // Vertical speed (feet per minute)

  plan: {
    route: string[],       // Waypoint names
    cruiseAltFt: number,   // Planned cruise altitude (feet)
    cruiseFL: number,      // Planned cruise altitude (flight level)
    exitPoint: string,     // Last waypoint (exit)
    exitAltitude?: number, // Required altitude at exit (feet)
    exitFL?: number        // Required altitude at exit (flight level)
  },

  activeLegIdx: number,    // Current leg in route
  targetAltFt: number,     // Cleared altitude (feet)
  targetFL: number,        // Cleared altitude (flight level)
  targetIASKts: number,    // Cleared airspeed
  targetHdgDeg?: number,   // Cleared heading (if vectoring)
  directTo?: string,       // Direct-to override
  offsetNm: number,        // Lateral offset from route
  sectorId: string         // Current sector
}

Key Features:

  • Flight level properties: altFL, targetFL, plan.cruiseFL, plan.exitFL
  • Exit constraints: plan.exitPoint, plan.exitAltitude, plan.exitFL
  • Aircraft objects have control methods: climb(), speed(), direct(), etc.

Weather API

Access via weather object in onTick context. Wind affects aircraft ground speed - headwinds reduce GS, tailwinds increase it.

weather.windAt(altitude)

Get wind at specified altitude.

Parameters:

  • altitude: number - Altitude in feet

Returns: {dirDeg: number, kts: number} - Direction wind is FROM and speed

Example:

javascript
onTick(({ weather, traffic }) => {
  const wind35000 = weather.windAt(35000);
  log(`Wind at FL350: ${wind35000.dirDeg}° at ${wind35000.kts}kts`);

  // Calculate headwind component for aircraft
  traffic.all().forEach(ac => {
    const wind = weather.windAt(ac.altFt);
    const hdgRad = (ac.hdgDeg * Math.PI) / 180;
    const windRad = (wind.dirDeg * Math.PI) / 180;
    const headwind = wind.kts * Math.cos(windRad - hdgRad);
    log(`${ac.cs} headwind: ${headwind.toFixed(0)}kts`);
  });
});

weather.surfaceWind()

Get surface wind (used for runway selection).

Returns: {dirDeg: number, kts: number}

Example:

javascript
onTick(({ weather }) => {
  const sfc = weather.surfaceWind();
  log(`Surface wind: ${sfc.dirDeg}° at ${sfc.kts}kts`);
});

weather.cells()

Get all active weather cells (thunderstorms).

Returns: WeatherCell[]

javascript
{
  id: string,            // Unique cell ID
  center: {lat, lon},    // Geographic position
  radiusNm: number,      // Cell radius in nautical miles
  intensity: string      // "light" | "moderate" | "heavy" | "extreme"
}

Example:

javascript
onTick(({ weather, traffic }) => {
  const cells = weather.cells();
  log(`Active weather cells: ${cells.length}`);

  cells.forEach(cell => {
    if (cell.intensity === 'extreme') {
      log(`Severe weather at ${cell.center.lat.toFixed(2)}, ${cell.center.lon.toFixed(2)}`);
    }
  });
});

weather.intensity()

Get current weather intensity setting (0-100).

Returns: number

Example:

javascript
onTick(({ weather }) => {
  const intensity = weather.intensity();
  if (intensity > 50) {
    log("Heavy weather conditions - expect deviations");
  }
});

onDeviationRequest(callback)

Called when a pilot requests deviation around weather.

Parameters:

  • callback: (request: DeviationRequest) => boolean | void
    • Return true to approve
    • Return false to deny
    • Return nothing to leave pending (controller decides via UI)

DeviationRequest Object:

javascript
{
  id: string,              // Request ID
  callsign: string,        // Aircraft callsign
  direction: string,       // "left" | "right"
  deviationNm: number,     // Requested deviation distance
  reason: string,          // Pilot phraseology
  weatherCellId: string    // ID of threatening weather cell
}

Example:

javascript
// Auto-approve all deviation requests
onDeviationRequest((request) => {
  log(`${request.callsign} requesting ${request.direction} deviation ${request.deviationNm}nm`);
  log(`Reason: ${request.reason}`);
  return true; // Approve
});

// Conditional approval based on traffic
onDeviationRequest((request) => {
  const ac = traffic.byCallsign(request.callsign);
  if (!ac) return false;

  // Check if deviation conflicts with other traffic
  const conflicting = traffic.all().some(other => {
    if (other.cs === ac.cs) return false;
    const lateral = distance(ac.pos, other.pos);
    return lateral < 10 && Math.abs(ac.altFt - other.altFt) < 2000;
  });

  if (conflicting) {
    log(`Denying ${request.callsign} deviation - traffic`);
    return false;
  }

  return true;
});

IAS vs Ground Speed

Aircraft have both indicated airspeed (IAS) and ground speed (GS):

  • IAS (iasKts) - What the pilot flies, displayed in aircraft panel
  • GS (gsKts) - Actual speed over ground, affected by wind
javascript
onTick(({ traffic, weather }) => {
  traffic.all().forEach(ac => {
    const wind = weather.windAt(ac.altFt);
    log(`${ac.cs}: IAS ${ac.iasKts}kts, GS ${ac.gsKts}kts`);
    log(`  Wind: ${wind.dirDeg}° at ${wind.kts}kts`);
  });
});

Sectors API

Access via sectors object in onTick context.

sectors.currentOf(callsign)

Get current sector of aircraft.

Returns: string | undefined

Example:

javascript
onTick(({ sectors, traffic }) => {
  traffic.all().forEach(ac => {
    const sector = sectors.currentOf(ac.cs);
    log(`${ac.cs} in sector ${sector}`);
  });
});

Fixes API

Access via fixes object in onTick context.

fixes.get(fixName)

Get waypoint by name.

Returns: {name: string, pos: {x, y}} | undefined

Example:

javascript
onTick(({ fixes, traffic }) => {
  const kmart = fixes.get("KMART");
  if (kmart) {
    log(`KMART at ${kmart.pos.x.toFixed(1)}, ${kmart.pos.y.toFixed(1)}`);

    // Calculate distance from aircraft
    const ac = traffic.byCallsign("AAL123");
    if (ac) {
      const dist = distance(ac.pos, kmart.pos);
      log(`Distance to KMART: ${dist.toFixed(1)}nm`);
    }
  }
});

Data Types

Vec2

Position in nautical miles.

javascript
{
  x: number,  // East-west (positive = east)
  y: number   // North-south (positive = north)
}

WakeCat

Wake turbulence category.

javascript
"L" | "M" | "H" | "J"
  • L: Light (< 15,500 lbs)
  • M: Medium (15,500 - 300,000 lbs)
  • H: Heavy (> 300,000 lbs)
  • J: Super (A380, etc.)

ResolutionManeuver

A maneuver to resolve a conflict. One of four types:

javascript
// Vertical - climb to target altitude
{ type: "climb", targetAltFt: number }

// Vertical - descend to target altitude
{ type: "descend", targetAltFt: number }

// Lateral - turn left or right
{ type: "turn", direction: "left" | "right", degrees: number, targetHdgDeg: number }

// Speed - adjust speed
{ type: "speed", targetKts: number }

ResolutionAdvisory

A suggested maneuver to resolve a conflict.

javascript
{
  callsign: string,              // Aircraft this advisory applies to
  resolutionType: string,        // "vertical" | "lateral" | "speed"
  maneuver: ResolutionManeuver,  // The specific maneuver
  effectiveness: number,         // 0-1 score (1 = fully resolves conflict)
  projectedSeparationNm: number, // Lateral separation if followed
  projectedSeparationFt: number, // Vertical separation if followed
  timeToEffect: number           // Seconds until separation improves
}

Effectiveness scoring:

  • 1.0 = Fully resolves conflict (achieves standard separation)
  • 0.7-0.9 = Significantly improves separation
  • 0.3-0.7 = Moderate improvement
  • < 0.3 = Minimal improvement

Resolution priority:

  1. Vertical - Preferred; fastest effect, cleaner separation
  2. Lateral - Used when vertical isn't effective
  3. Speed - Last resort; takes longest to achieve separation

ConflictAdvisories

Resolution options for a conflict pair.

javascript
{
  forA: ResolutionAdvisory[],       // Advisories for aircraft A
  forB: ResolutionAdvisory[],       // Advisories for aircraft B
  recommended: {                    // Best overall option
    callsign: string,
    advisory: ResolutionAdvisory
  } | null,
  available: boolean                // Whether any resolution is possible
}

Utility Functions

distance(a, b)

Calculate distance between two positions.

Parameters:

  • a: Vec2 - First position
  • b: Vec2 - Second position

Returns: number - Distance in nautical miles

Example:

javascript
const ac1 = traffic.byCallsign("AAL123");
const ac2 = traffic.byCallsign("DAL456");

if (ac1 && ac2) {
  const dist = distance(ac1.pos, ac2.pos);
  log(`Separation: ${dist.toFixed(1)}nm`);

  if (dist < 5) {
    log("Losing lateral separation!");
  }
}

headingTo(from, to)

Calculate heading from one position to another.

Parameters:

  • from: Vec2 - Starting position
  • to: Vec2 - Target position

Returns: number - Heading in degrees (0-360)

Example:

javascript
const ac = traffic.byCallsign("AAL123");
const fix = fixes.get("KMART");

if (ac && fix) {
  const hdg = headingTo(ac.pos, fix.pos);
  log(`Heading to KMART: ${hdg.toFixed(0)}°`);

  // Check if aircraft is on course
  const hdgDiff = Math.abs(ac.hdgDeg - hdg);
  if (hdgDiff > 10) {
    log(`${ac.cs} is ${hdgDiff.toFixed(0)}° off course`);
  }
}

flightlevel(fl) / fl(fl)

Convert flight level to feet.

Parameters:

  • fl: number - Flight level (e.g., 350 for FL350)

Returns: number - Altitude in feet

Example:

javascript
// These are equivalent:
aircraft.climb(flightlevel(350));
aircraft.climb(fl(350));  // Shorter alias
aircraft.climb(35000);    // Direct feet

// Useful for calculations
const targetFL = 350;
aircraft.climb(fl(targetFL));  // 35,000 feet

Activity Panel Functions

log(message)

Output messages to the activity panel.

Parameters:

  • message: string - Message to display

Examples:

javascript
log("Normal message");
log(`Aircraft ${ac.cs} at FL${ac.altFL}`);
log(`Warning: Conflict detected`);
log(`Separation: ${dist.toFixed(1)}nm`);

Emergency API

The emergency system allows scripted handling of in-flight emergencies. See Emergencies for full documentation.

Event Handlers

onEmergencyDeclared(callback)

Called when a pilot declares an emergency.

Parameters:

  • callback: (event: EmergencyDeclarationEvent) => void

EmergencyDeclarationEvent Object:

javascript
{
  emergency: Emergency,    // Emergency object
  pilotMessage: string     // Full pilot declaration
}

Example:

javascript
onEmergencyDeclared((event) => {
  log(`EMERGENCY: ${event.emergency.callsign}`);
  log(`Type: ${event.emergency.type}`);
  log(`Severity: ${event.emergency.severity}`);
  log(`Nature: ${event.emergency.nature}`);
  log(`Pilot says: ${event.pilotMessage}`);
});

onEmergencyUpdate(callback)

Called when an emergency status changes.

Parameters:

  • callback: (event: EmergencyUpdateEvent) => void

EmergencyUpdateEvent Object:

javascript
{
  emergency: Emergency,      // Emergency object
  callsign: string,          // Aircraft callsign
  previousStatus: string,    // Previous status
  newStatus: string          // New status
}

Example:

javascript
onEmergencyUpdate((event) => {
  log(`${event.callsign}: ${event.previousStatus} -> ${event.newStatus}`);
});

onEmergencyResolved(callback)

Called when an emergency is resolved.

Parameters:

  • callback: (event: EmergencyResolvedEvent) => void

EmergencyResolvedEvent Object:

javascript
{
  emergency: Emergency,        // Emergency object
  resolutionTimeMs: number     // Time to resolve (milliseconds)
}

Example:

javascript
onEmergencyResolved((event) => {
  const seconds = Math.round(event.resolutionTimeMs / 1000);
  log(`${event.emergency.callsign} resolved in ${seconds}s`);
});

Emergency Methods

center.declareEmergency(callsign, type)

Trigger an emergency for a specific aircraft.

Parameters:

  • callsign: string - Aircraft callsign
  • type: string - Emergency type: 'decompression', 'engine_failure', 'medical', 'fuel', 'fire'

Returns: Emergency | undefined

Example:

javascript
const emergency = center.declareEmergency('QFA3', 'decompression');

center.acknowledgeEmergency(emergencyId)

Acknowledge an emergency.

Parameters:

  • emergencyId: string - Emergency ID

Returns: boolean - True if acknowledged, false if already acknowledged

Example:

javascript
center.acknowledgeEmergency(emergency.id);

center.getActiveEmergencies()

Get all active emergencies.

Returns: Emergency[]

Example:

javascript
const emergencies = center.getActiveEmergencies();
emergencies.forEach(e => log(`${e.callsign}: ${e.status}`));

center.getEmergencyForAircraft(callsign)

Get emergency for a specific aircraft.

Parameters:

  • callsign: string - Aircraft callsign

Returns: Emergency | undefined

Example:

javascript
const emg = center.getEmergencyForAircraft('QFA3');
if (emg) {
  log(`${emg.callsign} has ${emg.type} emergency`);
}

center.setEmergencyFrequency(chancePerMinute)

Set random emergency frequency.

Parameters:

  • chancePerMinute: number - Chance per minute per aircraft (0-1)

Example:

javascript
center.setEmergencyFrequency(0.02);  // 2% chance per minute
center.setEmergencyFrequency(0);     // Disable random emergencies

Emergency Object

javascript
{
  id: string,                    // Unique emergency ID
  type: string,                  // decompression, engine_failure, medical, fuel, fire
  severity: string,              // MAYDAY or PAN_PAN
  callsign: string,              // Aircraft callsign
  nature: string,                // Human-readable (e.g., "Rapid decompression")
  status: string,                // declared, acknowledged, descending, resolved
  initialAltitude: number,       // Altitude when declared (feet)
  requestedAction: {
    targetAltitude?: number      // Target altitude for descent
  },
  declaredAt: number,            // Timestamp when declared
  acknowledgedAt?: number,       // Timestamp when acknowledged
  resolvedAt?: number            // Timestamp when resolved
}

Approach Control API

The approach control system handles ILS approaches, tower handoffs, and go-arounds for arriving aircraft.

Real-world usage: Approach control vectors aircraft to intercept the ILS localizer, then clears them for the approach. Once established, aircraft are handed off to tower for landing clearance. If unable to land safely, pilots execute a go-around and are re-sequenced.

Approach Methods

center.clearILS(callsign, runwayId)

Clear aircraft for ILS approach to specified runway.

Parameters:

  • callsign: string - Aircraft callsign
  • runwayId: string - Runway identifier (e.g., '27L', '09', '4R')

Returns: boolean - True if clearance was successful

Example:

javascript
// Clear aircraft for ILS runway 27L
const cleared = center.clearILS('DAL456', '27L');
if (cleared) {
  log('DAL456 cleared ILS runway 27L');
}

// Sequence arrivals for ILS approach
onSpawn((aircraft) => {
  // Check if this is an arrival (has destination airport)
  if (aircraft.plan.destination) {
    // Vector to final approach
    aircraft.before('FINAL', 10, (ac) => {
      center.clearILS(ac.cs, '27L');
      log(`${ac.cs} cleared ILS 27L`);
    });
  }
});

Approach sequence

  1. Vector aircraft to intercept the localizer (typically 30° intercept angle)
  2. Issue ILS clearance when aircraft is established or intercepting
  3. Aircraft automatically follows localizer and glideslope
  4. Contact tower when aircraft is on final

center.contactTower(callsign)

Hand off aircraft to tower frequency. After this, the aircraft is no longer controllable and will land automatically.

Parameters:

  • callsign: string - Aircraft callsign

Example:

javascript
// Hand off to tower when established on final
center.clearILS('DAL456', '27L');

// Later, when on final approach
center.contactTower('DAL456');
log('DAL456 contact tower');

Important Notes:

  • Aircraft must be cleared for ILS before contacting tower
  • Once handed to tower, the aircraft cannot receive further ATC commands
  • Aircraft will automatically land and despawn after touchdown

center.goAround(callsign)

Instruct aircraft to execute a go-around (missed approach).

Parameters:

  • callsign: string - Aircraft callsign

Example:

javascript
// Go-around due to runway occupied
if (center.isRunwayProtected('KJFK', '27L')) {
  center.goAround('DAL456');
  log('DAL456 go around, runway occupied');
}

// Go-around and re-sequence
center.goAround('AAL123');
// Aircraft will climb and can be re-vectored for another approach

Real-world usage: Go-arounds occur for runway incursions, unstable approaches, wake turbulence, or traffic on the runway. The aircraft climbs to the missed approach altitude and is re-sequenced for another approach attempt.


center.getApproachState(callsign)

Get the current approach state of an aircraft.

Parameters:

  • callsign: string - Aircraft callsign

Returns: ApproachState | undefined

ApproachState Values:

  • 'vectoring' - Being vectored to intercept localizer
  • 'intercepting' - Turning to intercept localizer
  • 'established' - On localizer, above glideslope
  • 'final' - On glideslope, descending
  • 'landing' - Below 200ft AGL, committed to landing

Example:

javascript
onTick(({ traffic }) => {
  traffic.all().forEach(ac => {
    const state = center.getApproachState(ac.cs);
    if (state) {
      log(`${ac.cs} approach state: ${state}`);

      // Hand to tower when on final
      if (state === 'final') {
        center.contactTower(ac.cs);
      }
    }
  });
});

center.isOnApproach(callsign)

Check if aircraft is currently on an ILS approach.

Parameters:

  • callsign: string - Aircraft callsign

Returns: boolean - True if aircraft is on approach

Example:

javascript
const ac = traffic.byCallsign('DAL456');
if (ac && center.isOnApproach(ac.cs)) {
  log(`${ac.cs} is on approach`);
  // Don't issue altitude/heading commands to aircraft on approach
}

center.isRunwayProtected(airportIcao, runwayId)

Check if a runway is currently protected (occupied or has traffic on final).

Parameters:

  • airportIcao: string - Airport ICAO code (e.g., 'KJFK')
  • runwayId: string - Runway identifier (e.g., '27L')

Returns: boolean - True if runway is protected

Example:

javascript
// Check before clearing for approach
if (!center.isRunwayProtected('KJFK', '27L')) {
  center.clearILS('DAL456', '27L');
} else {
  // Hold or delay the approach
  log('Runway 27L protected, holding DAL456');
}

Flow Control API

Flow control methods for managing traffic sequencing, holding patterns, and scheduled times of arrival.

center.holdAt(callsign, fix, options)

Instruct aircraft to hold at a waypoint.

Parameters:

  • callsign: string - Aircraft callsign
  • fix: string - Waypoint name to hold at
  • options: object
    • legNm: number - Holding leg length in nautical miles (default: 4)
    • turn: 'L' | 'R' - Turn direction (default: 'R' per ICAO standard)
    • efc?: number - Expected further clearance time (epoch seconds, optional)

Example:

javascript
// Standard hold at KMART (right turns, 4nm legs)
center.holdAt('AAL123', 'KMART', { legNm: 4, turn: 'R' });

// Left turns with 10nm legs
center.holdAt('DAL456', 'ROBUC', { legNm: 10, turn: 'L' });

// Hold with expected further clearance time
const efcTime = ctx.time + 600; // 10 minutes from now
center.holdAt('UAL789', 'MERGE', { legNm: 4, turn: 'R', efc: efcTime });
log(`UAL789 hold at MERGE, EFC ${efcTime}`);

Real-world usage: Holding patterns are used to delay aircraft when arrival demand exceeds capacity. Standard ICAO holds use right turns and 1-minute legs (approximately 4nm at typical holding speeds). Left turns are used when required by terrain, airspace, or traffic flow.


center.makeSTA(callsign, fix, epochSec)

Assign a scheduled time of arrival (STA) at a waypoint. The aircraft will automatically adjust speed to cross the fix at the specified time.

Parameters:

  • callsign: string - Aircraft callsign
  • fix: string - Waypoint name
  • epochSec: number - Target crossing time (simulation epoch seconds)

Example:

javascript
// Schedule aircraft to cross MERGE at a specific time
const targetTime = ctx.time + 300; // 5 minutes from now
center.makeSTA('AAL123', 'MERGE', targetTime);
log(`AAL123 cross MERGE at ${targetTime}`);

// Space arrivals 2 minutes apart at meter fix
onSpawn((aircraft) => {
  const meterFix = 'MERGE';
  const interval = 120; // 2 minutes between arrivals

  // Get existing STAs to find next available slot
  // (simplified - in practice, track scheduled times)
  const baseTime = ctx.time + 300;
  const slot = baseTime + (arrivalCount * interval);
  arrivalCount++;

  center.makeSTA(aircraft.cs, meterFix, slot);
  log(`${aircraft.cs} STA at ${meterFix}: ${slot}`);
});

Real-world usage: Time-based metering (TBM) assigns aircraft scheduled times at meter fixes to smooth arrival flow. Aircraft adjust speed to meet their assigned time, reducing the need for holding and improving efficiency.


TTS API

The text-to-speech system provides voice synthesis for radio communications. See Audio & Radio Effects for full documentation.

ttsService.speak(request)

Trigger custom speech.

Parameters:

  • request: SpeechRequest

SpeechRequest Object:

javascript
{
  type: 'atc' | 'pilot',     // Speaker type
  text: string,               // Text to speak
  callsign?: string,          // Pilot callsign (for voice selection)
  priority?: 'normal' | 'high', // Queue priority
  onStart?: () => void,       // Called when speech starts
  onEnd?: () => void          // Called when speech ends
}

Example:

javascript
// Speak as ATC
ttsService.speak({
  type: 'atc',
  text: 'All aircraft, expect delays due to weather'
});

// Speak as pilot
ttsService.speak({
  type: 'pilot',
  text: 'Roger, holding as published',
  callsign: 'DAL456'
});

ttsService.setSettings(settings)

Configure TTS settings.

Parameters:

  • settings: Partial<TTSSettings>

TTSSettings Object:

javascript
{
  enabled: boolean,         // Master on/off (default: false)
  volume: number,           // Volume 0-1 (default: 0.8)
  speechRate: number,       // Speed 0.5-2 (default: 1.1)
  radioEffects: boolean,    // PTT click sounds (default: false)
  pilotReadbacks: boolean,  // Speak pilot responses (default: true)
  atcVoiceName?: string     // Preferred ATC voice
}

Example:

javascript
ttsService.setSettings({
  enabled: true,
  volume: 0.7,
  speechRate: 1.2,
  radioEffects: true
});

ttsService.getSettings()

Get current TTS settings.

Returns: TTSSettings

Example:

javascript
const settings = ttsService.getSettings();
log(`TTS enabled: ${settings.enabled}`);
log(`Radio effects: ${settings.radioEffects}`);

ttsService.toggle()

Toggle TTS on/off.

Returns: boolean - New enabled state

Example:

javascript
const nowEnabled = ttsService.toggle();
log(`TTS is now ${nowEnabled ? 'on' : 'off'}`);

ttsService.getEnglishVoices()

Get available English voices.

Returns: TTSVoiceInfo[]

Example:

javascript
const voices = ttsService.getEnglishVoices();
voices.forEach(v => log(`Voice: ${v.name}`));

Queue Control

javascript
// Check queue status
const queueLength = ttsService.getQueueLength();
const isSpeaking = ttsService.isSpeaking();

// Control playback
ttsService.pause();   // Pause current speech
ttsService.resume();  // Resume playback
ttsService.stop();    // Stop and clear queue

// Reset (clears pilot voice assignments)
ttsService.reset();

Tips & Best Practices

Separation Management

javascript
// Check both lateral AND vertical separation
const isSeparated = (a, b) => {
  const lateral = distance(a.pos, b.pos);
  const vertical = Math.abs(a.altFt - b.altFt);
  return lateral >= 5 || vertical >= 1000;
};

Efficient Querying

javascript
// Cache frequently used values
onTick(({ traffic }) => {
  const flights = traffic.all(); // Call once, reuse

  flights.forEach(ac => {
    // Process aircraft
  });
});

Method Chaining

javascript
// Issue multiple clearances together
onSpawn((ac) => {
  ac
    .climb(fl(ac.plan.cruiseFL))
    .speed(400)
    .direct(ac.plan.exitPoint);
});

Error Handling

javascript
onConflict((pair) => {
  const acA = traffic.byCallsign(pair.a);
  const acB = traffic.byCallsign(pair.b);

  if (!acA || !acB) {
    log(`Missing aircraft in conflict: ${pair.a}, ${pair.b}`);
    return;
  }

  // Resolve conflict...
});

Using Flight Levels

javascript
// Use FL properties directly
log(`Aircraft at FL${ac.altFL}`);
log(`Target: FL${ac.targetFL}`);
log(`Cruise: FL${ac.plan.cruiseFL}`);

// Use fl() helper for commands
ac.climb(fl(350));  // Much clearer than 35000

Complete Example Script

javascript
// Basic traffic management

onTick(({ traffic, time }) => {
  const flights = traffic.all();

  // Maintain spacing with speed control
  for (let i = 0; i < flights.length - 1; i++) {
    const lead = flights[i];
    const trail = flights[i + 1];

    const dist = distance(trail.pos, lead.pos);

    if (dist < 8) {
      // Too close - slow down trailing aircraft
      trail.speed(Math.max(lead.targetIASKts - 30, 320));
    } else if (dist > 12) {
      // Too far - speed up trailing aircraft
      trail.speed(Math.min(trail.targetIASKts + 20, 450));
    }
  }

  // Ensure vertical separation if lateral is tight
  for (let i = 0; i < flights.length - 1; i++) {
    const a = flights[i];
    const b = flights[i + 1];

    const lateral = distance(a.pos, b.pos);
    const vertical = Math.abs(a.altFt - b.altFt);

    if (lateral < 5 && vertical < 1000) {
      a.climb(a.altFt + 2000);
    }
  }
});

onConflict((pair) => {
  log(`Conflict: ${pair.a} and ${pair.b}`);

  const acA = traffic.byCallsign(pair.a);
  const acB = traffic.byCallsign(pair.b);

  if (!acA || !acB) return;

  // Resolve with vertical separation
  const verticalSep = Math.abs(acA.altFt - acB.altFt);
  if (verticalSep < 1000) {
    if (acA.altFt > acB.altFt) {
      acA.climb(acA.altFt + 2000);
    } else {
      acB.climb(acB.altFt + 2000);
    }
  }
});

onSpawn((aircraft) => {
  log(`${aircraft.cs} (${aircraft.type}) at FL${aircraft.altFL}`);
  log(`Route: ${aircraft.plan.route.join('-')}`);
  log(`Exit: ${aircraft.plan.exitPoint} at FL${aircraft.plan.exitFL}`);

  // Set up handover at exit fix
  aircraft.over(aircraft.plan.exitPoint, (ac) => {
    ac.handover();
  });

  // Ensure aircraft reaches exit altitude
  if (aircraft.plan.exitAltitude) {
    aircraft.reach(aircraft.plan.exitPoint).altitude(aircraft.plan.exitAltitude);
  }

  // Initial clearances
  aircraft
    .climb(fl(aircraft.plan.cruiseFL))
    .speed(420);
});