Zum Inhalt

Migration

Diese Seite wurde aus der AirSimTech MediaWiki migriert.

Trajectory Computation Chain

Overview

TrajectoryActor computes a 4D trajectory — position, altitude, time, and fuel — in its own Akka.NET actor thread, publishing results to the EventStream when complete. No other actor blocks waiting for the result; they simply subscribe and cache whatever arrives. The key design principle is the queue-latest-only concurrency model: if a new ComputeTrajectory request arrives while a computation is already running, the previous pending request (if any) is discarded and only the newest request is remembered. This ensures the system always converges on the latest flight plan and aircraft state without queueing stale work. All computation is synchronous on the actor thread — there are no Task.Run, Await, or background threads inside TrajectoryActor; the Akka.NET mailbox dispatcher provides the background thread.

KernelTick to TrajectoryResult

The trigger for trajectory computation is the 1-second KernelTick timer in KernelActor. The full message flow from tick to published result:

Concurrency Model

The queue-latest-only pattern prevents stale trajectory computations from queuing while a long computation is still in progress.

Key fields in TrajectoryActor:

Field Type Purpose
_isComputing Boolean True while a computation is running on the actor thread
_pendingRequest ComputeTrajectory Holds the latest unprocessed request; Nothing if none queued

The ComputationComplete message is an internal message sent by the actor to itself via Self.Tell(New ComputationComplete(result), ActorRefs.NoSender). This ensures the publish and pending-check happen in the actor's own mailbox turn, maintaining single-threaded actor semantics.

Computation Phases

Each trajectory computation proceeds through three sequential phases on the actor thread:

Phase sequence:

  1. ClimbComputeClimbProfile steps forward from departure altitude in 5 NM increments. At each step: PerfDataStore lookup returns fuel flow and vertical speed; SpeedCalculations computes IAS/MACH crossover; wind component is computed from nearest wind table entry; altitude constraints are checked and the climb is terminated when cruise altitude is reached or route distance is exhausted. Below 10,000 ft, IAS is capped at 250 kt (standard speed restriction). Top-of-climb distance (TopOfClimbDistanceNm) is the final climb point's DistanceFromDepartureNm. The FL100 crossing point is marked as a (SPDLIM) pseudo-waypoint in the ComputedFlightPlan.
  2. DescentComputeDescentProfile steps backward from destination in 5 NM increments. InterpolatePositionBackward computes lat/lon at each step by walking legs in reverse, using distIntoLeg / legDist as the interpolation fraction (distance into the current leg segment from the destination end). Below 10,000 ft, IAS is capped at 250 kt. Top-of-descent distance (TopOfDescentDistanceNm) is where the descent must begin. Descent fuel burn uses an idle fuel flow of 900 kg/h (CFM56 approximate), applied in a forward-pass rebuild after the backward geometry computation. The FL100 crossing point is marked as a (SPDLIM) pseudo-waypoint in the ComputedFlightPlan. The approach deceleration zone is modelled as a distance-based linear IAS ramp from VAPP to descent IAS (see Approach Deceleration below), and the (DECEL) pseudo-waypoint marks its onset in the ComputedFlightPlan.
  3. CruiseComputeCruiseProfile fills the gap between T/C and T/D. Fuel and time state at end of climb are used as cruise starting conditions. The cruise section uses cost-index-optimised IAS or MACH.

Speed-Change Energy Modulation

When IAS changes between computation steps (e.g. at the FL100 boundary or due to Mach crossover), the vertical speed is modulated to model the kinetic/potential energy trade-off:

Phase Acceleration (IAS rising) Deceleration (IAS falling)
Climb VS halved (energy goes to speed) VS + 1000 fpm (speed converts to altitude)
Descent Sink rate + 1000 fpm (steeper descent funds speed gain) Sink rate halved (shallower descent absorbs speed loss)

Thresholds:

  • Minimum: IAS change must be >= 10 kt to trigger modulation (avoids jitter)
  • Maximum: IAS change is capped at 30 kt per 5 NM step. Larger transitions (e.g. 250→310 kt) are spread across multiple steps.

FL100 Crossing Sub-Step Splitting

When a 5 NM step crosses FL100 (10,000 ft), the step is split into two sub-steps at the exact crossing altitude:

  1. Sub-step 1: Remaining distance below FL100 at 250 kt (speed restriction active)
  2. Sub-step 2: Distance above FL100 at managed IAS, with the 30 kt/step cap scaled proportionally to the remaining distance (30 * distAfterFL100 / 5.0)

