====== 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).