====== 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 [[lyx_-_programmiersprache:units:aero|std.aero]] — vom Steigflug über den Sinkflugbeginn bis zur Warteschleife. → [[lyx_-_programmiersprache:guides|Guides]] · [[lyx_-_programmiersprache:guides:standardatmosphaere|Vorher lesen: Standardatmosphäre, Höhen und Geschwindigkeiten]] · [[lyx_-_programmiersprache:guides:navigation|Navigation und Wegpunkte]] · [[lyx_-_programmiersprache:units:aero|Unit-Übersicht]] · [[lyx_-_programmiersprache:guides:do-178c|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 Diese Units rechnen — sie fliegen nicht. Was hier entsteht, ist ein **Planungswerkzeug**. Ein Gerät an Bord braucht zusätzlich den Nachweisprozess nach [[lyx_-_programmiersprache:guides:do-178c|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. ---- ===== 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. [[lyx_-_programmiersprache:units:aero:vnav|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 [[lyx_-_programmiersprache:units:aero:vnav|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 [[lyx_-_programmiersprache:units:aero:holding|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 [[lyx_-_programmiersprache:units:aero:perf|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: - **Atmosphäre** festlegen: QNH, ISA-Abweichung, Übergangshöhe des Geschwindigkeitsplans (''AvUebergangshoeheFt''). - **Steigprofil** rechnen → Zeit, Strecke, Treibstoff, T/C-Position. - **Reiseflug** in Abschnitte teilen (Wind und Höhe je Abschnitt) → Grundgeschwindigkeit über ''AvGrundgeschwindigkeitKt''. - **Sinkflug** rückwärts vom Anflugpunkt → T/D und DECEL. - **Vorhersage** über alle Abschnitte fortschreiben → Zeit, EFOB je Wegpunkt. - **Reserven** dagegenhalten → Mindestmenge, Überschuss. - **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.