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
- Aircraft Control API
- Traffic Management
- Weather API
- Sectors API
- Fixes API
- Data Types
- Utility Functions
- Activity Panel Functions
- Emergency API
- Approach Control API
- Flow Control API
- TTS API
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):
{
time: number, // Simulation time (seconds)
dt: number, // Time step (seconds, usually 0.5)
traffic: TrafficView,
weather: WeatherView,
sectors: SectorsView,
fixes: FixesView
}Example:
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:
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:
{
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:
{
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:
{
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:
// 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:
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:
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:
// 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:
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:
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:
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:
- Takeoff - Accelerating on runway
- Departure - Climbing (NOT controllable until 10,000ft)
- 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 usefl()helper for flight levels
Returns: this (for method chaining)
Examples:
// 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 usefl()helper)
Returns: this (for method chaining)
Examples:
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:
// 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]
- Route
- Single waypoint not in route: Replaces entire route with just that waypoint
- Route
[A, B, C, D]→direct(E)→[E]
- Route
- 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]
- Route
Examples:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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 namecallback: (ac: Aircraft) => void- Function called when aircraft reaches waypoint
Returns: this (for method chaining)
Examples:
// 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 namedistanceNm: number- Distance before waypoint (nm)callback: (ac: Aircraft) => void- Function called when aircraft is within distance
Returns: this (for method chaining)
Examples:
// 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:
// 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 usefl()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:
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:
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:
{
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:
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:
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[]
{
id: string, // Unique cell ID
center: {lat, lon}, // Geographic position
radiusNm: number, // Cell radius in nautical miles
intensity: string // "light" | "moderate" | "heavy" | "extreme"
}Example:
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:
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
trueto approve - Return
falseto deny - Return nothing to leave pending (controller decides via UI)
- Return
DeviationRequest Object:
{
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:
// 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
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:
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:
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.
{
x: number, // East-west (positive = east)
y: number // North-south (positive = north)
}WakeCat
Wake turbulence category.
"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:
// 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.
{
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:
- Vertical - Preferred; fastest effect, cleaner separation
- Lateral - Used when vertical isn't effective
- Speed - Last resort; takes longest to achieve separation
ConflictAdvisories
Resolution options for a conflict pair.
{
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 positionb: Vec2- Second position
Returns: number - Distance in nautical miles
Example:
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 positionto: Vec2- Target position
Returns: number - Heading in degrees (0-360)
Example:
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:
// 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 feetActivity Panel Functions
log(message)
Output messages to the activity panel.
Parameters:
message: string- Message to display
Examples:
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:
{
emergency: Emergency, // Emergency object
pilotMessage: string // Full pilot declaration
}Example:
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:
{
emergency: Emergency, // Emergency object
callsign: string, // Aircraft callsign
previousStatus: string, // Previous status
newStatus: string // New status
}Example:
onEmergencyUpdate((event) => {
log(`${event.callsign}: ${event.previousStatus} -> ${event.newStatus}`);
});onEmergencyResolved(callback)
Called when an emergency is resolved.
Parameters:
callback: (event: EmergencyResolvedEvent) => void
EmergencyResolvedEvent Object:
{
emergency: Emergency, // Emergency object
resolutionTimeMs: number // Time to resolve (milliseconds)
}Example:
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 callsigntype: string- Emergency type:'decompression','engine_failure','medical','fuel','fire'
Returns: Emergency | undefined
Example:
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:
center.acknowledgeEmergency(emergency.id);center.getActiveEmergencies()
Get all active emergencies.
Returns: Emergency[]
Example:
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:
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:
center.setEmergencyFrequency(0.02); // 2% chance per minute
center.setEmergencyFrequency(0); // Disable random emergenciesEmergency Object
{
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 callsignrunwayId: string- Runway identifier (e.g.,'27L','09','4R')
Returns: boolean - True if clearance was successful
Example:
// 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
- Vector aircraft to intercept the localizer (typically 30° intercept angle)
- Issue ILS clearance when aircraft is established or intercepting
- Aircraft automatically follows localizer and glideslope
- 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:
// 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:
// 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 approachReal-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:
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:
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:
// 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 callsignfix: string- Waypoint name to hold atoptions: objectlegNm: 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:
// 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 callsignfix: string- Waypoint nameepochSec: number- Target crossing time (simulation epoch seconds)
Example:
// 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:
{
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:
// 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:
{
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:
ttsService.setSettings({
enabled: true,
volume: 0.7,
speechRate: 1.2,
radioEffects: true
});ttsService.getSettings()
Get current TTS settings.
Returns: TTSSettings
Example:
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:
const nowEnabled = ttsService.toggle();
log(`TTS is now ${nowEnabled ? 'on' : 'off'}`);ttsService.getEnglishVoices()
Get available English voices.
Returns: TTSVoiceInfo[]
Example:
const voices = ttsService.getEnglishVoices();
voices.forEach(v => log(`Voice: ${v.name}`));Queue Control
// 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
// 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
// Cache frequently used values
onTick(({ traffic }) => {
const flights = traffic.all(); // Call once, reuse
flights.forEach(ac => {
// Process aircraft
});
});Method Chaining
// Issue multiple clearances together
onSpawn((ac) => {
ac
.climb(fl(ac.plan.cruiseFL))
.speed(400)
.direct(ac.plan.exitPoint);
});Error Handling
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
// 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 35000Complete Example Script
// 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);
});