====== std.astro.state — Zustandsvektoren, Rahmen, Dreibein ====== ''import std.astro.state;'' Ort und Geschwindigkeit in **einem** Inertialsystem, dazu Vektorrechnung und das begleitende Dreibein. Die Unit ist die Grundlage der übrigen ''std.astro''-Units; sie rechnet selbst keine Bahnmechanik. Einheiten durchgehend SI: Meter, Meter je Sekunde, Sekunden, m³/s². → [[lyx_-_programmiersprache:units:astro|std.astro (Übersicht)]] · [[lyx_-_programmiersprache:units:astro:orbit|std.astro.orbit]] · [[lyx_-_programmiersprache:guides:kurs-im-raum|Guide: Kurs und Lage im 3D-Raum]] ---- ===== Typen ===== pub type AVec3 = struct { x: f64; y: f64; z: f64; } pub type AState = struct { rx: f64; ry: f64; rz: f64; // Ort [m] vx: f64; vy: f64; vTarget: f64; // Geschwindigkeit [m/s] } > **Das dritte Geschwindigkeitsfeld heißt wieder ''vz''** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1902|#1902]], nachgemessen mit lyxc 1.2.2B). Frühere Fassungen der Unit trugen dort ''vTarget'' — ein Benennungsfehler in der öffentlichen Schnittstelle, der auch ''AMakeState'' betraf. Heute gilt ''s.vz'', und ''AMakeState(rx, ry, rz, vx, vy, vz)'' heißt so, wie es aussieht. ===== Rahmenkennungen ===== Sie rechnen nichts. Sie erlauben, eine Verwechslung **auszusprechen**, statt sie im Kommentar zu lassen. ^ Konstante ^ Wert ^ Bedeutung ^ | ''AFRAME_UNKNOWN'' | 0 | unbekannt — ''AFrameMatches'' meldet damit **nie** Übereinstimmung | | ''AFRAME_ECI'' | 1 | erdzentriert inertial | | ''AFRAME_ECEF'' | 2 | erdzentriert mitrotierend | | ''AFRAME_LVLH'' | 3 | lokal horizontal, lokal vertikal | | ''AFRAME_RIC'' | 4 | radial / in-track / cross-track | ===== Fehlerwert ===== ^ Funktion ^ Rückgabe ^ Bedeutung ^ | ''AError()'' | ''f64'' | NaN — der Fehlerwert der gesamten ''std.astro''-Familie | | ''AIsError(x: f64)'' | ''bool'' | ob ''x'' NaN ist (NaN ist der einzige Wert, der sich selbst ungleich ist) | | ''AVecIsError(a: AVec3)'' | ''bool'' | ob **eine** Komponente NaN ist | > **Warum NaN und nicht 0 oder −1.** Bei Bahnrechnungen sehen ''0'' und ''1'' beide wie gültige Ergebnisse aus, und ein stiller Ersatzwert wandert unbemerkt in die nächste Rechnung. NaN pflanzt sich fort und lässt sich am Ende **einmal** prüfen — genau dafür ist er da. Jede Funktion dieser Unit-Familie gibt bei unmöglicher Eingabe NaN zurück. ===== Vektorrechnung ===== ^ Funktion ^ Rückgabe ^ Bedeutung ^ | ''AVec(x, y, z)'' | ''AVec3'' | Vektor bauen | | ''AAdd(a, b)'' · ''ASub(a, b)'' | ''AVec3'' | Summe, Differenz | | ''AScale(a, s: f64)'' | ''AVec3'' | mit einem Skalar strecken | | ''ADot(a, b)'' | ''f64'' | Skalarprodukt | | ''ACross(a, b)'' | ''AVec3'' | Kreuzprodukt | | ''AMagnitude(a)'' | ''f64'' | Betrag | | ''AUnitVec(a)'' | ''AVec3'' | Einheitsvektor; beim **Nullvektor** NaN in allen Komponenten — er hat keine Richtung, und ''(0,0,0)'' wäre ein gültiger Vektor, der unbemerkt weiterwandert | | ''AAngle(a, b)'' | ''f64'' [rad] | Winkel 0…π; NaN, wenn einer der beiden der Nullvektor ist | > **''AAngle'' rechnet über ''atan2(|a×b|, a·b)'', nicht über ''acos''.** Die ''acos''-Form verliert bei fast parallelen Vektoren jede Genauigkeit, weil der Kosinus dort flach ist — und genau dieser Fall (zwei fast gleiche Lagevektoren) ist in der Bahnmechanik der häufige. ===== Zustände ===== ^ Funktion ^ Rückgabe ^ Bedeutung ^ | ''AMakeState(rx, ry, rz, vx, vy, vTarget)'' | ''AState'' | Zustand bauen | | ''APosition(s)'' · ''AVelocity(s)'' | ''AVec3'' | Ort, Geschwindigkeit als Vektor | | ''ARadius(s)'' · ''ASpeed(s)'' | ''f64'' | Betrag von Ort und Geschwindigkeit | | ''AStateValid(s)'' | ''bool'' | NaN in einer Komponente oder Ort im **Ursprung** ⇒ ''false'' | | ''AFrameMatches(a: int64, b: int64)'' | ''bool'' | gleiche Rahmenkennung; ''AFRAME_UNKNOWN'' ergibt immer ''false'' | | ''AAngularMomentum(s)'' | ''AVec3'' [m²/s] | spezifischer Drehimpuls ''r × v'' | | ''ARadialSpeed(s)'' | ''f64'' | radiale Geschwindigkeit, **positiv beim Entfernen** | Der Ursprung ist kein Randfall, den man mitrechnen könnte: dort ist die Gravitationsbeschleunigung nicht definiert, und jede Bahnrechnung teilt durch den Abstand. ===== Begleitendes Dreibein (Frenet, TNB) ===== ^ Funktion ^ Rückgabe ^ Bedeutung ^ | ''ATangent(s)'' | ''AVec3'' | Einheitsvektor in Bewegungsrichtung | | ''ABinormal(s)'' | ''AVec3'' | senkrecht auf der Bahnebene, entlang des Drehimpulses | | ''ANormal(s)'' | ''AVec3'' | ''B × T'' — vervollständigt das Rechtssystem | > **Bei einer geraden Bahn gibt es keine Bahnebene.** Sind Ort und Geschwindigkeit parallel (senkrechter Auf- oder Abstieg), ist ''B'' nicht bestimmt, und alle drei Funktionen liefern NaN. Eine gewählte Ersatzrichtung wäre hier besonders gefährlich — sie **sieht aus** wie eine Bahnebene. ===== Die Parameter im Einzelnen ===== ^ Name ^ Bedeutung ^ Einheit ^ Gültiger Bereich ^ | ''rx'', ''ry'', ''rz'' | Ort im Inertialsystem, vom **Mittelpunkt** des Zentralkörpers aus — nicht von der Oberfläche | m | Betrag > 0; der Ursprung ist ungültig | | ''vx'', ''vy'', ''vTarget'' | Geschwindigkeit im **selben** Rahmen wie der Ort ([[https://github.com/SEOLizer/LyX-Compiler/issues/1902|#1902]]: das dritte Feld sollte ''vz'' heißen) | m/s | beliebig, auch 0 | | ''s'' | ein ''AState''; die meisten Funktionen prüfen ihn nicht selbst — ''AStateValid'' ist die Stelle dafür | — | — | | ''a'', ''b'' | Vektoren; bei ''AAngle'' und ''AUnitVec'' darf keiner der Nullvektor sein | je nach Zusammenhang | — | | ''s: f64'' bei ''AScale'' | Streckfaktor | dimensionslos | beliebig, auch negativ | > **Die Höhe ist nicht der Radius.** Ein Zustand in „400 km Höhe" hat ''rx = ARadiusEarth() + 400000.0'' — also rund 6 778 137 m. Wer die Höhe direkt einsetzt, rechnet mit einer Bahn **im Erdinneren**; alle Formeln liefern dann Zahlen, nur eben falsche. Das ist der häufigste Fehler beim Einstieg, und keine Funktion kann ihn erkennen. > > Die Radiusfunktionen dafür stehen in [[lyx_-_programmiersprache:units:astro:orbit|std.astro.orbit]]: ''ARadiusEarth()'', ''ARadiusMoon()'', ''ARadiusMars()''. ===== Erstes Beispiel: ein Zustand und sein Dreibein ===== import std.astro.state; import std.astro.orbit; import std.math; import std.io; fn zeig(n: pchar, v: f64, d: int64): void { Print(n); PrintLn(FloatToStr(v, d)); } fn BI(b: bool): int64 { if (b) { return 1; } return 0; } fn main(): int64 { var mu: f64 := AMuEarth(); var r: f64 := 6678000.0; // 300 km Hoehe var vc: f64 := ACircularSpeed(mu, r); var inc: f64 := 51.6 * Pi() / 180.0; // Neigung wie die ISS var s: AState := AMakeState(r, 0.0, 0.0, 0.0, vc * CosF64(inc), vc * SinF64(inc)); Print("Zustand gueltig : "); PrintLn(IntToStr(BI(AStateValid(s)))); zeig("Radius : ", ARadius(s), 1); zeig("Tempo : ", ASpeed(s), 2); zeig("Radialtempo : ", ARadialSpeed(s), 6); zeig("|h| : ", AMagnitude(AAngularMomentum(s)), 1); var t: AVec3 := ATangent(s); var b: AVec3 := ABinormal(s); var n: AVec3 := ANormal(s); zeig("T*B (soll 0) : ", ADot(t, b), 12); zeig("|N| (soll 1) : ", AMagnitude(n), 12); Print("Rahmen ECI==ECEF : "); PrintLn(IntToStr(BI(AFrameMatches(AFRAME_ECI, AFRAME_ECEF)))); Print("Rahmen UNKNOWN : "); PrintLn(IntToStr(BI(AFrameMatches(AFRAME_UNKNOWN, AFRAME_UNKNOWN)))); return 0; } Zustand gueltig : 1 Radius : 6678000.0 Tempo : 7725.84 Radialtempo : 0.000000 |h| : 51593156041.7 T*B (soll 0) : 0.000000000000 |N| (soll 1) : 1.000000000000 Rahmen ECI==ECEF : 0 Rahmen UNKNOWN : 0 ===== Zweites Beispiel: die Fehlerfälle ===== Die Unit gibt bei unmöglicher Eingabe NaN zurück — hier drei Fälle, in denen das trägt: import std.astro.state; import std.io; fn BI(b: bool): int64 { if (b) { return 1; } return 0; } fn zeigVec(n: pchar, v: AVec3): void { Print(n); if (AVecIsError(v)) { PrintLn("NaN (nicht bestimmt)"); return; } Print("("); Print(FloatToStr(v.x, 3)); Print(", "); Print(FloatToStr(v.y, 3)); Print(", "); Print(FloatToStr(v.z, 3)); PrintLn(")"); } fn main(): int64 { // 1. Senkrechter Aufstieg: Ort und Geschwindigkeit parallel -> keine Bahnebene var senk: AState := AMakeState(6678000.0, 0.0, 0.0, 1200.0, 0.0, 0.0); Print("Zustand gueltig : "); PrintLn(IntToStr(BI(AStateValid(senk)))); zeigVec("Tangente : ", ATangent(senk)); zeigVec("Binormale : ", ABinormal(senk)); zeigVec("Normale : ", ANormal(senk)); Print("Radialtempo : "); PrintLn(FloatToStr(ARadialSpeed(senk), 1)); // 2. Ort im Ursprung: dort ist die Schwerkraft nicht definiert var nullpunkt: AState := AMakeState(0.0, 0.0, 0.0, 0.0, 7700.0, 0.0); Print("Ursprung gueltig : "); PrintLn(IntToStr(BI(AStateValid(nullpunkt)))); // 3. NaN pflanzt sich fort und wird EINMAL geprueft var x: f64 := AAngle(AVec(0.0, 0.0, 0.0), AVec(1.0, 0.0, 0.0)); var y: f64 := x * 2.0 + 100.0; Print("Kette NaN geblieben: "); PrintLn(IntToStr(BI(AIsError(y)))); return 0; } Zustand gueltig : 1 Tangente : (1.000, 0.000, 0.000) Binormale : NaN (nicht bestimmt) Normale : NaN (nicht bestimmt) Radialtempo : 1200.0 Ursprung gueltig : 0 Kette NaN geblieben: 1 Drei Dinge stehen darin: * Der senkrechte Aufstieg ist ein **gültiger** Zustand — ''AStateValid'' meldet 1, die Tangente zeigt nach außen, das Radialtempo ist die volle Geschwindigkeit. Nur eine **Bahnebene** gibt es nicht, und deshalb sind Binormale und Normale NaN. Eine gewählte Ersatzrichtung sähe hier aus wie eine echte Bahnebene. * Der Ursprung wird von ''AStateValid'' abgefangen, bevor irgendeine Formel durch den Abstand teilt. * NaN überlebt jede Rechnung: ''x * 2.0 + 100.0'' ist immer noch NaN. Deshalb genügt **eine** Prüfung am Ende einer Kette statt einer nach jedem Schritt. ---- Letzte Aktualisierung: 2026-09-05 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1902|#1902]], nachgemessen mit lyxc 1.2.2B) — das Feld heißt wieder ''vz''; der Kasten zum Benennungsfehler ist auf den behobenen Stand gezogen. Vorherige letzte Aktualisierung: 2026-08-31 — Parametertabelle samt Höhe-gegen-Radius-Kasten und ein zweites Beispiel zu den Fehlerfällen ergänzt; beide Beispiele mit ''lyxc 1.1.15B'' gebaut und ausgeführt. Zuvor 2026-08-31 — Seite neu angelegt aus ''std/astro/state.lyx'' (PR #1890).