This ensures the IAS transition happens at the correct altitude rather than one step early or late. Applied in both ComputeClimbProfile (forward iteration, below→above) and ComputeDescentProfile (backward iteration, below→above in backward context).

Approach Deceleration (DECEL)

The descent profile models a deceleration zone in the approach phase using a 0.1 g horizontal deceleration rate (0.981 m/s²). The aircraft must reach VAPP (approach speed from SpeedCalculations.CalcVApp, fallback 140 kt) by 1,000 ft above ADES elevation.

Distance-based computation:

  1. Compute the decel distance using the energy formula: decelDistNm = (v1² - v2²) / (2 * 0.981 * 1852), where v1 = descent IAS in m/s, v2 = VAPP in m/s
  2. Compute the distance from ADES to the 1,000 ft AGL point using an approximate 3° glidepath
  3. The DECEL onset distance from ADES = distance to 1,000 ft AGL + decel distance

Three IAS zones in the backward loop (based on distFromDes, not altitude):

Zone Distance from ADES IAS Speed Cap
VAPP zone 0 to ~1,000 ft AGL equivalent Constant VAPP None
DECEL ramp ~1,000 ft AGL to decel onset Linear interpolation VAPP → descent IAS 0.1 g rate (no 30 kt cap)
Normal descent Above decel onset Min(ManagedDescentIas, 250) below FL100; ManagedDescentIas above 30 kt / 5 NM cap applies

Because IAS is interpolated by distance fraction, the ramp produces multiple profile points with gradually changing speeds. GS, ETA, and fuel are automatically correct because they are computed from the current IAS at each step.

The (DECEL) pseudo-waypoint in ComputedFlightPlan is placed at the last profile point before IAS begins to drop (i.e., the point still at full descent speed). InsertDecelMarker scans the descent profile in forward order and picks the point immediately before the first IAS drop exceeding 3 kt below FL100.

Short-Route Altitude Capping

If T/C distance exceeds T/D distance (aircraft cannot reach the requested cruise altitude on the available route distance), StartComputation activates the crossover altitude detection logic:

  1. FindCrossoverAltitude walks the climb profile points and interpolates descent altitude at each climb distance via InterpolateAltitudeAtDistance. The crossover altitude is where the climb profile meets or exceeds the descent profile.
  2. The cruise altitude is capped to the crossover altitude (or the lower of the two profile end altitudes as fallback).
  3. Both climb and descent profiles are recomputed with the capped cruise altitude.
  4. The cruise profile is empty (0 points) because T/C and T/D converge at the same distance.
  5. Any remaining distance overlap between climb and descent is trimmed so that climb ends where descent begins.

This ensures short routes (e.g. EDDF–EDSB, ~77 NM) produce a continuous climb-then-descent profile at a sensible altitude rather than an unreachable FL370 with phantom cruise segments.

ProfilePoint Structure

Each computation step produces one ProfilePoint instance. The 18 fields cover all computed 4D trajectory values, geographic coordinates for map view rendering, and performance data source tracking.

Field Type Description
AltitudeFt Double Altitude at this profile point in feet
IasKts Double Indicated airspeed in knots
Mach Double Mach number (0.0 to 1.0)
TasKts Double True airspeed in knots
GroundSpeedKts Double Ground speed in knots
FuelRemainingKg Double Fuel remaining at this point in kilograms
CumulativeFuelBurnedKg Double Cumulative fuel burned from departure in kilograms
EtaFromDeparture TimeSpan Elapsed time from departure
UtcTime TimeSpan UTC time at this point
DistanceFromDepartureNm Double Distance from departure waypoint in nautical miles
DistanceToGoNm Double Distance to destination in nautical miles
WindComponentKts Double Wind component: positive = tailwind, negative = headwind
Phase FlightPhase Flight phase enum: Climb (0), Cruise (1), Descent (2)
Latitude Double Geographic latitude in decimal degrees (map view, Phase 9)
Longitude Double Geographic longitude in decimal degrees (map view, Phase 9)
FuelFlowKgH Double Fuel flow in kg/h at this computation step (Phase 20)
VerticalSpeedFpm Double Vertical speed in ft/min; positive = climb, negative = descent (Phase 20)
PerfSourceMdb Boolean True when V/S and FF came from FMGCPERF1.MDB Perf1Engine; False when hardcoded fallback was used (Phase 20)

