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:
- Climb —
ComputeClimbProfilesteps forward from departure altitude in 5 NM increments. At each step:PerfDataStorelookup returns fuel flow and vertical speed;SpeedCalculationscomputes 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'sDistanceFromDepartureNm. The FL100 crossing point is marked as a(SPDLIM)pseudo-waypoint in theComputedFlightPlan. - Descent —
ComputeDescentProfilesteps backward from destination in 5 NM increments.InterpolatePositionBackwardcomputes lat/lon at each step by walking legs in reverse, usingdistIntoLeg / legDistas 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 theComputedFlightPlan. 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 theComputedFlightPlan. - Cruise —
ComputeCruiseProfilefills 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:
- Sub-step 1: Remaining distance below FL100 at 250 kt (speed restriction active)
- 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:
- 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 - Compute the distance from ADES to the 1,000 ft AGL point using an approximate 3° glidepath
- 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:
FindCrossoverAltitudewalks the climb profile points and interpolates descent altitude at each climb distance viaInterpolateAltitudeAtDistance. The crossover altitude is where the climb profile meets or exceeds the descent profile.- The cruise altitude is capped to the crossover altitude (or the lower of the two profile end altitudes as fallback).
- Both climb and descent profiles are recomputed with the capped cruise altitude.
- The cruise profile is empty (0 points) because T/C and T/D converge at the same distance.
- 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:
- Pilot enters a value on an MCDU page → page
HandleLskreturns a command (e.g.UpdatePerfSnapshot,SetFuelTableRteRsvPct) McduActorprocesses the command: updates its local cached field and sends aSetKernel*message toKernelActor(e.g.SetKernelPerfSnapshot,SetKernelCostIndex)KernelActorstores the value in its state field- On each 1-second
KernelTick,KernelActorpublishes aKernelStateSnapshotcontaining all current values McduActorreceives 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 numberCalcTasFromIas— IAS to TAS knots using OATCalcGsFromTas— 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 plusVnavMarkerpseudo-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:
- Interpolates the exact split point on the circle.
- Emits a CurvePoint at the boundary carrying the originating phase parameters.
- Looks up new-phase performance via
_perfData.GetPerformanceCachedat the current altitude and weight. - Updates "split state" variables (phase, IAS, TAS, Mach, V/S, FF,
PerfSourceMdb) so subsequent arc steps use new-phase flight parameters. - 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,PreStartMDB loadA320_FMGC/Trajectory/TrajectoryModels.vb—ProfilePoint(18 fields incl. FuelFlowKgH, VerticalSpeedFpm, PerfSourceMdb),ClimbProfile,CruiseProfile,DescentProfile,TrajectoryResult,WindEntry,PerfResult(incl. PerfSourceMdb)A320_FMGC/Trajectory/LegGeometry.vb—ComputeLegGeometrydispatch, all 16 leg type handlers,LegGeometryResultA320_FMGC/Trajectory/PerfDataStore.vb— MDB preload, cache thresholds, altitude/weight/phase lookup, Perf1Engine wiringA320_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 tablesA320_FMGC/Trajectory/EngineDataStore.vb— 84,000-entry engine N1/EPR database, trilinear interpolation scaffoldA320_FMGC/Trajectory/EngineCalculations.vb— N1/EPR computation from DISA/altitude/Mach/TLAA320_FMGC/Kernel/KernelActor.vb—KernelTickhandler,Ask(Of FlightPlanSnapshot)(GetFlightPlan, 500ms).PipeTo(Self),ComputeTrajectorydispatch,EventStreamsubscriptions