Skip to content

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:

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:

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.


onApproachCleared(callback)

Called when an aircraft is cleared for an ILS approach.

Parameters:

  • callback: (event: { cs: string, airport: string, runway: string }) => void

onILSEstablished(callback)

Called when an aircraft establishes on the localizer.

Parameters:

  • callback: (event: { cs: string, runway: string }) => void

onTowerContact(callback)

Called when an aircraft is handed off to tower.

Parameters:

  • callback: (event: { cs: string, runway: string }) => void

onLanding(callback)

Called when an aircraft touches down.

Parameters:

  • callback: (event: { cs: string, airport: string, runway: string }) => void

onGoAround(callback)

Called when an aircraft executes a go-around.

Parameters:

  • callback: (event: { cs: string, reason: string }) => void

onUnableILS(callback)

Called when an aircraft cannot accept an ILS clearance.

Parameters:

  • callback: (event: { cs: string, runway: string, reason: string }) => void

Reason values: 'heading', 'too_close', 'too_far', 'too_high', 'wrong_side', 'no_ils'


onAircraftRemoved(callback)

Called when an aircraft leaves the simulation.

Parameters:

  • callback: (event: { callsign: string, reason: string }) => void

onHandoffOffered(callback)

Called when a handoff is initiated to an adjacent sector.

Parameters:

  • callback: (event: { cs: string, targetSector: string }) => void

onHandoffAccepted(callback)

Called when an adjacent sector accepts the handoff.

Parameters:

  • callback: (event: { cs: string, targetSector: string }) => void

onHandoffRejected(callback)

Called when an adjacent sector rejects the handoff.

Parameters:

  • callback: (event: { cs: string, targetSector: string, reason?: string }) => void

onHandoffFrequencyChange(callback)

Called when the pilot switches frequency during handoff.

Parameters:

  • callback: (event: { cs: string }) => void

onHandoffTransferred(callback)

Called when a handoff is fully completed.

Parameters:

  • callback: (event: { cs: string, targetSector: string }) => void

onApproachingExit(callback)

Called when an aircraft is approaching its exit fix.

Parameters:

  • callback: (event: { cs: string, exitFix: string, distanceNm: number }) => void

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;
});

onConstraintSatisfied(callback)

Called when an aircraft crosses a procedure constraint fix and meets the altitude/speed requirement.

Parameters:

  • callback: (event) => void

Event object:

javascript
{
  cs: string,            // Aircraft callsign
  fix: string,           // Waypoint name (e.g., "GREKI")
  procedureId: string,   // Procedure name (e.g., "PARCH4")
  altFt: number          // Altitude at crossing
}

Example:

javascript
onConstraintSatisfied((e) => {
  log(`${e.cs} met constraint at ${e.fix} (${e.procedureId}) - ${Math.round(e.altFt)}ft`);
});

onConstraintBusted(callback)

Called when an aircraft crosses a procedure constraint fix and fails to meet the altitude/speed requirement.

Parameters:

  • callback: (event) => void

Event object:

javascript
{
  cs: string,            // Aircraft callsign
  fix: string,           // Waypoint name
  procedureId: string,   // Procedure name
  altFt: number,         // Altitude at crossing
  reason: string         // Why it was busted (e.g., "Expected at or below 24000ft, was 26000ft")
}

Example:

javascript
onConstraintBusted((e) => {
  log(`${e.cs} BUSTED constraint at ${e.fix}: ${e.reason}`);
});

onProcedureComplete(callback)

Called when all constraints on a procedure have been resolved (either satisfied or busted). Use this to track overall procedure compliance.

Parameters:

  • callback: (event) => void

Event object:

javascript
{
  cs: string,            // Aircraft callsign
  procedureId: string,   // Procedure name
  satisfied: number,     // Number of constraints met
  busted: number         // Number of constraints busted
}

Example:

javascript
onProcedureComplete((e) => {
  if (e.busted === 0) {
    log(`${e.cs} completed ${e.procedureId} perfectly (${e.satisfied}/${e.satisfied} constraints met)`);
  } else {
    log(`${e.cs} completed ${e.procedureId} with ${e.busted} busted constraints`);
  }
});