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
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)
}Example:
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);
}
}
}
});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));
}
}
}
});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
gsKts: number, // Ground speed (knots)
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.
weather.windAt(altitude)
Get wind at specified altitude.
Parameters:
altitude: number- Altitude in feet
Returns: {dirDeg: number, kts: number}
Example:
onTick(({ weather, traffic }) => {
const wind35000 = weather.windAt(35000);
log(`Wind at FL350: ${wind35000.dirDeg}° at ${wind35000.kts}kts`);
// Adjust for headwind/tailwind
traffic.all().forEach(ac => {
const wind = weather.windAt(ac.altFt);
// Wind affects ground speed calculation
});
});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.)
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`);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);
});