Points are grouped into three typed wrappers: ClimbProfile, CruiseProfile, and DescentProfile, each holding an IReadOnlyList(Of ProfilePoint). These are assembled into TrajectoryResult together with TopOfClimbDistanceNm, TopOfDescentDistanceNm, TotalFuelBurnedKg, TotalTimeSeconds, a ComputedAt UTC timestamp, and ComputationDurationMs As Double — sub-millisecond wall-clock duration measured by a Stopwatch wrapping StartComputation. Typical values: 0.3–1.5 ms for short routes, 1–5 ms for long routes (all data is in-memory, no I/O).

ARINC 424 Leg Types

LegGeometry (in LegGeometry.vb) dispatches on the 2-character ArinCCommand field of each FlightPlanLeg via a Select Case. Unknown leg types fall through to the TF/CF handler as a safe fallback. The dispatch method is ComputeLegGeometry.

Leg Type Description Dispatch Method
TF Track to Fix — straight-line track to a stored fix ComputeTfCf
CF Course to Fix — fly specified course to a stored fix ComputeTfCf
DF Direct to Fix — direct turn to a stored fix ComputeDfDirect
DIRECT FMGC Direct-To leg — geometry identical to DF ComputeDfDirect
FC Fix to a Course — fly from fix on a specified course for a given distance ComputeFc
CA Course to Altitude — fly a course until reaching a target altitude ComputeCa
VA Heading to Altitude — fly current track until reaching a target altitude ComputeVa
FD Fix to DME Distance — fly from fix to a DME distance ComputeFd
CD Course to DME Distance ComputeCd
CR Course to Radial — intercept a radial from a specified navaid ComputeCr
VD Heading to DME Distance ComputeVd
VM Heading to Manual Termination — fly 50 NM (default) ComputeVm
FM Fix to Manual Termination — fly from fix for 50 NM (default) ComputeFm
FA Fix to Altitude — fly from fix to a target altitude ComputeFa
VI Heading to Intercept — intercept the next leg's course ComputeVi
CI Course to Intercept — intercept the next leg's course ComputeCi

Each method returns a LegGeometryResult (a Friend Class) containing EndLatitude, EndLongitude, TrackDegrees, DistanceNm, and TargetAltitudeFt (non-zero only for altitude-terminated legs: CA, VA, FA, FD, CD, CR, VD).

Computation Inputs

KernelActor assembles a ComputeTrajectory message on each KernelTick when a flight plan is available and has at least 2 navigational legs. Cost Index and Cruise Flight Level default to -1 (not set) until the pilot enters values on the INIT A page. When building the message, KernelActor applies fallback defaults for unset values:

Bare PPOS plans (fewer than 2 navigational legs) skip the full trajectory computation. Instead, KernelActor synthesizes a single-point TrajectoryResult using the PPOS leg's coordinates and the aircraft's cached altitude/speed/groundspeed. The point is placed in the CruiseProfile with PointType=Internal. This ensures ComputedFlightPlanBuilder.Build() produces both Layer 1 display legs and a Layer 2 path point (tagged "PPOS") for all diagnostic views.

Input Default (not set) Fallback for trajectory Set by
Cost Index -1 30 INIT A page or SimBrief
CRZ FL -1 FL090 (9000 ft) INIT A page or SimBrief

INIT A displays amber dashes (aaaa / aaaaaa/aaab) when values are -1. The trajectory always receives valid values (never -1) because KernelActor substitutes the fallback before dispatching.

MCDU State Synchronisation

MCDU page entries (V1/VR/V2, fuel table, cost index, etc.) follow a bidirectional flow between McduActor and KernelActor:

  1. Pilot enters a value on an MCDU page → page HandleLsk returns a command (e.g. UpdatePerfSnapshot, SetFuelTableRteRsvPct)
  2. McduActor processes the command: updates its local cached field and sends a SetKernel* message to KernelActor (e.g. SetKernelPerfSnapshot, SetKernelCostIndex)
  3. KernelActor stores the value in its state field
  4. On each 1-second KernelTick, KernelActor publishes a KernelStateSnapshot containing all current values
  5. McduActor receives the snapshot and overwrites its local caches

Critical: If step 2 is missing (McduActor updates locally but does not notify KernelActor), the next snapshot in step 5 overwrites the local value — the pilot's entry disappears after ~1 second.

INIT B Fuel Table Defaults

FuelTableSnapshot defaults (matching VB6 initialisation):

Field Default Display
Taxi fuel 200 kg Small font (computed)
RTE RSV % 5.0% Large font (manual flag = true)
Final time 0030 (30 min) Large font (manual flag = true)
ZFWCG 25.0 %MAC Small font

Performance Data

All performance and engine data is preloaded from MDB at startup; no OleDb queries are performed during computation.

