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².

std.astro (Übersicht) · std.astro.orbit · 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 (#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 Ursprungfalse
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 (#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 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 (#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).