Einen Flugführungsrechner bauen (MCDU/FMS)

Ein Flugführungsrechner beantwortet dem Piloten drei Fragen: wo bin ich auf dem Profil, wann komme ich an und womit komme ich an. Dieser Guide baut die Rechnungen dahinter Schritt für Schritt mit std.aero — vom Steigflug über den Sinkflugbeginn bis zur Warteschleife.

Guides · Vorher lesen: Standardatmosphäre, Höhen und Geschwindigkeiten · Navigation und Wegpunkte · Unit-Übersicht · DO-178C

import std.aero.atmos;     // Atmosphäre, Höhen, Geschwindigkeiten
import std.aero.perf;      // Bahngeometrie, Zwangspunkte, Reiseflug, Wind
import std.aero.vnav;      // segmentierte Steig- und Sinkprofile
import std.aero.fplan;     // Vorhersage über die Route, Reserven
import std.aero.holding;   // Warteschleifen

<WRAP center round important 90%> Diese Units rechnen — sie fliegen nicht. Was hier entsteht, ist ein Planungswerkzeug. Ein Gerät an Bord braucht zusätzlich den Nachweisprozess nach DO-178C, eine Musterzulassung und die Daten aus dem Flughandbuch des konkreten Musters. std.aero ist neu (2026-09-04) und im ausgelieferten lyxc 1.1.18A noch nicht enthalten. </WRAP>


1. Der Entwurfsgedanke: vier Fragen, vier Funktionen

Der häufigste Anfängerfehler beim Nachbau eines FMS ist eine Funktion namens Sinkrate(). Die gibt es nicht, weil ein FMS vier verschiedene Fragen beantwortet — und wer sie verwechselt, bekommt eine Zahl, die plausibel aussieht und nicht zur Lage passt:

Frage Funktion Hängt ab von
Welche Rate hält einen 3°-Pfad bei dieser Grundgeschwindigkeit? AvSinkrateFpm nur der Grundgeschwindigkeit — reine Trigonometrie
Welche Rate erreicht „FL100 bei 30 NM„? AvNoetigeSinkrateFpm Höhe und Strecke als Vorgabe
Wo muss der Sinkflug beginnen, damit er im Leerlauf aufgeht? AvTodStreckeNm Gleitzahl des Musters und Wind
Welche Geschwindigkeit ist bei diesem Cost Index günstiger? AvGuenstigere Kostenverhältnis — eine Optimierung

Dieselbe Trennung gilt auf der Steigseite: die beste Steigrate ist ein Energieüberschuss-Problem (AvSteigrateFpm: überschüssige Leistung geteilt durch Gewicht), keine Geometrie.


2. Steigflug: warum eine mittlere Rate nicht reicht

Ein Verkehrsflugzeug steigt unten mit 2500 ft/min und oben mit 500, und der Stundenverbrauch fällt dabei um ein Drittel. std.aero.vnav integriert deshalb in Schritten über die Höhe, statt mit einem Mittelwert zu rechnen:

unit main;
import std.aero.atmos;
import std.aero.perf;
import std.aero.vnav;
import std.io;
import std.string;

fn main(): int64 {
    // Steigflug 1500 ft -> FL350, Plan 280 kt / M0.78, ISA+10, 20 kt Gegenwind
    var p: AvProfil := AvSteigprofil(1500.0, 35000.0, 1000.0,
                                     280.0, 0.78, 10.0,
                                     2400.0, 500.0,       // ROC unten / oben
                                     3200.0, 1500.0,      // kg/h unten / oben
                                     0.0 - 20.0);

    if (AvProfilGueltig(p) == false) { PrintLn("Profil ungueltig"); return 1; }

    PrintLn(StrConcat("erreichte Hoehe: ", StrConcat(FloatToStr(AvProfilEndhoeheFt(p), 0), " ft")));
    PrintLn(StrConcat("Dauer:           ", StrConcat(FloatToStr(AvProfilZeitMin(p), 1), " min")));
    PrintLn(StrConcat("Strecke:         ", StrConcat(FloatToStr(AvProfilStreckeNm(p), 1), " NM")));
    PrintLn(StrConcat("Treibstoff:      ", StrConcat(FloatToStr(AvProfilKraftstoffKg(p), 0), " kg")));

    // Zum Vergleich: eine einzige mittlere Rate
    PrintLn(StrConcat("grob (1450 fpm): ",
        StrConcat(FloatToStr(AvSteigzeitMin(1500.0, 35000.0, 1450.0), 1), " min")));
    return 0;
}