PerfDataStore (PerfDataStore.vb) : Loaded from FMGCPERF1.MDB in TrajectoryActor.PreStart via _perfData.LoadFromDatabase(dbPath). Provides fuel flow (kg/hr) and vertical speed (ft/min) for any combination of altitude, weight, IAS, and flight phase. At startup, LoadFromDatabase instantiates Perf1Engine which decrypts the PERF table and builds 5 in-memory 3D grids (VSCLB, VSDES, FFCLB, FFCRZ, FFDES). A lambda is wired to _lookupFunction that clamps IAS and GW to per-phase grid limits, then dispatches by flight phase: climb calls GetData("VSCLB") + GetData("FFCLB"), cruise calls GetData("FFCRZ"), descent calls GetData("VSDES") + GetData("FFDES"). For descent, negative VS values (sink rates) are converted via Math.Abs. Each call performs tri-linear interpolation (Weight x IAS x Altitude) via the Calcit engine with edge clamping. If Perf1Engine fails to load, a linear fallback approximation is used. Cache thresholds: altitude >= 30,000 ft -> 1,000 ft bucket; >= 20,000 ft -> 2,000 ft bucket; else -> 3,000 ft bucket; weight 1,000 kg; IAS 10 kt. Cache is reset at the start of each computation via _perfData.ResetCache(). See PERF1 Performance Engine for full details.

Perf1Engine (Perf1Engine.vb) : Port of VB6 PREF.ctl COM control. Decrypts the encoded PERF table from FMGCPERF1.MDB (exponent/mantissa encoding with PublicKey scaling), routes rows to 5 grids by Mode field, and provides tri-linear interpolated lookups via GetData(tableName, gw, ias, alt). Grid structure: each grid type holds N weight pages; each page is a 2D matrix of IAS rows x ALT columns. The Calcit interpolation chain interpolates in three stages: IAS -> ALT -> GW, using the VB6 CALC_AVERAGE linear interpolation formula at each stage.

EngineDataStore (EngineDataStore.vb) : Preloads approximately 84,000 EngineEntry rows from the engine MDB, covering the full DISA x Altitude x Mach x TLA 4D space for IAE engine variants (A319). VB6 field scaling applied at load time: N1/100, OAT/1000, MACH/1000. Dictionary key = row ID (Long), matching VB6 array index. All lookups after load are pure in-memory dictionary operations.

SpeedCalculations (SpeedCalculations.vb) : Pure-function module with no state or I/O. Methods consumed by TrajectoryActor at every computation step:

  • CalcIasFromMach — Mach to IAS knots (ISA atmosphere, tropopause cap at 36,089 ft)
  • CalcMachFromIas — IAS knots to Mach number
  • CalcTasFromIas — IAS to TAS knots using OAT
  • CalcGsFromTas — TAS to ground speed given wind direction/speed
  • VLS, Greendot, FSpeed, SSpeed, VApp — stall-speed-derived managed speed values from weight-bracket lookup tables

TrajectoryResult Subscribers

All subscribers receive TrajectoryResult via EventStream subscription set up in each actor's PreStart. The publisher (TrajectoryActor) has no knowledge of its subscribers.

Subscriber Subscription What It Does with the Result
KernelActor EventStream.Subscribe(Self, GetType(TrajectoryResult)) Caches _latestTrajectory; calls ComputedFlightPlanBuilder.Build() which returns a two-layer BuildResult; dual-publishes ComputedFlightPlanResult (legacy, carrying BuildResult.Display rewrapped as ComputedFlightPlan) and BuildResultPublished (carrying the full two-layer BuildResult). Also dual-triggers when a FlightPlanSnapshot arrives with trajectory already cached. The TMPY pipeline mirrors this with TmpyComputedFlightPlanResult + TmpyBuildResultPublished. Includes the result in the next GuidanceInput bundle on the next KernelTick
McduActor (CPT and FO) Same pattern Caches _latestTraj; triggers re-render of PROG page with updated fuel/time/distance predictions. Also subscribes to ComputedFlightPlanResult (legacy message from KernelActor) and caches _latestComputedFp for F-PLN page prediction columns
GuidanceActor Same pattern Uses T/C, T/D, descent profile to compute DECEL point, managed speed transitions, and cross-track error
SideViewSubscriberActor + SideViewPathSubscriberActor + SideViewCfpSubscriberActor TrajectoryResult, BuildResultPublished, ComputedFlightPlanResult (and TMPY equivalents) Forwards to frmSideView UI thread via BeginInvoke; ScottPlot renders a phase-split altitude profile from BuildResult.Path with abeam-projected waypoint labels
MapViewSubscriberActor + MapViewPathSubscriberActor + MapViewCfpSubscriberActor Same Forwards to frmMapView UI thread via BeginInvoke; GDI+ redraws lateral track from BuildResult.Path with phase colours
RawFplnBuildResultSubscriberActor BuildResultPublished, TmpyBuildResultPublished, ComputedFlightPlanResult, TmpyComputedFlightPlanResult Forwards to frmRawFpln Display tab (Layer 1) and legacy MCDU tab
TrajectoryLogSubscriberActor TrajectoryResult only Forwards per-step TrajectoryLogEntry records to frmTrajectoryLog

