Arrivals & Parking
traffic.ArrivalController lands one AI aircraft and taxis it to a stand: spawn on final, approach, touchdown, rollout to a runway exit, a stop clear of the runway, taxi-in along a route from pkg/airport and parking on the stop mark. It is the counterpart of the departure TaxiController and shares its injected ground driving (Injected Ground Movement).
Three modes
| Mode | Option | Who flies / drives |
|---|---|---|
| MSFS AI | — | MSFS AI flies one waypoint chain from final to the vacate stop, then a taxi-in chain to the stand |
| Hybrid | ArrivalWithInjector(inj) |
MSFS AI flies and lands; the injector takes over on the runway (≤ 70 kt) or, failing that, clear of it, and drives the rollout, exit, taxi-in and parking |
| Injected | ArrivalWithInjector(inj) + ArrivalRequest.InjectApproach |
The injector flies the approach, flare and touchdown too; MSFS AI never flies the aircraft |
Only the injected modes keep the lights as set, drive smooth turns and stop exactly on the mark; MSFS AI switches lights itself and taxis at 6–9 kt. InjectApproach without an injector fails Start with ErrBadTaxiRequest.
Lifecycle
Start ─► spawning ─► approaching ─► landing ─► rollout ─► vacating ─► awaiting taxi ─► taxiing ─► parking ─► parked
▲ │ ▲
ClearToTaxi / ClearUpTo ▼ │ ClearToCross
holding short
Cancel (any time) ─► cancelled
errors ─► failed
| State | Meaning |
|---|---|
ArrivalSpawning |
AICreateNonATCAircraft sent, waiting for the object ID |
ArrivalApproaching |
On final |
ArrivalLanding |
Below LandingAGLFt (200 ft) |
ArrivalRollout |
On the runway after touchdown; Touchdown and TouchdownFpm are set |
ArrivalVacating |
Off the runway, rolling to the vacate stop |
ArrivalAwaitingTaxi |
Stopped clear of the runway, waiting for the taxi clearance or the after-landing dwell |
ArrivalTaxiing |
Taxiing to the stand |
ArrivalHoldingShort |
Stopped short of a runway crossing (HoldingShortOf), waiting for ClearToCross |
ArrivalParking |
On the last stretch onto the stand |
ArrivalParked |
Stopped on the stand; the controller stops tracking the aircraft |
ArrivalCancelled / ArrivalFailed |
Terminal. ErrNoTouchdown if the aircraft passes the runway end + 300 m without touching down |
ArrivalState.Terminal() is true from ArrivalParked on, and Events() is closed then.
Usage
fleet := traffic.NewFleet(client)
inj := traffic.NewInjector(client)
ctl := traffic.NewArrivalController(fleet, traffic.ArrivalWithInjector(inj))
g, _ := cache.Graph("LKPR")
stand, _ := g.Layout.ParkingIndex("C22")
err := ctl.Start(traffic.ArrivalRequest{
Graph: g,
Runway: "24",
Parking: stand,
Model: "FSLTL A320 Air France SL", // container title of an installed aircraft
Tail: "CSA456",
InjectApproach: true, // fly the approach by injection too
HoldForClearance: true, // wait clear of the runway for ClearToTaxi / ClearUpTo
HoldAtCrossings: true, // stop short of every runway crossing
})
plan := ctl.Plan() // exit, taxi-in route, vacate stop, stop mark
for {
select {
case msg := <-client.Stream():
if ok, err := inj.Handle(msg); ok {
if err != nil { log.Print(err) }
continue
}
ctl.Handle(msg)
case ev, ok := <-ctl.Events():
if !ok {
return // parked, cancelled or failed
}
switch ev.State {
case traffic.ArrivalAwaitingTaxi:
ctl.ClearToTaxi() // or ctl.ClearUpTo(node) for a progressive taxi
case traffic.ArrivalHoldingShort:
ctl.ClearToCross()
}
}
}
ArrivalEvent carries the state, object ID, position, height above ground (AGL), heading, ground speed, on-ground flag, Touchdown (metres past the threshold) and TouchdownFpm, distance Remaining to the stand, the current Taxiway, HoldingShortOf, the progressive-taxi LimitNode / AtLimit, the Lights the sim reports, and Err. From touchdown (hybrid) or from the start (injected) the aircraft is read every sim frame; progress events are then limited to one a second, while state and light changes are always sent. A warning with ErrTaxiStuck is sent if the aircraft stands still for StuckTimeout (90 s) on the ground.
ArrivalRequest options not shown above: Livery, SpawnNm (default DefaultSpawnNm, 5 nm; below 2 nm the default is used), Exit (force a *airport.RunwayExit), Options (airport.RouteOptions; Via and Taxiways give a custom taxi-in), NoseOffset, AfterLandingDwell, RollThroughChance, Profile (MotionProfile), Rollout (RolloutProfile), Approach (ApproachProfile), Procedure (see STAR and approach), Aircraft (an AircraftProfile, see Aircraft Profiles), Airport (airport.Limits, see Airport limits), and for MSFS AI comparisons GroundAGL and NoStopWaypoint.
Cancel() removes the aircraft at any point, also once it has parked. A controller is single use; each uses 4 definition IDs and 4 request IDs (ArrivalWithIDs(defBase, reqBase), defaults 7500 / 7600). One Injector serves all controllers. The other options are ArrivalWithSeed (Natural timing), ArrivalWithGroundPicture (Ground traffic) and ArrivalWithDetail (Level of detail); for many arrivals, take the ID blocks from IDBlocks (IDs for a long session).
Plan
PlanArrival(graph, runwayEnd, parking, ArrivalOptions) builds the plan Start uses and can be called on its own (the airport map’s arrival mode does):
- Exit: among the exits the aircraft can reach at its exit speed (
Along ≥ RequiredRollout(ExitSpeed(e)): touchdownTouchdownMeters(300 m) past the threshold at 125 kt, then braking at 1.5 m/s² — about 1.6 km for a high-speed exit, 1.7 km for any other), the one with the lowest cost: taxi-in length, plus 0.3 × the distance down the runway, plus 3 m for each degree of every turn sharper than 30°. The last exit always counts as reachable. A light single (touching down below 90 kt,exitLightTouchdownKts;ArrivalOptions.TouchdownKtsandBrakeDecel) is planned to touch down 250 m in (exitLightTouchdownMeters; its injected approach crosses the threshold atLightThresholdHeightFt, 30 ft, and touches down at about 220 m) and to brake at its ownBrakeDecel, and each meter down the runway costs 0.3 × (125 kt / its touchdown speed)², about 1.5 for a C172: it takes the first exit it can make, even for a longer taxi-in (LKPR 30: R at 521 m, clear about 30 s after touchdown, not G at 2 km). - Route:
Graph.RouteFromRunway(exit, parking, opts): the exit path off the runway, then on in the direction the exit leaves it to the stand. The runway being vacated is not a crossing. - Vacate stop (
VacateIndex): just past the hold-short behind the exit when the route passes it, else the first route pointVacateOffsetMeters(100 m) from the centreline. - Stop mark (
Stop):StandPointfor the stand (see Stands). - Waypoints for MSFS AI: approach points every nautical mile on a 3° path crossing the threshold at 50 ft, an on-ground touchdown point, rollout points every 250 m slowing to the exit speed at the exit, the roll clear to the vacate stop (
Waypoints), and the taxi-in chain (TaxiWaypoints).
Runway exits
Graph.RunwayExits("24") lists the taxiways leaving a runway for landings on that end, nearest the threshold first; Graph.ExitFor("24", rollout) returns the first at least rollout metres down it (the last exit when none is).
RunwayExit field |
Meaning |
|---|---|
RunwayNode, Node, Path |
Where the exit leaves the centreline, its first node off the runway surface, the node path between |
Along |
Metres from the landing threshold to RunwayNode |
Angle, Side |
Turn-off angle from the landing direction (0–180°) and side (ExitLeft / ExitRight) |
HighSpeed |
Angle ≤ HighSpeedExitAngle (45°) |
Taxiway |
The exit’s name |
HoldShort |
Hold-short node for this runway behind the exit (within 300 m), −1 if none |
- Exits turning more than
MaxExitAngle(90°) point back towards the threshold and are left out. - Taxiway segments can run along the runway before turning off, so several runway nodes can lead to the same exit node. The shortest path is kept: its runway node is where the taxiway really leaves the centreline.
- Naming: the last named edge on the exit path. An unnamed connector takes the name of the named taxiway it continues onto most straight (EDDM: an unnamed link joins A4 to 08L/26R).
MSFS AI arrival
Without an injector, the controller spawns the aircraft on final, sets its gear handle down (without it MSFS AI never touches down), sends the landing chain, and watches it once a second:
- Vacating once the aircraft is off the runway surface (half-width +
RunwayClearMeters) near or past the planned exit. - Vacate stop: near it the chain is replaced by a single waypoint where the aircraft is, so the AI does not creep on. Once stopped within
VacateArriveMetersit waits forClearToTaxi(HoldForClearance) orAfterLandingDwell(default 15 s), then gets the taxi-in chain. - Stand: the aircraft is stopped the same way within
StandStopMeters(2 m) of the stop mark, or once it passes it. It counts as parked when stationary withinParkedMeters(15 m); stopped further away for 10 s it parks with an error giving the distance. - “Stopped” means the position moved less than 1 m in 3 s: MSFS AI keeps reporting its last commanded ground speed.
Hybrid arrival
With ArrivalWithInjector(inj) MSFS AI flies the approach and touchdown. From touchdown the aircraft is read every sim frame and the injector watches the ground height under it.
- Takeover on the runway once the aircraft has been on the ground
TakeoverAfterTouchdown(2 s), has slowed toTakeoverKts(70 kt) and is still at leastTakeoverBeforeExitMeters(150 m) before the exit. The mover starts at the nose gear with the aircraft’s heading and speed, so nothing jumps. - Takeover clear of the runway if the aircraft reaches the exit first. If the takeover fails, a warning is sent and MSFS AI carries on.
Injected approach
With InjectApproach the aircraft spawns exactly where the injected approach starts and is taken over at once: gear down (Injector.SetGear), flaps at ApproachFlapsPct (flaps 3), approach lights. ApproachMover (pure computation, ApproachProfile) flies it, and Injector.PlaceAir places each ApproachPose.
ApproachProfile |
A320 default |
|---|---|
GlideSlopeDeg / ThresholdHeightFt |
3° / 50 ft (main wheels over the threshold) |
StartKts → ApproachKts from ApproachSpeedNm |
150 → 135 kt from 1 nm, lag SpeedTimeSeconds 3 s |
TouchdownKts |
130 |
FlareFt / TouchdownFpm |
30 ft / −120 fpm |
ApproachPitchDeg / FlarePitchDeg |
2.5° / 5.5° |
DerotateSeconds / DerotateDecel |
4 s / 0.5 m/s² |
- Phases (
ApproachPhase):ApproachFinalon the glide path;ApproachFlarebelowFlareFt, sink rate easing toTouchdownFpmwhile the pitch rises;ApproachDerotateon the main wheels, the nose coming down;ApproachDonewith all wheels down. - Flaps run to full over
FlapsFullSeconds(5 s) when passingFlapsFullFt(1400 ft), set before the 1000 ft stabilised-approach gate. Once clear of the runway they retract overFlapsRetractSeconds(20 s), with the spoilers stowing and the strobes and landing lights off (the after-landing flow); the taxi light followsTaxiLightDelaylater.ArrivalController.Sequence()lists the landing step by step (takeover on final, gear, flaps, flare, touchdown, nose down, vacated) with time, height and speed. - Spoilers deploy over
SpoilerDeploySeconds(1 s) at main-gear touchdown (Injector.SetSpoilers) and stow once clear of the runway. - Hand-over: at
ApproachDonethe injected rollout continues from exactly that pose. - Thrust reversers cannot be shown on an AI aircraft.
Measured live at LKPR runway 24: touchdown 486 m past the threshold at −120 fpm.
STAR and approach
With InjectApproach and Procedure (e.g. airport.Procedures.Arrival(runway, entryFix): the STAR from its entry fix and the best approach via the transition where the STAR ends) the arrival starts in the terminal area (#315):
route, _ := procs.Arrival("06", "GOLOP") // GOLO4T → ILS 06 via KUVIX
ctl.Start(traffic.ArrivalRequest{Graph: g, Runway: "06", Parking: stand, Model: model,
InjectApproach: true, Procedure: route})
- The aircraft appears at the STAR’s first fix at 250 kt and MSFS AI flies the procedure as waypoints. Altitudes descend 3° (
ProcedureDescentFtPerNm) back from the join point, at mostProcedureTopFt(10 000 ft) unless the procedure asks for more, within every point’s constraints (the approach’s 4000 ft minimums at LKPR). Speed 250 kt, 180 kt from the IAF. - The procedure’s points on the final are replaced by two centreline points: aligned
ProcedureAlignNmbefore the join point, and the join pointProcedureJoinNm(8 NM) out. - At the join point (within
JoinCaptureMeters, or established on the centreline abeam it) the injected approach takes over from where the aircraft is; the offset between MSFS AI’s position and the injected glide path fades out overJoinBlendSeconds, so nothing jumps. - From there on it is the injected approach below.
Sequencing and delays. On its STAR the arrival can be spaced behind the others: an ApproachSequencer gives it a landing time and a delay, AbsorbDelay(delay) loses the delay by flying slower and a dog-leg, and HoldFix / EnterHold / LeaveHold send it round a hold for what is left. GoAround() sends an injected arrival on final round the circuit (ATC Commands). All of this is in Airborne Separation.
Rollout and exit
ArrivalRequest.Rollout is a RolloutProfile per aircraft type (zero = DefaultRolloutProfile(), A320). The aircraft brakes hard to SlowKts, then slows gently and evenly, reaching the exit speed exactly at the exit; clear of the runway it slows to taxi speed.
RolloutProfile |
A320 default |
|---|---|
BrakeDecel |
2.5 m/s² (down to SlowKts; jerk RolloutJerk 0.6 m/s³) |
SlowKts |
80 |
HighSpeedExitKts / ExitKts |
32 / 12 |
ExitLateralAccel |
1.5 m/s² (cornering through the exit) |
After landing
- Vacate stop: the aircraft stops at the vacate point (or as soon as comfortably possible when already past it) and waits for
ClearToTaxi/ClearUpTo(HoldForClearance) or the after-landing dwell, which varies by ±10 % (DwellJitter). AClearToTaxigiven earlier takes effect as soon as the aircraft has stopped. - Roll-through: without
HoldForClearance,RollThroughChanceof arrivals (defaultDefaultRollThroughChance, 30 %; negative never) only slow toRollThroughKts(0.5 kt) there and taxi on, like a rolling clearance. - Runway crossings: with
HoldAtCrossingsthe aircraft stops its nose gearHoldShortStopMeters(7 m) before the hold-short line of every runway it crosses and reportsArrivalHoldingShortwithHoldingShortOf.ClearToCross()sends it on. Given earlier, it clears the next crossing ahead, so the aircraft does not stop; each call clears one crossing. WithoutHoldAtCrossingscrossings count as cleared in advance.
Progressive taxi
ClearUpTo(node airport.NodeID) error clears an injected arrival to taxi up to a node of its route and hold there, as a controller would give “taxi via A, hold short of B”.
- The node must be on
Plan().Route.Nodes; otherwiseErrNotOnRoute. Without an injector:ErrNotInjected. - Before the taxi-in (from the approach to
ArrivalAwaitingTaxi) it is the taxi clearance with a limit. The vacate stop still comes first; atArrivalAwaitingTaxithe taxi starts at once. A node that is not ahead of the aircraft when the taxi-in starts (for example on the exit path) holds it where it is (see the last point below). - While taxiing or holding short it moves the limit. The node must lie ahead on the current path (within 15 m of it); a node behind the aircraft returns
ErrNotOnRoute. InArrivalParkingand later it always does. - The nose gear stops on the node, or
HoldShortStopMetersbefore it when the node is a hold-short. The aircraft stops at the nearer of the limit and the next uncleared crossing. ArrivalEvent.LimitNodeis the current limit (−1 for none) andAtLimitis set while the aircraft holds there; an event is sent when it arrives and when it moves on.- A later
ClearUpTomoves the limit on;ClearToTaxi()removes it. - A limit given before the taxi-in starts (during the approach or rollout) that is behind the aircraft by then (on the exit path) holds it clear of the runway and is reported as an
ArrivalEventwithErrwrappingErrNotOnRoute.
Stands
StandPoint(parking, noseOffset) is where an aircraft stands on a parking spot. MSFS stands are circles sized for the largest aircraft allowed, and aircraft park with the nose at the front of the circle (by the jetway and stop mark). The reference point is therefore Radius − noseOffset ahead of the circle’s centre along the stand heading, not at the centre. NoseOffset is the reference-point-to-nose distance (default DefaultNoseOffsetMeters, 17 m, A320 family). Arrivals stop there and departures spawn there.
- Nose-in stands: the injected path ends with 25 m straight along the stand axis, the last
StandSlowMeters(30 m) atStandTaxiSpeedKts(5 kt), with the reference point on the stop mark. The state isArrivalParkingover the last 55 m. - Face-out (self-manoeuvring) stands: when the lead-in junction lies ahead of the parked aircraft (LKPR N50–N58 and the S stands), the aircraft comes off the lead-in, swings out to the side with fewer other stands within 60 m, loops round behind the stop mark (scaled by
TurnAroundMeters, 18 m) and comes back along the centreline facing out, with about three wheelbases of straight so the main gear lines up. This is decided per route. - Parked: the aircraft stays frozen on the stand under the injector.
Injector.Releasehands it back to MSFS AI;Cancelremoves it.
The sweep tests fly 44 injected arrivals across LKPR stands and runways; all park within 3° of the stand heading and 1 m of the stop mark.
Stand allocation
StandAllocator assigns stands at one airport and tracks who is on them (#292):
stands := traffic.NewStandAllocator(client, g) // feed stands.Handle(msg); call stands.Scan() every ~10 s
s, err := stands.Assign(traffic.StandRequirements{
Owner: "DLH1394", Airline: "DLH", Runway: "24", // HalfSpan 0 = A320
})
// … ArrivalRequest{Parking: s, …}; stands.ReleaseOwner("DLH1394") when the aircraft is gone
- Reservations:
Occupy(stand, owner, halfSpan)fails withErrStandTakenwhen someone else holds the stand or an aircraft on an overlapping stand (Layout.ParkingConflicts) is in the way: two aircraft clash when their half spans plusStandWingtipClearanceMeters(3 m) exceed the distance between the stand centres.Release,ReleaseOwner. - Detection:
Scanrequests every aircraft around the airport (AI and the user). One on the ground belowStandDetectKtswithin a stand’s RADIUS holds that stand, with its realWING SPAN. A reservation and a detection on the same stand merge (the owner stays, the object ID and span come from the scan). - Assign: suitable stands (
Layout.SuitableStandsfor the span, optionalTypes), the airline’s own stands first, then stands open to every airline; ranked by taxi-in length from the arrival runway’s best exit.ErrNoStandwhen none is free. WithOffBlock(a departure’s STD, an arrival’s turnaround) a stand withinStandPushNeighbourMeters(90 m) of a reserved stand whose aircraft is due off withinStandPushConflictWindow(8 min) ranks as if its taxi-in were up toStandPushConflictMeters(600 m) longer, less as the times are further apart; without a runway such stands come last. Neighbours then rarely push at once.SetOffBlockupdates the time (a turnaround’s departure). - Taxi routes:
ReserveRoute(owner, nodes)returns the owners whose reserved routes share a node (a warning; spacing on the ground is theGroundPicture, see Ground traffic).ReleaseRoute. Occupancy()is a snapshot for maps and ATC.
Lights
Injected (hybrid after the takeover, injected throughout):
| Phase | Lights |
|---|---|
| Injected approach, rollout on the runway | nav, beacon, strobes, landing (logo on) |
| Clear of the runway | strobes off |
| Vacate stop (or the slowest point when rolling through) | landing off, taxi on TaxiLightDelay (1.5 s) later |
| Crossing a runway | strobes and landing on from CrossingOnMeters (10 m) past the first hold-short line until the main gear is CrossingTailMeters (40 m) past the opposite one |
| Parked | nav (beacon and taxi off) |
Logo and wing lights otherwise stay as the aircraft had them. With MSFS AI the controller sets approach, rollout, after-landing (taxi, beacon, nav) and parked (beacon, nav) lights once per phase; the AI overrides them within about a second (see Injected Ground Movement). ArrivalEvent.Lights always reports what the sim shows.
Example
examples/ai-arrival lands an aircraft (LKPR runway 24 → C22 by default), prints the plan (exit, taxi-in route, crossings), each state change, light changes and progress. Ctrl+C removes the aircraft.
go run ./examples/ai-arrival -inject -inject-approach -hold-crossings
| Flag | Default | Description |
|---|---|---|
-icao / -runway / -stand |
LKPR / 24 / C22 |
Airport, runway end to land on, stand label |
-model / -livery / -tail |
FSLTL A320 Air France SL / default / CSA456 |
Aircraft |
-spawn-nm |
5 | Distance out on final to spawn |
-inject |
false |
Hybrid: position injection takes over during the rollout |
-inject-approach |
false |
With -inject: fly the approach, flare and touchdown by injection too |
-hold |
false |
Hold clear of the runway until Enter (taxi clearance) |
-dwell |
0 (15 s) | After-landing stop before taxiing on |
-roll-through |
0 (0.3) | With -inject: chance of a rolling clearance at the vacate point; negative never |
-hold-crossings |
false |
With -inject: hold short of runway crossings; the demo clears after -cross-after |
-cross-after |
15 s | Demo crossing clearance delay |
-nose |
0 (17 m) | Reference-point-to-nose distance |
-keep |
false |
Leave the parked aircraft in the sim on exit |
-ground-agl / -no-stop |
false |
MSFS AI comparisons: ground waypoints at 0 ft AGL; no active stop at the stand |
cmd/airport-map spawns arrivals and departures from the map and gives every clearance, including progressive taxi by clicking a route point.