erreichte Hoehe: 35000 ft
Dauer:           27.6 min
Strecke:         175.1 NM
Treibstoff:      985 kg
grob (1450 fpm): 23.1 min

Viereinhalb Minuten Unterschied auf einem einzigen Steigflug — und die Mittelwertrechnung liegt in die optimistische Richtung daneben. Über einen Flugplan mit Treibstoffrechnung ist das der Unterschied zwischen „reicht“ und „reicht nicht„.

Die Strecke aus dem Profil ist gleichzeitig der T/C (Top of Climb): 175 NM nach dem Start ist die Reiseflughöhe erreicht. Für die Position auf der Route gibt es AvTopOfClimbNm.

 
Erreicht die Steigrate null, bricht das Profil ab und AvProfilEndhoeheFt sagt, wie weit es gekommen ist. Das ist die ehrliche Antwort auf „das Flugzeug schafft die Höhe heute nicht“ — ein Ergebnis mit der Zielhöhe wäre eine Lüge. Immer AvProfilGueltig und die erreichte Höhe prüfen, nie die angeforderte weiterverwenden.

3. Sinkflug: der Punkt, an dem alles hängt

Der Sinkflugbeginn (T/D) ist die wichtigste einzelne Zahl im Reiseflug. Zu spät heißt: zu hoch am Zwangspunkt und Bremsen im Sinkflug. Zu früh heißt: minutenlang tief fliegen und Treibstoff verschwenden.

unit main;
import std.aero.perf;
import std.io;
import std.string;

fn main(): int64 {
    var jetztFt:   f64 := 35000.0;
    var zielFt:    f64 := 3000.0;
    var gleitzahl: f64 := 17.0;      // aus dem Flughandbuch
    var tasKt:     f64 := 280.0;
    var wind:      f64 := 25.0;      // Rückenwind, positiv

    PrintLn(StrConcat("Faustformel 3:1:   ",
        StrConcat(FloatToStr(AvTodStreckeNmFaustformel(jetztFt, zielFt), 1), " NM")));
    PrintLn(StrConcat("mit Gleitzahl 17:  ",
        StrConcat(FloatToStr(AvTodStreckeNm(jetztFt, zielFt, gleitzahl, tasKt, wind), 1), " NM")));
    PrintLn(StrConcat("Sinkrate dazu:     ",
        StrConcat(FloatToStr(AvTodSinkrateFpm(gleitzahl, tasKt, wind), 0), " fpm")));

    // Zwangspunkt: FL100 bei 30 NM, wir sind in FL180
    PrintLn(StrConcat("noetige Rate:      ",
        StrConcat(FloatToStr(AvNoetigeSinkrateFpm(18000.0, 10000.0, 30.0, 300.0), 0), " fpm")));
    PrintLn(StrConcat("noetiger Winkel:   ",
        StrConcat(FloatToStr(AvNoetigerWinkelGrad(18000.0, 10000.0, 30.0), 2), " Grad")));

    if (AvZwangspunktErreichbar(18000.0, 10000.0, 30.0, gleitzahl, tasKt, wind)) {
        PrintLn("Zwangspunkt: erreichbar");
    } else {
        PrintLn(StrConcat("zu hoch um:        ",
            StrConcat(FloatToStr(AvAbweichungVomProfilFt(18000.0, 10000.0, 30.0,
                                                          gleitzahl, tasKt, wind), 0), " ft")));
    }
    return 0;
}

Faustformel 3:1:   96.0 NM
mit Gleitzahl 17:  97.5 NM
Sinkrate dazu:     1668 fpm
noetige Rate:      1333 fpm
noetiger Winkel:   2.51 Grad
Zwangspunkt: erreichbar

Die 3:1-Regel („drei mal die abzubauenden Tausend Fuß„) liegt hier nur anderthalb Meilen daneben — bei Rückenwind und einer besseren Gleitzahl geht die Schere weiter auf. Sie steht als eigene Funktion da, weil sie im Cockpit tatsächlich benutzt wird, aber sie ersetzt die Rechnung nicht.

AvZwangspunktErreichbar hält die nötige gegen die mögliche Rate. Genau das ist der Grund, warum ein FMS eine Meldung ausgibt, statt still ein unmögliches Profil zu zeichnen — und AvAbweichungVomProfilFt liefert die Zahl hinter der Anzeige „so viele Fuß über dem Pfad“.

 
Noch genauer als die Gleitzahl über den ganzen Höhenbereich ist AvTodStreckeAusProfilNm aus std.aero.vnav: dort wird das Sinkprofil integriert und die Strecke ist das Ergebnis, nicht die Eingabe.