See Trajectory Visualization and Diagnostics for the full EventStream fan-out and subscriber actor catalogue.

Two-Layer Build Pipeline

After TrajectoryResult is published, KernelActor calls ComputedFlightPlanBuilder.Build(plan, traj, adesElev, vApp) which returns a BuildResult with:

  • Layer 1 (Display) — navigational waypoints at database coordinates plus VnavMarker pseudo-waypoints (T/C, T/D, SPDLIM, DECEL) plus structural legs. Consumed by the MCDU F-PLN page and the RAW FPLN diagnostic Display tab.
  • Layer 2 (Path) — all Internal + CurvePoint geometry points with perpendicular-abeam waypoint name projection. Consumed by side/map view path renderers.

KernelActor then dual-publishes on the EventStream: the legacy ComputedFlightPlanResult (reconstructed from BuildResult.Display) for backward-compatible MCDU consumption, and BuildResultPublished carrying the full two-layer result for diagnostic renderers.

See 25 — Two-Layer Architecture for the complete 6-phase builder algorithm, including waypoint matching, VNAV marker insertion, curve deduplication, overshoot removal, sandwich-point filtering, and abeam projection.

Phase 29: Phase-Boundary Arc Split

When a fly-by turn arc sweeps across a T/C or T/D boundary, GenerateFlyByArc receives optional parameters (boundaryDistNm, newPhase, newPhaseIasKts, weightKg, oatC) from the calling profile function. Inside the arc loop, when the cumulative arc distance crosses boundaryDistNm, the generator:

  1. Interpolates the exact split point on the circle.
  2. Emits a CurvePoint at the boundary carrying the originating phase parameters.
  3. Looks up new-phase performance via _perfData.GetPerformanceCached at the current altitude and weight.
  4. Updates "split state" variables (phase, IAS, TAS, Mach, V/S, FF, PerfSourceMdb) so subsequent arc steps use new-phase flight parameters.
  5. Continues the arc with unchanged geometry (same center, same radius) — only the flight parameters change at the boundary.

GenerateFlyOverArc and GenerateRfArc currently retain the single-phase assumption; split support is deferred because fly-over / RF legs at phase boundaries are extremely rare in practice (see TrajectoryActor.vb:1606 TODO comment).

Source Files

  • A320_FMGC/Trajectory/TrajectoryActor.vb_isComputing, _pendingRequest, ComputeTrajectory, ComputationComplete, StartComputation, three-phase orchestration, PreStart MDB load
  • A320_FMGC/Trajectory/TrajectoryModels.vbProfilePoint (18 fields incl. FuelFlowKgH, VerticalSpeedFpm, PerfSourceMdb), ClimbProfile, CruiseProfile, DescentProfile, TrajectoryResult, WindEntry, PerfResult (incl. PerfSourceMdb)
  • A320_FMGC/Trajectory/LegGeometry.vbComputeLegGeometry dispatch, all 16 leg type handlers, LegGeometryResult
  • A320_FMGC/Trajectory/PerfDataStore.vb — MDB preload, cache thresholds, altitude/weight/phase lookup, Perf1Engine wiring
  • A320_FMGC/Trajectory/Perf1Engine.vb — PERF1 engine: PERF table decryption, 5 grid build, tri-linear interpolation (Calcit)
  • A320_FMGC/Trajectory/SpeedCalculations.vb — IAS/MACH/TAS/GS conversions, managed speed tables
  • A320_FMGC/Trajectory/EngineDataStore.vb — 84,000-entry engine N1/EPR database, trilinear interpolation scaffold
  • A320_FMGC/Trajectory/EngineCalculations.vb — N1/EPR computation from DISA/altitude/Mach/TLA
  • A320_FMGC/Kernel/KernelActor.vbKernelTick handler, Ask(Of FlightPlanSnapshot)(GetFlightPlan, 500ms).PipeTo(Self), ComputeTrajectory dispatch, EventStream subscriptions