Moderne C2-Dashboards rendern keine Hunderte von Tracks mehr. Sie rendern Zehntausende. Luftlagebilder aus föderierten Radarnetzen, AIS-Maritimfeeds, Drohnenschwarm-Telemetrie, GPS-Pings von Bodeneinheiten und fusionierte Multisensor-Tracks landen alle auf demselben Operator-Bildschirm. Ein praktikables gemeinsames Lagebild erreicht 2026 routinemäßig über 100.000 gleichzeitige Tracks. Die Visualisierungsschicht hält unter dieser Last entweder 60 fps — oder der Operator verliert das Vertrauen in das System.

Dieser Artikel ist ein Engineering-Leitfaden dafür, wie man Frame-Budgets bei dieser Größenordnung ehrlich hält. Wir behandeln WebGL-Instancing, Cesium-Primitive-Batching, deck.gl-Layer-Komposition, LOD-Strategie, GPU-Speicherbudgets, Frame-Pacing-Arithmetik und das Testharness, das Sie brauchen, um Regressionen vor dem Operator zu fangen.

Die 100.000-Track-Realität

Die alte Faustregel — „eine taktische Anzeige muss ein paar Tausend Tracks verarbeiten" — war 2010 korrekt und ist 2026 gefährlich falsch. NATO-Multisensor-Fusionsfeeds, Tiefflug-Radargitter und kommerzielle ADS-B/AIS-Aggregatoren erzeugen während Übungen und aktiven Operationen routinemäßig sechsstellige Track-Zahlen. Allein die Drohnenschwarm-Doktrin kann 200–500 freundliche Emitter in eine 50-km-Box bringen.

Die Performance-Lücke zählt, weil das Operator-Vertrauen unter 30 fps zusammenbricht. Sobald das Schwenken ruckelt oder die Symbologie hinter dem zugrundeliegenden Track-Update zurückbleibt, misstraut der Operator allem anderen auf der Anzeige — und beginnt, das Bild gegen eine Papierkarte oder einen Zweitbildschirm gegenzuprüfen. Diese Gegenprüfzeit ist genau die operative Latenz, die das C2-System eliminieren sollte. Ein C2-Dashboard, das unter realistischer Last unter 60 fps fällt, ist kein langsames C2-Dashboard. Es ist ein kaputtes.

Das Ziel ist fix: 60 fps anhaltend, mit 100.000+ Tracks, auf einer typischen Mid-Tier-Operations-Workstation (8-Kern-CPU, Mid-Range-dGPU, 1440p). Dieses Ziel zu erreichen erfordert, dass jede Schicht des Stacks — Geometrie, Draw Calls, GPU-Speicher, Netzwerk-Ingest, State-Mutation — das 16,67-ms-Frame-Budget respektiert.

WebGL-Instancing

Der erste Hebel ist der Draw Call. 100.000 individuelle Draws pro Frame auszugeben ist auf keiner aktuellen GPU bei 60 fps möglich; allein der CPU-seitige Treiber-Overhead verbrennt das gesamte Budget. Instanziertes Rendering kollabiert Tausende von Symbolen in einen einzigen Draw Call. Eine Geometrie (das Symbol-Mesh), ein Shader und ein Per-Instance-Attribut-Buffer mit Position, Heading, Zugehörigkeit und Symbol-ID.

Das Standardmuster verwendet ANGLE_instanced_arrays in WebGL 1 oder natives drawArraysInstanced in WebGL 2. Per-Instance-Attribute streamen aus einem dicht gepackten Buffer: typischerweise 32 Byte pro Track (vec3 Position, vec2 Velocity, uint32 gepackte Flags). Bei 100k Tracks sind das 3,2 MB Vertex-Attribut-Daten — klein genug, um bei Bedarf jeden Frame neu hochgeladen zu werden, obwohl partielle Updates via bufferSubData günstiger sind.

Three.js stellt Instancing über InstancedMesh bereit; deck.gl behandelt es für fast jeden Layer nativ; Cesiums Primitive-API unterstützt es über GeometryInstance-Arrays. Die drei Frameworks landen auf demselben Spektrum — Three.js gibt Ihnen die meiste Freiheit und die wenigste eingebaute Geospatial-Mathematik, deck.gl ist der schnellste Weg zu einer funktionierenden hochdichten Layer, Cesium liefert 3D-Globus-Semantik und Terrain-Occlusion, die die anderen beiden nicht haben.

Cesium-Primitive-Batching

Cesiums Entity-API ist das falsche Werkzeug oberhalb von 5.000 Tracks. Entitäten allozieren pro Track JavaScript-Objekte, führen eine CPU-seitige Update-Schleife aus und bauen Geometrie bei Property-Änderungen neu auf. Die Kosten amortisieren sich bei kleinen Mengen gut und sind bei großen katastrophal.

Für 100k-Skalen-Rendering steigen Sie auf die Primitive-API herab. Eine PointPrimitiveCollection rendert bis zu ~1 Mio. Punkte in einem einzigen Draw Call. Eine BillboardCollection verarbeitet Zehntausende von icon-texturierten Sprites mit einem gemeinsamen Texture-Atlas. Das GeometryInstance-Muster gruppiert Tausende statischer Geometrien (z. B. Reichweitenringe, Geofences) zur Erstellungszeit zu einem gebatchten Primitive.

Label-Rendering ist asymmetrisch. Eine 100k PointPrimitiveCollection rendert mühelos; 100k Labels obendrauf sprengen das Frame-Budget sofort. Labels gehen durch Cesiums SDF-Text-Pfad, der sowohl Glyph-Atlas-Speicher als auch einen separaten gebatchten Draw kostet. Die Lösung ist LOD-gesteuerte Label-Sichtbarkeit: Labels nur für Tracks innerhalb eines zoomabhängigen Bildschirm-Radius um den Cursor oder für von der Fusion-Engine als „von Interesse" markierte Tracks rendern. Ein typischer Operator-Bildschirm braucht nicht mehr als 50–200 sichtbare Labels gleichzeitig.

deck.gl-Layer für die Verteidigung

deck.gl sitzt auf einer MapboxGL- oder MapLibre-Basiskarte und gibt Ihnen einen komponierbaren Layer-Stack, der genau für dieses Problem entworfen wurde. Die relevanten Layer für eine C2-Anzeige:

ScatterplotLayer. Das Arbeitstier für rohe Track-Positionen. Rendert Millionen von Punkten bei 60 fps, weil jedes Attribut (Position, Farbe, Radius) GPU-gebunden ist. Verwenden Sie es für unsymbolisierte Track-Punkte, Sensor-Abdeckungsringe und hochdichte flache Layer.

IconLayer. Rendert MIL-STD-2525-Symbole aus einem Texture-Atlas. Die Performance skaliert mit der Atlas-Größe; packen Sie 2525-Symbologie in einen einzigen 4096×4096-Atlas mit allen vorgerasterten Zugehörigkeits-/Echelon-Varianten. Bei 100k Icons mit einem einzigen gemeinsamen Atlas hält IconLayer auf Mid-Tier-Hardware komfortabel 60 fps.

PathLayer. Für Track-Historien und projizierte Kurse. Die Kosten skalieren mit der Vertex-Anzahl, nicht mit der Pfad-Anzahl — bevorzugen Sie die Dezimierung langer Historien (Douglas-Peucker, mit zoom-abhängigem Epsilon) gegenüber dem Verwerfen von Pfaden.

GPU-Aggregations-Layer. ScreenGridLayer, HexagonLayer und HeatmapLayer aggregieren Millionen von Punkten auf der GPU zu Bin-summierten Visualisierungen. Nützlich als Zoom-Out-Dichte-Overlays — bei niedrigem Zoom wollen Sie nicht 100k Symbole sehen, Sie wollen den Bedrohungsdichte-Gradient sehen.

Level-of-Detail (LOD)-Strategie

Die effektivste Performance-Optimierung besteht darin, nicht zu rendern, was der Operator nicht sinnvoll sehen kann. Bei einem 2000 km breiten Zoom-Level kann eine 100k-Track-Anzeige einzelne Symbole nicht auflösen — jedes Bildschirm-Pixel deckt mehrere Tracks ab. Volle MIL-STD-2525-Symbologie bei diesem Zoom zu rendern ist verschwendete GPU-Zeit und erzeugt ein unlesbares Bild.

Die LOD-Leiter: Bei weitem Zoom aggregierte Dichte (GPU-binned Heatmap oder Hex-Layer) rendern; bei mittlerem Zoom unsymbolisierte Punkte nach Zugehörigkeit eingefärbt rendern; bei nahem Zoom volle 2525-Symbologie mit Labels für markierte Tracks rendern; bei maximalem Zoom volle Symbologie mit Labels, Historien-Trails und projizierten Kursen für jeden sichtbaren Track rendern.

Bildschirm-Clustering ist die Ergänzung. Selbst bei nahem Zoom erzeugen dichte Cluster (ein Parkplatz voller Fahrzeuge, ein Hafen voller Schiffe) überlappende Symbole, die einander verdecken. Ein k-d-Tree- oder Grid-Bin-Clustering-Durchlauf (auf dem Worker-Thread, nicht dem Main-Thread) kollabiert überlappende Symbole zu einem einzigen „N Tracks hier"-Badge, bis der Operator hineinzoomt.

Das Nutzungsprofil rechtfertigt aggressives LOD: Ein Operator zoomt einmal alle 30–60 Sekunden hinein und verbringt die meiste Zeit damit, das Weitbild zu scannen. Das Frame-Budget des Weitbilds zu optimieren zahlt sich kontinuierlich aus; das Close-Zoom-Frame-Budget zu optimieren zahlt sich nur in Momenten aktiven Interesses aus.

GPU-Speicherbudgets

Speicherdruck ist der stille Killer hochdichter Anzeigen. Die sichtbare Track-Anzahl ist nur ein Teil des Budgets. Texture-Atlanten (Symbol-Sheet, Terrain-Kacheln, Basemap-Raster-Kacheln), Vertex-Buffer (Per-Instance-Attribute, History-Pfade), Uniform-Buffer, Framebuffer-Anhänge und der eigene Compositor des Browsers ziehen alle aus demselben Pool.

Die realen Budgets, mit denen wir planen: Eine 4-GB-integrierte GPU (Intel Iris Xe, AMD Radeon 780M) auf einem ausgelieferten Laptop hat etwa 2–2,5 GB nutzbar für den WebGL-Kontext, nachdem OS, Browser und andere Tabs ihren Anteil nehmen. Eine 16-GB-Diskret-GPU (RTX-4070-/5070-Klasse) hat 12+ GB nutzbar. Viele konservative Bereitstellungen — Operations-Center mit gehärteten Workstations, die vor Jahren beschafft wurden — laufen noch auf iGPU-Klassen-Hardware. Für das iGPU-Envelope zu entwerfen ist der sicherere Standard.

Praktische Zahlen für ein 100k-Track-C2-Dashboard: Per-Instance-Attribut-Buffer ~5–10 MB; Symbol-Atlas ~64 MB (4096² RGBA); Basemap-Raster-Cache ~200–400 MB; Terrain-Kachel-Cache ~300–600 MB; History-Pfad-Geometrie ~20–50 MB je nach Aufbewahrung. Gesamt ~600 MB–1,2 GB. Das passt mit Reserve in das iGPU-Envelope, aber nur wenn jede Schicht diszipliniert mit Texturgrößen und Buffer-Wachstum umgeht.

Latenz und Frame-Pacing

Das 16,67-ms-Frame-Budget gliedert sich grob: 2–3 ms für Input-Handling und State-Mutation, 4–6 ms für Layer-Update (CPU-seitig, hauptsächlich Attribut-Buffer-Neuberechnung und Culling), 6–8 ms für GPU-Rendering, 1–2 ms für Compositor-Overhead. Alles, was mehr als seinen Anteil verbraucht, klaut vom nächsten Anteil und erzeugt einen verlorenen Frame.

Die schlimmsten Spikes verstecken sich an leicht zu übersehenden Stellen. Track-Korrelations-Joins auf dem Main-Thread — den Korrelationsdurchlauf der Fusion-Engine inline mit dem Rendering laufen lassen — erzeugen 50–200 ms Stalls jedes Mal, wenn ein neuer Sensor-Batch eintrifft. Die Lösung ist, die Korrelation auf einem Worker-Thread laufen zu lassen und unveränderliche Track-Deltas an den Main-Thread zu posten. Server-gepushte Update-Bursts (ein WebSocket, der 5.000 Track-Updates in einem Tick ablädt) sättigen die JS-Event-Loop; Deltas auf ein gepuffertes Update pro Frame raten-limitieren und batchen.

Garbage Collection ist die dritte Spike-Quelle. Das Allozieren neuer Objekte pro Track pro Frame erzeugt alle paar Sekunden sägezahnartige GC-Pausen. Verwenden Sie Typed-Array-Pools und wiederverwenden Sie Buffer; vermeiden Sie Pro-Frame-Objekt-Literal-Erstellung in heißen Pfaden. Die 60-fps-oder-Pleite-Erwartung ist real, und eine einzige 100-ms-GC-Pause alle 10 Sekunden ist genau die Art von Ruckeln, die das Operator-Vertrauen zerstört.

Testen im großen Maßstab

Sie können keine 100k-Track-Anzeige ausliefern ohne ein Testharness, das Lasten in dieser Größenordnung erzeugt und wiedergibt. Drei Komponenten: ein synthetischer Track-Generator, eine automatisierte FPS-Regressions-Suite und eine aufgezeichnete-Wiedergabe-Wahrheitsquelle.

Der synthetische Generator erzeugt deterministische 100k-Track-Szenarien — zufällige Verteilungen, dichte Cluster, Drohnenschwarm-Formationen, Massen-Raid-Szenarien — jedes geseedet, sodass ein CI-Lauf jedes Mal dieselbe Szene reproduziert. Jedes Szenario treibt einen Headless-Browser durch einen geskripteten Kamerapfad (Schwenken, Hineinzoomen, Herauszoomen, Entcluttern, Filtern), während das Harness performance.now()-Delta-Histogramme abtastet und p50/p95/p99-Frame-Zeiten meldet.

Die FPS-Regressions-Suite läuft bei jedem PR. Die Schwellenwerte sind explizit: p95-Frame-Zeit unter 18 ms, p99 unter 25 ms, keine verlorenen Frames über 50 ms in einem 60-Sekunden-Skript-Lauf. Jeder Commit, der die Zahlen über die Schwelle drückt, blockiert den Merge. Das ist die einzige Möglichkeit, die Tod-durch-tausend-Schnitte-Regressionen zu fangen, bei denen jede einzelne Änderung 0,2 ms kostet.

Real aufgezeichnete Sensor-Wiedergabe ist die Wahrheitsquelle. Synthetische Lasten fangen Performance-Klippen, aber verfehlen die asymmetrischen Verteilungen echter Daten — die Cluster, die Lücken, die Burst-Muster. Eine pcap-artige Aufzeichnung eines Live-Übungsfeeds, mit Wanduhr-Geschwindigkeit gegen das Dashboard abgespielt, ist das Näheste, was Sie an operativer Last ohne Operator im Stuhl bekommen. Paaren Sie das synthetische Harness mit zwei oder drei aufgezeichneten Szenen aus Verifikationsübungen, und Sie haben ein Regressionsnetz, das hält.