4. Die Pseudo-Wegpunkte

T/C, T/D und DECEL sind keine echten Wegpunkte — sie stehen in keiner Datenbank, sondern werden aus dem Profil gerechnet und als Symbol auf die Route gezeichnet. Alle drei liefern eine Position in NM entlang der Route, nicht eine Strecke:

Symbol Funktion Bedeutung
T/C AvTopOfClimbNm hier ist die Reiseflughöhe erreicht
T/D AvTopOfDescentNm hier beginnt der Sinkflug
DECEL AvDecelPunktNm hier beginnt die Verzögerung auf Anfluggeschwindigkeit
 
Liegt der T/D vor dem jetzigen Ort, liefert AvTopOfDescentNm den Fehlerwert — keine negative Zahl. „Der Sinkflug hätte schon begonnen haben müssen„ ist eine Lage, keine Position, und eine negative Zahl sähe aus wie ein Ergebnis.

Dazu kommt die Kurvengeometrie, ohne die eine Route zu lang gerechnet wird: AvKurvenvorhaltNm sagt, wie weit vor dem Knickpunkt die Kurve beginnt, AvKurvenabkuerzungNm den Streckengewinn. Ab 180° Kursänderung ist der Vorhalt ein Fehler — eine Kehrtwende lässt sich nicht abkürzen.


5. Die Vorhersage: eine Kette, keine Formel

Das ist die Seite, die ein Pilot am häufigsten ansieht. Jeder Abschnitt verbraucht Zeit und Treibstoff, und der Zustand am Ende ist der Anfang des nächsten:

unit main;
import std.aero.fplan;
import std.io;
import std.string;

fn zeige(name: pchar, v: AvVorhersage): void {
    var s: pchar := StrConcat(name, "  t=");
    s := StrConcat(s, FloatToStr(AvVorhersageZeitMin(v), 0));
    s := StrConcat(s, " min  s=");
    s := StrConcat(s, FloatToStr(AvVorhersageStreckeNm(v), 0));
    s := StrConcat(s, " NM  EFOB=");
    PrintLn(StrConcat(s, StrConcat(FloatToStr(AvVorhersageEfobKg(v), 0), " kg")));
}

fn main(): int64 {
    var v: AvVorhersage := AvVorhersageStart(9000.0);

    AvVorhersageZeitabschnitt(v, 12.0, 600.0);        // Rollen: Zeit ohne Strecke
    zeige("nach Rollen ", v);
    AvVorhersageAbschnitt(v, 175.0, 380.0, 2100.0);   // Steigflug
    zeige("T/C         ", v);
    AvVorhersageAbschnitt(v, 520.0, 460.0, 1900.0);   // Reiseflug
    zeige("T/D         ", v);
    AvVorhersageAbschnitt(v, 98.0, 400.0, 900.0);     // Sinkflug
    zeige("Anflug      ", v);

    var strecke:  f64 := AvVorhersageVerbrauchKg(v);
    var zuschlag: f64 := AvZuschlagKg(strecke, 5.0);
    var endres:   f64 := AvEndreserveKg(1200.0, 30.0);
    var mindest:  f64 := AvMindestkraftstoffKg(120.0, strecke, zuschlag, 1400.0, endres, 0.0);

    PrintLn(StrConcat("Streckenverbrauch: ", StrConcat(FloatToStr(strecke, 0), " kg")));
    PrintLn(StrConcat("Mindestmenge:      ", StrConcat(FloatToStr(mindest, 0), " kg")));
    if (AvKraftstoffReicht(9000.0, mindest)) {
        PrintLn(StrConcat("Ueberschuss:       ",
            StrConcat(FloatToStr(AvKraftstoffUeberschussKg(9000.0, mindest), 0), " kg")));
    } else {
        PrintLn("REICHT NICHT");
    }

    var ab: f64 := 22.0 * 60.0 + 40.0;                // Abflug 22:40
    var an: f64 := AvAnkunftMinuten(ab, AvVorhersageZeitMin(v));
    PrintLn(StrConcat("Ankunft:           ",
        StrConcat(IntToStr(AvStundeAus(an)), StrConcat(":", IntToStr(AvMinuteAus(an))))));
    return 0;
}

nach Rollen   t=12 min  s=0 NM    EFOB=8880 kg
T/C           t=40 min  s=175 NM  EFOB=7913 kg
T/D           t=107 min  s=695 NM  EFOB=5765 kg
Anflug        t=122 min  s=793 NM  EFOB=5545 kg
Streckenverbrauch: 3455 kg
Mindestmenge:      5748 kg
Ueberschuss:       3252 kg
Ankunft:           0:42

