Deutsch | English
Browser-basierter N-Körper-Gravitationssimulator. Der Standard-Modus ist eine einzige HTML-Datei — kein Build, kein Backend, einfach im Browser öffnen. Für massive Szenarien (Hunderttausende Körper) gibt es ein optionales CUDA-Backend mit Multi-GPU-Physik und einem entkoppelten Film-Modus.
- 19 vordefinierte Szenarien: Sonnensystem, Sonnensystem mit Asteroidengürteln (Asteroiden- und Kuipergürtel, Anzahl per Slider konfigurierbar), TRAPPIST-1, Alpha Centauri, Kepler-16, Kepler-47 (Doppelstern + 3 Planeten), Trisolaris (3 Sonnen), Lagrange- Konstellationen (stabil/instabil), Trojaner (L4), Figur-8-Choreografie, Butterfly I, Moth I, Goggles, Yarn, „Leeres System" zum freien Bauen — sowie drei kosmologische Szenarien (nur mit CUDA-Backend): Galaxienhaufen (Kollaps / Expansion) und Strukturbildung (selbstgravitierend), siehe Selbstgravitierende Strukturbildung
- Selbstgravitierende Strukturbildung — hunderttausende Massen, bei denen jede jede andere anzieht (nicht nur wenige Zentralmassen). Aus einer fast homogenen Wolke wachsen Halos, Brücken und Voids: die qualitative Signatur des kosmischen Netzes. Getestet mit > 750 000 Körpern (250 k Massen + 500 k Tracer) über fünf GPUs
- Störmassen interaktiv injizieren — Position, Masse (10⁻³ bis 10⁶ Erdmassen, inkl. Sternen ab ~80 M⊕), Geschwindigkeit und Richtung
- Asteroiden-Wolken injizieren — ganze Schwärme aus Kleinkörpern mit Anzahl (logarithmisch bis 50 000 pro Injektion), Gesamtmasse, Dichte und Streuung per Slider; Shift+Klick auf den Inject-Button (bzw. langer Druck auf Mobile) spawnt statt einer Rogue eine Wolke
- Fünf Physik-Engines per Toggle — Hauptthread, WebWorker, WebGPU, Hybrid und ein natives CUDA-Backend (f64, Multi-GPU), siehe Physik-Engines
- Film-Modus — Compute und Darstellung vollständig entkoppelt: die GPU rechnet mit maximalem Durchsatz voraus, der Browser spielt die Timeline wie ein Video (scrubben, zurückspulen, live folgen), siehe Film-Modus
- Asteroiden-Kollisionen (optional per Toggle) — Asteroiden prallen wie Kugeln unterschiedlicher Masse voneinander ab (massenabhängiger Slingshot: der schwerere wird blau, der leichtere rot markiert und fliegt weg). Beim Treffer auf einen Planeten/Stern wird ein Asteroid absorbiert; Körper × Körper mit hoher Relativgeschwindigkeit zerbirst in Fragmente. Alle drei Pfade greifen live wie im Film
- Echtzeit-N-Body-Integration mit konfigurierbarem Zeitschritt und Verlangsamungsfaktor; Pause, Reset und Einzelschritt
- Log-Zoom-Modus für gleichzeitige Sicht auf Sonne und äußere Planeten
- Bahnspuren (variable Länge), Kraftvektoren, Schwerpunkt- Zentrierung und Gitternetz ein-/ausblendbar
- Konfigurationen lokal speichern, exportieren und importieren — gesichert werden alle Körper samt Zustand und sämtliche Regler und Schalter (Zeitschritt, Film-Einstellungen, Darstellungsoptionen)
- Live-Statistik: aktive/gesamte Körper nach Typ, Schwerpunkt-Drift, Fluchtkurs-Erkennung; Energie-Statistik (kinetisch/potenziell/gesamt) optional zuschaltbar — die potenzielle ist O(M²) über alle Massen und bei zehntausenden Massen zu teuer, daher standardmäßig aus. Live-Benchmark-Badge (FPS, Tage/s, aktive Engine)
- Mobile vollständig unterstützt — Long-Press, Pinch-Zoom, 2-Finger-Pan, dedizierte mobile Toolbar und Bottom-Sheet
- Mehrsprachig Deutsch / Englisch — Toggle oben links im Seitenmenü,
Sprachwahl bleibt im
localStorageerhalten
Die Engine ist im Seitenmenü umschaltbar; die Wahl bleibt im
localStorage erhalten.
| Engine | Läuft auf | Eignung |
|---|---|---|
| Hauptthread (CPU) | Browser, ein Thread | Debug/Referenz |
| WebWorker | Browser, eigener Thread (f64) | Standard — auf den meisten Geräten der schnellste browserinterne Pfad |
| WebGPU | integrierte/dedizierte GPU (f32) | starke GPUs; auf schwacher iGPU langsamer als der Worker |
| Hybrid | Worker + gezielt genauere Asteroiden-Substeps | nahe Begegnungen präziser |
| CUDA-Backend | NVIDIA-GPUs, nativ f64, Multi-GPU | Hunderttausende Körper, Film-Modus |
Interessanterweise ist der WebWorker auf vielen (auch schwächeren) Rechnern der schnellste browserinterne Pfad: native f64-Arithmetik auf der CPU schlägt bei ~7 000 Körpern mit nur ~10 massiven Quellen eine schwache iGPU, deren Dispatch-/Sync-Latenzen pro Substep die eigentliche Rechenzeit übersteigen. Erst eine starke GPU (WebGPU) oder das CUDA-Backend drehen das Verhältnis.
Nur mit aktivem CUDA-Backend. Compute und Darstellung sind vollständig entkoppelt:
- Ein eigener GPU-Producer-Prozess rechnet mit vollem Durchsatz in einen Ringpuffer voraus und stoppt nur, wenn er zu weit vorläuft.
- Der Browser liest den Puffer als push-gestreamten Punktfilm und spielt die Timeline wie ein Videoplayer: scrubben, zurückspulen, live folgen an der Produktionskante.
- Übertragen werden nur Positionen als Integer-Bildschirmkoordinaten (server-seitiges View-Culling, u16-Quantisierung); Masse/Sichtbarkeit laufen als kompakte Ereignisse. Die Referenz-Box folgt dem Bildausschnitt, nicht der Szene — ein weit hinausgeschleuderter Körper verdirbt die Auflösung im Zentrum also nicht.
- Gemessene Zwischenbilder statt geratener Kurven: Für eng begegnende Asteroiden legt der Kernel in seiner Feinschleife Stützpunkte auf der echten Bahn ab (Regler, Voreinstellung 8 je Raster); der Browser interpoliert dazwischen nur noch geradlinig. Ein Perihel-Durchgang ist damit ein Messwert. Alle übrigen Körper laufen über Catmull-Rom durch vier Sample-Punkte — bei ihnen liegt der Sehnenfehler ohnehin unter der Sichtbarkeitsschwelle. Der Server spannt die Stützpunkte über das jeweils gestreamte Intervall, die Bandbreite je Körper bleibt damit unabhängig vom Abspieltempo.
- Dichte-LOD: Reicht das Punktbudget nicht für alle Körper, gilt eine Rangfolge — massive Körper (Sonne, Planeten, Rogues, Sterne, Schwarze Löcher) werden nie ausgedünnt, danach kommen die Asteroiden des geladenen Systems, den Rest bekommen nachträglich injizierte Wolken. Innerhalb jeder Stufe wird dichteabhängig gedünnt (behalten ~ Anzahl^0,5 je Gitterzelle): ein Gürtel aus wenigen hundert Objekten verschwindet dadurch nicht neben einer Wolke aus hunderttausenden, während dichte Gebiete sichtbar dichter bleiben. Die Auswahl läuft über einen Hash des Körperindex — stufenlos, ohne Rasterartefakte, und über Samples hinweg stabil. Das Budget ist per Regler einstellbar (Auto … „Alle"). Physik und Kollisionen laufen immer für alle Körper — ausgedünnt wird nur die Darstellung.
- Kollisions-Ereignisse (Merge, Kill, Bounce, Zerbersten) tragen ihren exakten Ort und Zeitpunkt und werden zeitrichtig als Explosionen abgespielt. Der Kontakt mit einem massiven Körper wird in der Feinschleife des Kernels je Substep geprüft — ein Körper, der mit 50 AE/Jahr in einen Stern stürzt, verschwindet dadurch in ihm und nicht eine Rasterweite davor (das wären 0,07 AE, das Fünfzehnfache eines Sonnenradius).
- Beim Verlassen des Films oder Engine-Wechsel wird der exakte f64-Zustand an die neue Engine übergeben — kein Impulsverlust.
So bleibt eine Wolke aus > 250 000 Körpern flüssig abspielbar, während die GPU im Hintergrund weiterrechnet.
Die klassischen Szenarien haben wenige Zentralmassen (Sonne, Planeten) und viele masselose Tracer, die das Feld nachzeichnen. Die kosmologischen Szenarien drehen das um: jeder Körper trägt Masse und zieht jeden anderen an. Aus einer fast homogenen Wolke wächst dann von selbst Struktur.
Was man sieht. Überdichten kollabieren unter ihrer eigenen Schwerkraft zu Halos (Knoten). Zwischen ihnen strömt Materie entlang der Gravitationsgradienten und bildet Brücken; dazwischen leeren sich Voids. Knoten + Brücken + Voids sind die qualitative Signatur des kosmischen Netzes. Das können masselose Tracer prinzipiell nicht — die folgen nur einem vorgegebenen Feld und bleiben glatt. Struktur emergiert erst durch die Selbstgravitation.
Wie es rechnet. Ein eigener selbstgravitierender Kernel (all-pairs
mit Tiling, O(N²)) mit Plummer-Softening: Unterhalb einer Distanz wird
die Masse als verschmiert statt punktförmig gerechnet. Das ist bewusst so —
ohne Softening würden nahe Teilchen sich künstlich wegschleudern
(Zweikörper-Streuung) und die Struktur zerrauschen. Deshalb gibt es hier
keine Kollisionen und keine Verschmelzungen: Bei Massen-Samples eines
Dichtefelds sind „Stoß" und „Merge" der falsche Begriff. Fester Zeitschritt,
keine Erkennungs-Pipeline — die freigewordenen Karten rechnen Physik mit.
Warum alle GPUs mitziehen. Der Kernel misst, dass der Zustand f64 braucht (Geschwindigkeitsinkremente unterhalb der f32-Auflösung), die Kraftsumme aber f32 verträgt (das Softening glättet sie ohnehin). Damit rechnen auch die in f64 schwachen Quadro RTX 8000 die Kräfte in f32 voll mit — alle fünf Karten am Anschlag statt zwei brachliegend. Getestet mit > 750 000 Körpern; der Aufwand wächst mit ~N^2,5 (mehr Teilchen = feiner aufgelöst, plus feinerer Zeitschritt durch das mitschrumpfende Softening), Details in Hardware & Findings.
Drei Szenarien, ein Aufbau:
- Galaxienhaufen (Kollaps) — nur Rotation, 58 % der Fluchtgeschwindigkeit. Tief gebunden → die Wolke fällt zusammen: dichter Kern mit Halo.
- Galaxienhaufen (Expansion) — zusätzlich ein radialer Hubble-Fluss (marginal gebunden, Ω ≈ 1). Die Expansion verhindert den globalen Kollaps; es bleibt ein sich dehnendes Feld mit Struktur darin.
- Strukturbildung (selbstgravitierend) — Regler für Massen, Tracer und Weichzeichnung (Softening als Vielfaches des mittleren Teilchenabstands, damit der Wert bei jeder Teilchenzahl passt).
Ehrlich eingeordnet. Das ist der kollaps-getriebene Fall — die Knoten sind eher rundliche Halos. Echte, langgezogene kosmologische Filamente brauchen zusätzlich die Expansion des Raums und anisotrope Anfangsbedingungen; das ist eine 2D-Karikatur ohne mitwachsende Metrik. Die Brücken und Voids sind aber echte selbstgravitierende Struktur, kein nachgezeichnetes Feld.
Optional, für die letzten beiden Engines und den Film-Modus. Benötigt NVIDIA-GPU(s) mit CUDA-12-Treiber. Das Frontend funktioniert ohne Backend vollständig (WebWorker/WebGPU) — das Backend schaltet nur die CUDA-Engine und den Film-Modus frei.
- Native f64-Physik in einem CuPy/NVRTC-Kernel (Yoshida-4.-Ordnung, identisch zur Worker-Referenz), der ganze Frame läuft als ein cooperative Launch (Grid-Sync).
- Multi-GPU-Sharding, hardwareagnostisch: die Asteroiden werden gewichtet nach f64-Score auf alle tauglichen Karten verteilt, die massiven Körper sind repliziert und bleiben bit-identisch. Die Substep-Synchronisation läuft atomics-frei über PCIe-gemappten Host-Speicher — kein NVLink nötig. Mit einer Karte degeneriert dieselbe Codebahn ohne Overhead.
- Hierarchische Zeitschritte: nur sonnennahe Körper („Taucher") laufen in privaten Feinschleifen, der Rest im groben Raster — statt dass ein einziger Sonnenstürzer die Rate aller Körper drückt.
- Kollisions-/Bounce-Erkennung pipelined auf eigenen GPUs (f32-Vorfilter + exakte f64-Nachprüfung), überlappt mit der Physik. Die Bounce-Suche ist der Engpass (75–93 % der Batchzeit) und wird räumlich auf bis zu zwei Karten aufgeteilt: jede prüft einen x-Streifen plus einen Halo und behält nur Paare, deren linkerer Partner ihr gehört — lückenlos, überschneidungsfrei, ohne Datenaustausch zwischen den Karten. Ob sich das lohnt, hängt an der Kandidatenzahl pro Sample, nicht an der Körperzahl: dieselben 250 k Asteroiden ergeben als dichter Klumpen über 10⁹ zu prüfende Paare, als ausgebildeter Gürtel unter 10⁶. Der Producer schaltet die zweite Karte deshalb pro Batch zu und ab (Schwellen mit Hysterese, gemessener Gewinn bis 1,7×) — ohne Filmneustart.
- Server-Lebenszyklus vom Browser gesteuert: Auswahl der CUDA-Engine startet den Server on-demand (optional per systemd socket activation), Abwahl beendet ihn nach kurzer Leerlauffrist — GPUs sind im Ruhezustand frei.
cd backend
# venv im Projekt-Root, mit CuPy für CUDA 12 (Toolkit im Wheel enthalten):
python3 -m venv ../venv
../venv/bin/pip install "cupy-cuda12x[ctk]" websockets numpy
# Server starten (Default-Port 8765, wählt automatisch die beste f64-GPU):
../venv/bin/python server.pyDanach im Browser die Engine „CUDA-Backend (nativ, f64)" wählen; das
Frontend verbindet sich per WebSocket (lokal 127.0.0.1:8765, remote über
einen Reverse-Proxy).
Nützliche Schalter: --ring-gib (Größe des Film-Ringpuffers, bestimmt die
navigierbare Vergangenheit), --det-gpus (Erkennungskarten pro Session),
--diag (Zeitanteile von Producer und Stream-Loop mitloggen — für die
Engpass-Suche, im Normalbetrieb aus).
Tests und Messwerkzeuge (Standalone-Skripte, kein pytest):
cd backend
../venv/bin/python test_kernel.py # Kernel gegen NumPy, 1/2/3 GPUs
../venv/bin/python test_film_golden.py # Kollisionskette Ende-zu-Ende
../venv/bin/python test_erkennung_streifen.py # Streifen == eine Karte
../venv/bin/python test_lod_dichte.py # Rangfolge + Dichte-Auswahl
../venv/bin/python bench_erkennung.py # Umschaltschwelle der 2. Karte
../venv/bin/python bench_film.py --szene knoedel --det-gpus 1 2| Eingabe | Aktion |
|---|---|
| Linksklick + ziehen | Position + Geschwindigkeitsvektor setzen |
| Rechts-/Mittelklick + ziehen | Ansicht verschieben (Pan) |
| Mausrad | Zoom |
P |
Pause / Weiter |
R |
Szenario zurücksetzen |
Leertaste |
Einzelschritt (im Pause-Modus) |
| Geste | Aktion |
|---|---|
| 1 Finger lang halten | Position armieren (Long-Press) |
| Lang halten + ziehen | Geschwindigkeit setzen |
| 2 Finger pinch | Zoom |
| 2 Finger ziehen | Ansicht verschieben |
| Untere Toolbar | Pause, Zoom ±, Reset, Zentrieren |
| Rechte Aktions-Buttons | Fliehende / Störmassen / Wolken entfernen |
| Roter FAB unten rechts | Injizieren (langer Druck: Wolke) |
| Hamburger oben links | Einstellungen-Sheet |
Die Sonnensystem-Koordinate einer geplanten Injektion bleibt während Zoom, Pan und Pinch stabil — der Marker zeigt sie vor jedem „Injizieren"- Klick an, und ein erneuter Klick spawnt eine weitere Masse exakt an derselben Stelle.
Für den Standard-Modus reicht jeder statische Webserver, z. B.:
python3 -m http.server 8080
# Browser: http://localhost:8080/Alternativ einfach index.html direkt im Browser öffnen (file:// —
die Simulation selbst läuft komplett offline; nur die optionalen
Footer-Elemente (GoatCounter-Pageview-Zähler, GitHub-Stars-Badge)
sind dann inaktiv). Für die CUDA-Engine + Film-Modus siehe
CUDA-Backend.
Mit großen Asteroidengürteln (~1400 Körper) gibt es Unterschiede zwischen den Browsern. Für den browserinternen Betrieb (ohne CUDA-Backend):
Desktop:
| Browser | Empfehlung | Bemerkung |
|---|---|---|
| Edge / Chrome | WebGPU an (starke GPU) oder WebWorker | Dawn-Backend + DirectX-12-Pfad; auf schwacher iGPU ist der Worker oft schneller |
| Firefox | WebWorker | wgpu-Backend noch unreif; Canvas-Operationen in Firefox teurer pro Call |
Android:
| Browser | Empfehlung | Bemerkung |
|---|---|---|
| Chrome | WebWorker oder WebGPU | Auf den meisten Geräten liefert der Worker die stabilsten 60 fps |
| Firefox | WebWorker | Schwächeres wgpu-Backend |
Bei wenigen Bodies (Standard-Szenarien ohne Gürtel) macht der Unterschied keine Rolle; alle Browser laufen flüssig auf 60 fps. Für Hunderttausende Körper führt kein Weg am CUDA-Backend vorbei.
- Reines HTML / CSS / Vanilla-JS in einer Datei; Asteroiden werden per WebGL-Punkt-Batch in einem Draw-Call gerendert (statt zehntausender Canvas-Calls), Rest über Canvas-2D.
- N-Body-Integration mit symplektischem Verlet (Yoshida-4. Ordnung); asteroidenreiche Szenarien überspringen die Asteroid-Asteroid-Gravitation für O(N)- statt O(N²)-Aufwand.
- Kontinuierliche Kollisionserkennung (CCD) über parametrischen Bahnschnitt — schnelle Begegnungen (Wolke gegen Gürtel im Gegenlauf) werden zum Kontaktzeitpunkt aufgelöst, ohne dass Körper hindurchtunneln.
- Keine Build-Pipeline, kein npm, keine Bibliotheken — die Sim-Engine lädt nichts nach. Nur der Footer holt optional GoatCounter (privacy- friendly Pageview-Zähler) und den GitHub-Stars-Wert.
- Netz-Worker im Film-Modus: WebSocket-Empfang und Frame-Zerlegung laufen in einem eigenen Worker, die Nutzlast wird transferiert statt kopiert. Sonst blockiert der Renderloop des Hauptthreads das Leeren des Sockets, das TCP-Empfangsfenster kollabiert und die Leitung wird nicht ausgelastet.
localStoragefür gespeicherte Konfigurationen und UI-Settings.
- Python + CuPy/NVRTC —
server.py(WebSocket-Server, Session- Verwaltung, Integer-Streaming mit Culling und LOD),film_producer.py(Producer-Prozess: Ringpuffer, wählt je nach Szenario den Kernel). Zwei Rechenkerne für zwei Regime:nbody_kernel.py— Multi-GPU-f64-Kernel mit hierarchischen Zeitschritten und Kollisions-/Bounce-/Zerberst-Erkennung. Für wenige Massen (≤ 64) und viele Tracer: Sonnensysteme, Galaxienhaufen mit Testteilchen. Exakte Kräfte, Bahnen auf der Messerschneide.selfgrav_kernel.py— selbstgravitierender Kernel (all-pairs mit Tiling), zehntausende gleichberechtigte Massen, Plummer-Softening, ohne Kollisionen. f64-Zustand, f32-Kraft — so tragen auch die in f64 schwachen Karten voll bei. Für die Strukturbildung.
- Prozess-Split: der Producer besitzt die GPU exklusiv, der Server beantwortet Puffer-Anfragen aus dem Shared Memory in Mikrosekunden — die Physik-GPU verhungert nie am GIL.
- Tests:
test_kernel.py(Kernel gegen NumPy-Referenz, 1/2/3-GPU + Benchmarks),test_film_golden.py(Kollisionskette Ende-zu-Ende),test_erkennung_streifen.py(die räumlich aufgeteilte Bounce-Suche liefert exakt dieselben Treffer wie eine einzelne Karte),test_lod_dichte.py(Rangfolge und dichteabhängige Punktauswahl). Messwerkzeuge:bench_erkennung.py,bench_film.py.
Das CUDA-Backend wurde auf einem ungewöhnlichen Setup entwickelt und gemessen: fünf Rechenzentrums-GPUs (3× Tesla V100, 2× Quadro RTX 8000) an einem Mini-PC, alle über M.2-zu-Oculink- bzw. USB4-Adapter mit nur PCIe ×4 angebunden, ohne NVLink. Warum das trotzdem alle fünf Karten gleichzeitig auslastet (anders als LLM-Inferenz), wie der f64/f32-Split die f64-schwachen Karten voll mitziehen lässt, warum „100 % Auslastung" bei 42 W nichts heißt und weitere Erkenntnisse:
MIT