Vier Dinge sind daran wichtig:

  • Grundgeschwindigkeit und Verbrauch je Abschnitt. Eine Mittelung über die ganze Strecke ist bei Gegenwind auf der zweiten Hälfte gutmütig falsch — sie sagt zu wenig Verbrauch voraus, und zwar in die gefährliche Richtung.
  • Rollen und Warten sind Zeitabschnitte. AvVorhersageZeitabschnitt gibt es, damit man sie nicht mit Strecke 0 und irgendeiner Geschwindigkeit einsetzen muss — die Division wäre eine Lüge.
  • EFOB darf negativ werden. Das ist die Aussage „so weit reicht es nicht“; sie zu verschweigen wäre der stille Ersatzwert, den es hier nicht geben soll.
  • Der Überlauf über Mitternacht wird gerechnet: 22:40 plus 122 Minuten ist 00:42, nicht 24:42. Gerechnet wird in Minuten seit Mitternacht, damit keine Zeitzonenlogik in die Unit gerät.

Reserven

Die Aufteilung steht in der Unit, die Zahlen kommen vom Aufrufer — sie stehen im Betriebshandbuch und unterscheiden sich je nach Betreiber und Flugart:

  • Rollen · Strecke · Zuschlag (üblich 5 %) · Ausweichflug · Endreserve (üblich 30 min Warteflug) · Zusatz

Die Summe ist in AvMindestkraftstoffKg bewusst ausgeschrieben statt in einem Faktor versteckt: wer eine Position weglässt, sieht es an der Aufrufstelle.


6. Warteschleife

unit main;
import std.aero.holding;
import std.io;
import std.string;

fn main(): int64 {
    var einflugkurs: f64 := 90.0;
    var hoeheFt:     f64 := 12000.0;
    var tasKt:       f64 := 260.0;
    var bank:        f64 := 25.0;

    PrintLn(StrConcat("Anflug 090: ", AvEinflugName(AvEinflugverfahren(einflugkurs, 90.0,  true))));
    PrintLn(StrConcat("Anflug 270: ", AvEinflugName(AvEinflugverfahren(einflugkurs, 270.0, true))));
    PrintLn(StrConcat("Anflug 030: ", AvEinflugName(AvEinflugverfahren(einflugkurs, 30.0,  true))));

    PrintLn(StrConcat("max. IAS:    ",
        StrConcat(FloatToStr(AvMaxWartegeschwindigkeitKt(hoeheFt), 0), " kt")));
    PrintLn(StrConcat("Runde:       ",
        StrConcat(FloatToStr(AvRundenzeitMin(hoeheFt, tasKt, bank), 2), " min")));
    PrintLn(StrConcat("je Runde:    ",
        StrConcat(FloatToStr(AvRundenverbrauchKg(hoeheFt, tasKt, bank, 1800.0), 0), " kg")));
    PrintLn(StrConcat("mit 1200 kg: ",
        StrConcat(IntToStr(AvMoeglicheRunden(1200.0, hoeheFt, tasKt, bank, 1800.0)), " volle Runden")));
    PrintLn(StrConcat("Breite:      ",
        StrConcat(FloatToStr(AvSchleifenbreiteNm(tasKt, bank), 1), " NM")));
    return 0;
}

Anflug 090: direkt
Anflug 270: parallel
Anflug 030: versetzt
max. IAS:    230 kt
Runde:       5.06 min
je Runde:    152 kg
mit 1200 kg: 7 volle Runden
Breite:      4.2 NM

Das Einflugverfahren ist die einzige wirklich knifflige Stelle: die Sektorgrenzen sind nicht symmetrisch (180° direkt, 110° parallel, 70° versetzt). Wer die 110 und die 70 vertauscht, bekommt in einem schmalen Bereich das falsche Verfahren — und merkt es nicht, weil die anderen Richtungen weiter stimmen. Die Konvention der Unit steht auf der Unit-Seite: einflugkursGrad ist der Kurs auf dem Anflugschenkel zum Fix.

„Wie viele Runden erlaubt der Treibstoff„ verbindet die Schleife mit der Vorhersage: jede Runde ist ein AvVorhersageZeitabschnitt.


7. Was der Aufrufer mitbringen muss

std.aero enthält kein Flughandbuch. Diese Kennzahlen sind musterabhängig und müssen von außen kommen:

Größe Wofür Größenordnung Verkehrsflugzeug
Gleitzahl im Sinkflug T/D, Zwangspunkte 15 bis 18
Steigraten unten/oben Steigprofil 2500 / 500 fpm
Verbrauch unten/oben Profil und Vorhersage musterabhängig
Schub und Widerstand Steigrate, Dienstgipfelhöhe, Drift-Down in Newton
W/δ im Optimum optimale Reiseflughöhe musterabhängig
Verzögerung im Leerlauf DECEL-Punkt 10 bis 20 kt/min

Ein eingebauter „typischer“ Wert wäre für jedes andere Muster falsch und würde nirgends auffallen — deshalb gibt es ihn nicht.


8. Die optimale Reiseflughöhe steigt

W/δ ist im Optimum konstant. Wird das Flugzeug leichter, muss δ kleiner werden — die beste Höhe steigt im Verlauf des Fluges. Genau deshalb gibt es Stufensteigflüge:

var optM:   f64 := AvOptimaleHoeheM(masseKg, wProDeltaOptN);
var gewinn: f64 := AvStufensteigGewinnM(masseVorherKg, masseNachherKg, wProDeltaOptN);

Wer die Reiseflughöhe als feste Zahl führt, fliegt die zweite Hälfte zu tief — und bezahlt das im Verbrauch.

Ob eine Stufe sich lohnt, ist eine Kostenfrage, keine Höhenfrage: AvGesamtkostenKgAequiv und AvGuenstigere vergleichen zwei Angebote bei gegebenem Cost Index. CI 0 heißt „Treibstoff ist alles„ (maximale Reichweite), ein hoher CI „Zeit ist teuer“.


9. Die Notfälle

Beides sind Sinkprofile, nur mit anderem Anlass — die erste Rechnung sucht eine Höhe, die zweite eine Zeit:

Fall Funktionen
Drift-Down nach Triebwerksausfall: auf welche Höhe sinkt das Flugzeug, und reicht sie über die Berge? AvDriftDownHoeheFt, AvDriftDownFreigaengig
Notabstieg bei Druckverlust: wie lange bis zur atembaren Höhe, und reicht der Sauerstoff? AvNotabstiegZeitMin, AvNotabstiegStreckeNm, AvSauerstoffReicht

AvDriftDownFreigaengig prüft zwei Seiten: die erreichbare Höhe und den geforderten Abstand darüber. AvSauerstoffReicht rechnet den Abstieg und den anschließenden Flug in der Zwischenhöhe — die Frage, die über die zulässige Streckenführung entscheidet.

Dazu passen die Umkehrpunkte aus std.aero.perf: AvPunktGleicherZeitNm (von hier dauert Weiterfliegen so lang wie Umkehren) und AvPunktOhneWiederkehrNm (der letzte Punkt, von dem der Treibstoff zur Rückkehr reicht). Über Wasser sind das die beiden Zahlen, die die Streckenführung bestimmen — und der Wind verschiebt sie erheblich, weil er in eine Richtung hilft und in die andere bremst.


10. Ein Bauplan

Die Reihenfolge, in der ein Rechner die Seite füllt:

  1. Atmosphäre festlegen: QNH, ISA-Abweichung, Übergangshöhe des Geschwindigkeitsplans (AvUebergangshoeheFt).
  2. Steigprofil rechnen → Zeit, Strecke, Treibstoff, T/C-Position.
  3. Reiseflug in Abschnitte teilen (Wind und Höhe je Abschnitt) → Grundgeschwindigkeit über AvGrundgeschwindigkeitKt.
  4. Sinkflug rückwärts vom Anflugpunkt → T/D und DECEL.
  5. Vorhersage über alle Abschnitte fortschreiben → Zeit, EFOB je Wegpunkt.
  6. Reserven dagegenhalten → Mindestmenge, Überschuss.
  7. Prüfen: jeder Zwangspunkt erreichbar? Drift-Down über der höchsten Erhebung? Sauerstoff ausreichend?

Und über allem: Fehlerwerte prüfen. Jede Funktion liefert bei unsinniger Eingabe NaN (AvIsError) oder false. Ein FMS, das eine unmögliche Lage still in eine plausible Zahl übersetzt, ist gefährlicher als eines, das gar nichts anzeigt.


Letzte Aktualisierung: 2026-09-05 — Seite neu angelegt; alle Beispiele gegen lyxc 1.1.18A übersetzt und ausgeführt, die Ausgaben stammen aus dem Lauf.

Prüflauf 2026-09-05 mit doku-pruefer.py über den gesamten Aero-Bereich (Repo-Compiler lyxc 1.2.2A): 13 Vollprogramme, 13 übersetzen, 0 echte Fehler; 193 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen.