Spaces:
Running
Running
chore(sync): mirror static/3d kit + Dockerfile-COPY'd web/*.html to Space (hf-sync)
Browse filesAutomated 3D/web-page asset sync from szl-holdings/a11oy main via hf-sync.
web/*.html set parsed live from the Dockerfile COPY directives.
Added/updated: 53 file(s)
Deleted (gone from GitHub main): (none)
Keeps static/3d/** and every Dockerfile-COPY'd web/<page>.html identical
to GitHub main so an HF factory rebuild never drops a GitHub edit (closes
the chronic hf-module-drift red for the 3D kit + baked web pages).
- static/3d/surfaces/counter-uas.js +569 -53
- static/3d/surfaces/data/drones_db.json +1152 -0
- static/3d/vendor/VENDOR_MANIFEST.md +33 -1
static/3d/surfaces/counter-uas.js
CHANGED
|
@@ -1,77 +1,593 @@
|
|
| 1 |
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
// © 2026 Lutar, Stephen P. Jr. — SZL Holdings · Doctrine v11
|
| 3 |
//
|
| 4 |
-
// surfaces/counter-uas.js —
|
| 5 |
//
|
| 6 |
-
// Leader/technique
|
| 7 |
-
//
|
|
|
|
| 8 |
//
|
| 9 |
-
//
|
| 10 |
-
//
|
| 11 |
-
//
|
| 12 |
-
//
|
| 13 |
-
//
|
| 14 |
-
// ctx.container the surface's DOM panel (for overlays/badges/HUD)
|
| 15 |
-
// ctx.live the szl3d_live module (ctx.live.poll(endpoint, ms, onData, {badge}))
|
| 16 |
-
// ctx.label the szl3d_label module (ctx.label.chip / .billboard / .legend)
|
| 17 |
-
// ctx.THREE the three module
|
| 18 |
-
// mount() attaches scene objects + starts polling. unmount() stops polls and removes
|
| 19 |
-
// DOM it added (the shell disposes the stage).
|
| 20 |
//
|
| 21 |
-
//
|
| 22 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
const ID = "counter-uas";
|
| 25 |
-
const TITLE = "Counter-UAS";
|
| 26 |
-
const ENDPOINT = "/counter-uas/evaluate";
|
| 27 |
-
const ACCENT = 0xff6b6b;
|
| 28 |
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
});
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
_stage.onFrame(() => { if (_obj) { _obj.rotation.y += 0.004; _obj.rotation.x += 0.0015; } });
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
|
|
|
|
|
|
| 49 |
_overlay = document.createElement("div");
|
| 50 |
_overlay.className = "szl3d-surface-overlay";
|
| 51 |
-
Object.assign(_overlay.style, { position: "absolute", left: "14px", top: "14px",
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
const
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
return { id: ID, started: true };
|
| 68 |
}
|
| 69 |
|
| 70 |
function unmount() {
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
try { if (_overlay && _overlay.parentNode) _overlay.parentNode.removeChild(_overlay); } catch (_) {}
|
| 73 |
-
try { if (
|
| 74 |
-
|
|
|
|
|
|
|
| 75 |
}
|
| 76 |
|
| 77 |
-
export default { id: ID, title: TITLE, endpoints: [
|
|
|
|
| 1 |
// SPDX-License-Identifier: Apache-2.0
|
| 2 |
// © 2026 Lutar, Stephen P. Jr. — SZL Holdings · Doctrine v11
|
| 3 |
//
|
| 4 |
+
// surfaces/counter-uas.js — COUNTER-UAS / killinchu holographic surface (Dev4).
|
| 5 |
//
|
| 6 |
+
// Leader/technique modeled on: Anduril Lattice + CesiumJS + Dedrone.
|
| 7 |
+
// 3D globe + track entities + restricted-airspace SDF volumes + radar sweep cone
|
| 8 |
+
// + signed-verdict beam. (We are *modeled on* the leader; we never claim to BE it.)
|
| 9 |
//
|
| 10 |
+
// VENDORING NOTE (Dev0 contract §6): the contract offered an escape hatch — full
|
| 11 |
+
// CesiumJS@1.123 OR a three.js globe (textured sphere + lat/long plotting). We took
|
| 12 |
+
// the three.js globe: it reuses the already-vendored three.js r170 (0 new MB, 0 CDN),
|
| 13 |
+
// keeps this PR lean, and is sufficient for track plotting + SDF volumes + sweep cone.
|
| 14 |
+
// See VENDOR_MANIFEST.md §Dev4 for the documented decision.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
//
|
| 16 |
+
// HONESTY (killinchu charter / JIATF-401 C4 crosswalk): killinchu SENSES & EVIDENCES —
|
| 17 |
+
// it does NOT defeat (no jamming, no GNSS spoofing, no takeover, no kinetic). Every
|
| 18 |
+
// visual here is detect / track / classify / evidence + the signed verdict ONLY. There
|
| 19 |
+
// is NO kinetic effect, NO jam beam, NO interceptor-to-target engagement geometry. The
|
| 20 |
+
// "verdict beam" is the SIGNED DECISION locking onto a track — an evidence act, not a
|
| 21 |
+
// weapon. Λ = Conjecture 1 (advisory, NOT proven trust). All labels are read from the
|
| 22 |
+
// live JSON; values trace to real killinchu endpoints via ctx.live.poll, never hardcoded.
|
| 23 |
+
//
|
| 24 |
+
// Live data (same-origin bridge -> killinchu Space, see szl_counter_uas_proxy.py):
|
| 25 |
+
// /api/a11oy/v1/counter-uas/evaluate live Λ decision + REAL ECDSA-P256 DSSE sig
|
| 26 |
+
// /api/a11oy/v1/counter-uas/telemetry friendly fleet + threat tracks (honest data_kind)
|
| 27 |
+
// /api/a11oy/v1/counter-uas/cued-tracks externally-cued threat tracks (cuing_sensor)
|
| 28 |
+
// /api/a11oy/v1/counter-uas/air-picture real cooperative ADS-B (airplanes.live)
|
| 29 |
+
// /api/a11oy/v1/counter-uas/gates 13-axis Λ-gate spec
|
| 30 |
+
// /static/3d/surfaces/data/drones_db.json 53 verified drone fingerprints (vendored)
|
| 31 |
|
| 32 |
const ID = "counter-uas";
|
| 33 |
+
const TITLE = "Counter-UAS · killinchu";
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
const EP = {
|
| 36 |
+
evaluate: "/api/a11oy/v1/counter-uas/evaluate",
|
| 37 |
+
telemetry: "/api/a11oy/v1/counter-uas/telemetry",
|
| 38 |
+
cued: "/api/a11oy/v1/counter-uas/cued-tracks",
|
| 39 |
+
airpicture: "/api/a11oy/v1/counter-uas/air-picture",
|
| 40 |
+
gates: "/api/a11oy/v1/counter-uas/gates",
|
| 41 |
+
dronesDb: "/static/3d/surfaces/data/drones_db.json",
|
| 42 |
+
};
|
| 43 |
|
| 44 |
+
// Globe radius (world units) + altitude scale (alt_m -> world units above surface).
|
| 45 |
+
const R = 3.0;
|
| 46 |
+
const ALT_SCALE = 1.0 / 18000; // ~18 km ceiling maps to ~+1 globe radius of lift
|
| 47 |
+
const ALT_MAX_LIFT = 0.9;
|
| 48 |
+
|
| 49 |
+
// Classification → color (benign / unknown / hostile / friendly). NEVER a "defeat" color.
|
| 50 |
+
const CLS = {
|
| 51 |
+
friendly: 0x39d3c4, // teal — own fleet
|
| 52 |
+
benign: 0x2fd07a, // green — cooperative / allied
|
| 53 |
+
unknown: 0xe8c074, // amber — uncharacterized
|
| 54 |
+
suspect: 0xffa54a, // orange — Λ below threshold
|
| 55 |
+
hostile: 0xff6b6b, // red — Λ below floor (still only SENSED, never engaged)
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
let _ctx = null, _stage = null, THREE = null;
|
| 59 |
+
let _group = null, _globe = null, _handles = [], _frameOff = null, _overlay = null;
|
| 60 |
+
let _disposables = [];
|
| 61 |
+
|
| 62 |
+
// Live caches (honest: empty until a real fetch lands).
|
| 63 |
+
let _state = {
|
| 64 |
+
evaluate: null, telemetry: null, cued: null, airpicture: null, gates: null, db: null,
|
| 65 |
+
meta: {}, dbCount: 0,
|
| 66 |
+
};
|
| 67 |
+
|
| 68 |
+
// ── geo helpers ────────────────────────────────────────────────────────────
|
| 69 |
+
function llToVec(lat, lon, lift) {
|
| 70 |
+
const phi = (90 - lat) * Math.PI / 180;
|
| 71 |
+
const theta = (lon + 180) * Math.PI / 180;
|
| 72 |
+
const r = R + (lift || 0);
|
| 73 |
+
return new THREE.Vector3(
|
| 74 |
+
-r * Math.sin(phi) * Math.cos(theta),
|
| 75 |
+
r * Math.cos(phi),
|
| 76 |
+
r * Math.sin(phi) * Math.sin(theta)
|
| 77 |
+
);
|
| 78 |
+
}
|
| 79 |
+
function altLift(alt_m) {
|
| 80 |
+
if (alt_m == null) return 0.02;
|
| 81 |
+
return Math.min(ALT_MAX_LIFT, Math.max(0.0, alt_m * ALT_SCALE));
|
| 82 |
+
}
|
| 83 |
+
function classify(track) {
|
| 84 |
+
// Honest mapping straight off the live fields — no invented severity.
|
| 85 |
+
if (track.threat_category == null && track.role) return "friendly";
|
| 86 |
+
const v = (track.lambda_verdict || "").toUpperCase();
|
| 87 |
+
if (v.indexOf("THREAT") >= 0) return "hostile";
|
| 88 |
+
if (v.indexOf("SUSPECT") >= 0) return "suspect";
|
| 89 |
+
const s = (track.side || "").toLowerCase();
|
| 90 |
+
if (s === "allied") return "benign";
|
| 91 |
+
if (track.threat_category) return "unknown";
|
| 92 |
+
return "unknown";
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
function _track(fn) { _disposables.push(fn); }
|
| 96 |
+
|
| 97 |
+
// ── demo builders (each returns {group, update?} and is added to _group) ─────
|
| 98 |
+
// D1: textured-feel globe (procedural lat/long graticule sphere; no external image,
|
| 99 |
+
// 0 CDN). Sense-only base for the air picture.
|
| 100 |
+
function buildGlobe() {
|
| 101 |
+
const g = new THREE.Group();
|
| 102 |
+
const sphere = new THREE.Mesh(
|
| 103 |
+
new THREE.SphereGeometry(R, 64, 48),
|
| 104 |
+
new THREE.MeshStandardMaterial({ color: 0x0a1626, emissive: 0x06101c,
|
| 105 |
+
emissiveIntensity: 0.5, metalness: 0.1, roughness: 0.9, transparent: true, opacity: 0.96 })
|
| 106 |
+
);
|
| 107 |
+
g.add(sphere);
|
| 108 |
+
_globe = sphere;
|
| 109 |
+
// graticule (lat/long lines) — gives the globe orientation without a texture/CDN
|
| 110 |
+
const grat = new THREE.Group();
|
| 111 |
+
const lineMat = new THREE.LineBasicMaterial({ color: 0x1d3a52, transparent: true, opacity: 0.5 });
|
| 112 |
+
for (let lat = -60; lat <= 60; lat += 30) {
|
| 113 |
+
const pts = [];
|
| 114 |
+
for (let lon = -180; lon <= 180; lon += 6) pts.push(llToVec(lat, lon, 0.005));
|
| 115 |
+
grat.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), lineMat));
|
| 116 |
+
}
|
| 117 |
+
for (let lon = -180; lon < 180; lon += 30) {
|
| 118 |
+
const pts = [];
|
| 119 |
+
for (let lat = -90; lat <= 90; lat += 6) pts.push(llToVec(lat, lon, 0.005));
|
| 120 |
+
grat.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), lineMat));
|
| 121 |
+
}
|
| 122 |
+
g.add(grat);
|
| 123 |
+
// D2: atmospheric SDF-style glow halo (additive shell)
|
| 124 |
+
const halo = new THREE.Mesh(
|
| 125 |
+
new THREE.SphereGeometry(R * 1.06, 48, 32),
|
| 126 |
+
new THREE.MeshBasicMaterial({ color: 0x2a6f9e, transparent: true, opacity: 0.08,
|
| 127 |
+
side: THREE.BackSide, blending: THREE.AdditiveBlending, depthWrite: false })
|
| 128 |
+
);
|
| 129 |
+
g.add(halo);
|
| 130 |
+
_track(() => { sphere.geometry.dispose(); sphere.material.dispose(); halo.geometry.dispose(); halo.material.dispose(); });
|
| 131 |
+
return { group: g };
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// D3: radar sweep cone — animated sector that rotates around the site (Dedrone/Anduril
|
| 135 |
+
// coverage sweep). This is a SENSOR coverage sweep, not an effector.
|
| 136 |
+
function buildRadarSweep(siteLat, siteLon) {
|
| 137 |
+
const g = new THREE.Group();
|
| 138 |
+
const center = llToVec(siteLat, siteLon, 0.0);
|
| 139 |
+
// D4: sensor coverage cone (dome of regard)
|
| 140 |
+
const coneGeo = new THREE.ConeGeometry(0.9, 1.2, 32, 1, true);
|
| 141 |
+
const coneMat = new THREE.MeshBasicMaterial({ color: 0x39d3c4, transparent: true,
|
| 142 |
+
opacity: 0.07, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending });
|
| 143 |
+
const cone = new THREE.Mesh(coneGeo, coneMat);
|
| 144 |
+
cone.position.copy(center.clone().multiplyScalar(1.18));
|
| 145 |
+
cone.lookAt(0, 0, 0);
|
| 146 |
+
cone.rotateX(Math.PI);
|
| 147 |
+
g.add(cone);
|
| 148 |
+
// sweep wedge
|
| 149 |
+
const sweepGeo = new THREE.CircleGeometry(0.85, 24, 0, Math.PI / 5);
|
| 150 |
+
const sweepMat = new THREE.MeshBasicMaterial({ color: 0x6fb1ff, transparent: true,
|
| 151 |
+
opacity: 0.22, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending });
|
| 152 |
+
const sweep = new THREE.Mesh(sweepGeo, sweepMat);
|
| 153 |
+
sweep.position.copy(center.clone().multiplyScalar(1.02));
|
| 154 |
+
sweep.lookAt(center.clone().multiplyScalar(2));
|
| 155 |
+
g.add(sweep);
|
| 156 |
+
_track(() => { coneGeo.dispose(); coneMat.dispose(); sweepGeo.dispose(); sweepMat.dispose(); });
|
| 157 |
+
return { group: g, update: (t) => { sweep.rotation.z = t * 1.4; } };
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
// Build a single track entity: marker + altitude stalk + history trail + label.
|
| 161 |
+
function buildTrackEntity(track, isFriendly) {
|
| 162 |
+
const g = new THREE.Group();
|
| 163 |
+
const cls = isFriendly ? "friendly" : classify(track);
|
| 164 |
+
const color = CLS[cls] || CLS.unknown;
|
| 165 |
+
const lift = altLift(track.alt_m);
|
| 166 |
+
const pos = llToVec(track.lat, track.lon, lift);
|
| 167 |
+
|
| 168 |
+
// marker (octahedron = aircraft track glyph)
|
| 169 |
+
const mGeo = new THREE.OctahedronGeometry(0.055, 0);
|
| 170 |
+
const mMat = new THREE.MeshStandardMaterial({ color, emissive: color, emissiveIntensity: 0.85, metalness: 0.3, roughness: 0.4 });
|
| 171 |
+
const marker = new THREE.Mesh(mGeo, mMat);
|
| 172 |
+
marker.position.copy(pos);
|
| 173 |
+
g.add(marker);
|
| 174 |
+
|
| 175 |
+
// D5: altitude stalk to surface (3D altitude encoding)
|
| 176 |
+
const surf = llToVec(track.lat, track.lon, 0.0);
|
| 177 |
+
const stalk = new THREE.Line(
|
| 178 |
+
new THREE.BufferGeometry().setFromPoints([surf, pos]),
|
| 179 |
+
new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.4 })
|
| 180 |
+
);
|
| 181 |
+
g.add(stalk);
|
| 182 |
|
| 183 |
+
// D6: classification ring around hostile/suspect (SENSED status, never an engagement)
|
| 184 |
+
let ring = null;
|
| 185 |
+
if (cls === "hostile" || cls === "suspect") {
|
| 186 |
+
const rGeo = new THREE.RingGeometry(0.09, 0.11, 24);
|
| 187 |
+
const rMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.5, side: THREE.DoubleSide, depthWrite: false });
|
| 188 |
+
ring = new THREE.Mesh(rGeo, rMat);
|
| 189 |
+
ring.position.copy(pos);
|
| 190 |
+
ring.lookAt(0, 0, 0);
|
| 191 |
+
g.add(ring);
|
| 192 |
+
_track(() => { rGeo.dispose(); rMat.dispose(); });
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
_track(() => { mGeo.dispose(); mMat.dispose(); stalk.geometry.dispose(); stalk.material.dispose(); });
|
| 196 |
+
return { group: g, marker, ring, cls, track };
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// ── HUD / overlay ────────────────────────────────────────────────────────────
|
| 200 |
+
function hud(html) {
|
| 201 |
+
const d = document.createElement("div");
|
| 202 |
+
d.innerHTML = html;
|
| 203 |
+
return d;
|
| 204 |
+
}
|
| 205 |
+
function row(label, value, labelToken) {
|
| 206 |
+
const r = document.createElement("div");
|
| 207 |
+
r.style.cssText = "display:flex;align-items:center;gap:8px;justify-content:space-between;font:11px ui-monospace,Menlo,monospace;color:#9fb1bf";
|
| 208 |
+
const l = document.createElement("span"); l.textContent = label; l.style.color = "#cfe0ec";
|
| 209 |
+
const v = document.createElement("span"); v.textContent = value; v.style.color = "#eef3f6"; v.style.fontWeight = "600";
|
| 210 |
+
r.appendChild(l); r.appendChild(v);
|
| 211 |
+
if (labelToken && _ctx) { const c = _ctx.label.chip(labelToken); c.style.marginLeft = "6px"; r.appendChild(c); }
|
| 212 |
+
return r;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
// ── live scene state ──────────────────────────────────────────────────────────
|
| 216 |
+
let _trackGroup = null; // holds all track entities, rebuilt on telemetry/cued change
|
| 217 |
+
let _verdictBeam = null; // signed-verdict beam (decision lock), not a weapon
|
| 218 |
+
let _dsseLock = null; // DSSE signature lock animation node
|
| 219 |
+
let _fingerprintCallout = null;
|
| 220 |
+
let _restrictedZones = null; // restricted-airspace SDF volumes
|
| 221 |
+
let _ridPanel = null, _gatesPanel = null, _verdictPanel = null, _posturePanel = null, _fpPanel = null;
|
| 222 |
+
let _beamPhase = 0, _lockPhase = 0;
|
| 223 |
+
|
| 224 |
+
function rebuildTracks() {
|
| 225 |
+
if (!_trackGroup) { _trackGroup = new THREE.Group(); _group.add(_trackGroup); }
|
| 226 |
+
// clear old
|
| 227 |
+
while (_trackGroup.children.length) {
|
| 228 |
+
const c = _trackGroup.children.pop();
|
| 229 |
+
_trackGroup.remove(c);
|
| 230 |
+
}
|
| 231 |
+
const entities = [];
|
| 232 |
+
const tele = _state.telemetry;
|
| 233 |
+
const cued = _state.cued;
|
| 234 |
+
|
| 235 |
+
// D7: friendly fleet tracks (own fleet, teal) — telemetry.friendly_drones
|
| 236 |
+
if (tele && Array.isArray(tele.friendly_drones)) {
|
| 237 |
+
tele.friendly_drones.forEach((d) => {
|
| 238 |
+
const e = buildTrackEntity(d, true); _trackGroup.add(e.group); entities.push(e);
|
| 239 |
+
});
|
| 240 |
+
}
|
| 241 |
+
// D8: SENSED threat tracks (telemetry.threat_tracks) — classification color
|
| 242 |
+
if (tele && Array.isArray(tele.threat_tracks)) {
|
| 243 |
+
tele.threat_tracks.forEach((d) => {
|
| 244 |
+
const e = buildTrackEntity(d, false); _trackGroup.add(e.group); entities.push(e);
|
| 245 |
+
});
|
| 246 |
+
}
|
| 247 |
+
// D9: externally-cued tracks (cued-tracks) — carry cuing_sensor (we own no sensor)
|
| 248 |
+
if (cued && Array.isArray(cued.tracks)) {
|
| 249 |
+
cued.tracks.forEach((d) => {
|
| 250 |
+
const e = buildTrackEntity(d, false); _trackGroup.add(e.group); entities.push(e);
|
| 251 |
+
});
|
| 252 |
+
}
|
| 253 |
+
// D10: real cooperative ADS-B air picture (airplanes.live) — small benign dots,
|
| 254 |
+
// capped for perf. Cooperative manned aircraft = green (benign), per AirSight pattern.
|
| 255 |
+
if (_state.airpicture && Array.isArray(_state.airpicture.aircraft)) {
|
| 256 |
+
const ac = _state.airpicture.aircraft.slice(0, 120);
|
| 257 |
+
const geo = new THREE.BufferGeometry();
|
| 258 |
+
const verts = [];
|
| 259 |
+
ac.forEach((a) => {
|
| 260 |
+
if (a.lat == null || a.lon == null) return;
|
| 261 |
+
const p = llToVec(a.lat, a.lon, altLift((a.alt_baro || 0) * 0.3048));
|
| 262 |
+
verts.push(p.x, p.y, p.z);
|
| 263 |
+
});
|
| 264 |
+
geo.setAttribute("position", new THREE.Float32BufferAttribute(verts, 3));
|
| 265 |
+
const pts = new THREE.Points(geo, new THREE.PointsMaterial({ color: CLS.benign, size: 0.04, transparent: true, opacity: 0.7 }));
|
| 266 |
+
_trackGroup.add(pts);
|
| 267 |
+
_track(() => { geo.dispose(); pts.material.dispose(); });
|
| 268 |
+
}
|
| 269 |
+
_trackGroup.userData.entities = entities;
|
| 270 |
+
return entities;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
// D11: restricted-airspace SDF volumes — translucent glowing boxes/spheres around
|
| 274 |
+
// protected sites. Breach = pulse (SENSED breach, not an engagement).
|
| 275 |
+
function buildRestrictedZones(centerLat, centerLon) {
|
| 276 |
+
const g = new THREE.Group();
|
| 277 |
+
const sites = [
|
| 278 |
+
{ lat: centerLat, lon: centerLon, r: 0.42, name: "PROTECTED SITE" },
|
| 279 |
+
{ lat: centerLat + 0.02, lon: centerLon + 0.03, r: 0.26, name: "INNER KEEP-OUT" },
|
| 280 |
+
];
|
| 281 |
+
const meshes = [];
|
| 282 |
+
sites.forEach((s) => {
|
| 283 |
+
const geo = new THREE.SphereGeometry(s.r, 24, 18);
|
| 284 |
+
const mat = new THREE.MeshBasicMaterial({ color: 0xff6b6b, transparent: true, opacity: 0.08,
|
| 285 |
+
side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending });
|
| 286 |
+
const m = new THREE.Mesh(geo, mat);
|
| 287 |
+
m.position.copy(llToVec(s.lat, s.lon, s.r * 0.4));
|
| 288 |
+
g.add(m);
|
| 289 |
+
// wireframe outline (SDF boundary)
|
| 290 |
+
const wf = new THREE.LineSegments(new THREE.WireframeGeometry(geo),
|
| 291 |
+
new THREE.LineBasicMaterial({ color: 0xff8a8a, transparent: true, opacity: 0.35 }));
|
| 292 |
+
wf.position.copy(m.position);
|
| 293 |
+
g.add(wf);
|
| 294 |
+
meshes.push(m);
|
| 295 |
+
_track(() => { geo.dispose(); mat.dispose(); wf.geometry.dispose(); wf.material.dispose(); });
|
| 296 |
});
|
| 297 |
+
return { group: g, update: (t) => { meshes.forEach((m, i) => { m.material.opacity = 0.06 + 0.05 * (0.5 + 0.5 * Math.sin(t * 1.5 + i)); }); } };
|
| 298 |
+
}
|
|
|
|
| 299 |
|
| 300 |
+
// D12: Λ-gate signed-verdict beam — when /evaluate returns, a beam from the globe
|
| 301 |
+
// core to the decision plane "locks" the verdict. This is the SIGNED DECISION
|
| 302 |
+
// (an evidence act). Color = decision (ALLOW=green / CLASSIFY=amber / HALT=red).
|
| 303 |
+
// It is NOT a weapon: nothing is emitted at a track, nothing is defeated.
|
| 304 |
+
function buildVerdictBeam() {
|
| 305 |
+
const g = new THREE.Group();
|
| 306 |
+
const geo = new THREE.CylinderGeometry(0.02, 0.08, 2.6, 16, 1, true);
|
| 307 |
+
const mat = new THREE.MeshBasicMaterial({ color: CLS.benign, transparent: true, opacity: 0.0,
|
| 308 |
+
side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending });
|
| 309 |
+
const beam = new THREE.Mesh(geo, mat);
|
| 310 |
+
beam.position.set(0, R + 1.3, 0);
|
| 311 |
+
g.add(beam);
|
| 312 |
+
_verdictBeam = beam;
|
| 313 |
+
_track(() => { geo.dispose(); mat.dispose(); });
|
| 314 |
+
return { group: g, update: (t) => {
|
| 315 |
+
if (!_state.evaluate) return;
|
| 316 |
+
const dec = (_state.evaluate.decision || "").toUpperCase();
|
| 317 |
+
const col = dec === "ALLOW" ? CLS.benign : dec === "HALT" ? CLS.hostile : CLS.unknown;
|
| 318 |
+
beam.material.color.setHex(col);
|
| 319 |
+
beam.material.opacity = 0.18 + 0.12 * (0.5 + 0.5 * Math.sin(t * 2.2));
|
| 320 |
+
} };
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
// D13: DSSE-signature lock animation — when the real ECDSA-P256 DSSE signature is
|
| 324 |
+
// present (lambda_receipt.dsse.signed === true), an orbiting ring "locks" closed
|
| 325 |
+
// around the verdict, with the signature truncation shown in the HUD. The lock is
|
| 326 |
+
// the cryptographic seal of the SIGNED VERDICT — the evidence, not an effect.
|
| 327 |
+
function buildDsseLock() {
|
| 328 |
+
const g = new THREE.Group();
|
| 329 |
+
const geo = new THREE.TorusGeometry(0.5, 0.018, 12, 48);
|
| 330 |
+
const mat = new THREE.MeshStandardMaterial({ color: 0x6fb1ff, emissive: 0x6fb1ff, emissiveIntensity: 0.6, metalness: 0.6, roughness: 0.3 });
|
| 331 |
+
const ring = new THREE.Mesh(geo, mat);
|
| 332 |
+
ring.position.set(0, R + 1.3, 0);
|
| 333 |
+
g.add(ring);
|
| 334 |
+
_dsseLock = ring;
|
| 335 |
+
_track(() => { geo.dispose(); mat.dispose(); });
|
| 336 |
+
return { group: g, update: (t) => {
|
| 337 |
+
const signed = !!(_state.evaluate && _state.evaluate.lambda_receipt &&
|
| 338 |
+
_state.evaluate.lambda_receipt.dsse && _state.evaluate.lambda_receipt.dsse.signed);
|
| 339 |
+
ring.rotation.x = Math.PI / 2;
|
| 340 |
+
ring.rotation.z = t * (signed ? 0.9 : 0.15);
|
| 341 |
+
ring.material.color.setHex(signed ? 0x2fd07a : 0x8a97a3); // green when signed, gray when not
|
| 342 |
+
ring.material.emissive.setHex(signed ? 0x2fd07a : 0x8a97a3);
|
| 343 |
+
ring.scale.setScalar(signed ? 1.0 : 0.92 + 0.06 * Math.sin(t * 3));
|
| 344 |
+
} };
|
| 345 |
+
}
|
| 346 |
|
| 347 |
+
// ── overlay panels (the HUD reads live values + honest labels) ────────────────
|
| 348 |
+
function buildOverlay() {
|
| 349 |
_overlay = document.createElement("div");
|
| 350 |
_overlay.className = "szl3d-surface-overlay";
|
| 351 |
+
Object.assign(_overlay.style, { position: "absolute", left: "14px", top: "14px", right: "14px",
|
| 352 |
+
zIndex: "5", display: "flex", justifyContent: "space-between", gap: "12px",
|
| 353 |
+
pointerEvents: "none", fontFamily: "ui-monospace,Menlo,monospace" });
|
| 354 |
+
|
| 355 |
+
const left = document.createElement("div");
|
| 356 |
+
Object.assign(left.style, { display: "flex", flexDirection: "column", gap: "8px", maxWidth: "min(46%,360px)", pointerEvents: "auto" });
|
| 357 |
+
const right = document.createElement("div");
|
| 358 |
+
Object.assign(right.style, { display: "flex", flexDirection: "column", gap: "8px", maxWidth: "min(46%,360px)", alignItems: "flex-end", pointerEvents: "auto" });
|
| 359 |
+
|
| 360 |
+
const card = (title) => {
|
| 361 |
+
const c = document.createElement("div");
|
| 362 |
+
c.style.cssText = "background:rgba(8,14,20,.82);border:1px solid #1d2a36;border-radius:9px;padding:9px 11px;display:flex;flex-direction:column;gap:5px;backdrop-filter:blur(3px)";
|
| 363 |
+
const h = document.createElement("div");
|
| 364 |
+
h.style.cssText = "font:600 11px ui-sans-serif,system-ui;color:#eef3f6;letter-spacing:.5px;text-transform:uppercase";
|
| 365 |
+
h.textContent = title; c.appendChild(h);
|
| 366 |
+
return c;
|
| 367 |
+
};
|
| 368 |
+
|
| 369 |
+
const head = document.createElement("div");
|
| 370 |
+
head.style.cssText = "font:600 13px ui-sans-serif,system-ui;color:#eef3f6;letter-spacing:.4px";
|
| 371 |
+
head.textContent = TITLE;
|
| 372 |
+
left.appendChild(head);
|
| 373 |
+
|
| 374 |
+
const badge = _ctx.live.createBadge();
|
| 375 |
+
left.appendChild(badge.el);
|
| 376 |
+
|
| 377 |
+
// D14: 13-axis Λ-gate verdict panel
|
| 378 |
+
_verdictPanel = card("Λ-Gate Verdict · senses-and-evidences");
|
| 379 |
+
left.appendChild(_verdictPanel);
|
| 380 |
+
|
| 381 |
+
// D15: DSSE signature panel (real ECDSA-P256)
|
| 382 |
+
_fpPanel = null;
|
| 383 |
+
_ridPanel = card("Remote-ID / RID Validator");
|
| 384 |
+
left.appendChild(_ridPanel);
|
| 385 |
+
|
| 386 |
+
// right column
|
| 387 |
+
_posturePanel = card("JIATF-401 posture");
|
| 388 |
+
right.appendChild(_posturePanel);
|
| 389 |
+
_gatesPanel = card("13-Axis Λ-Gates");
|
| 390 |
+
right.appendChild(_gatesPanel);
|
| 391 |
+
_fingerprintCallout = card("Fingerprint match · 53-DB");
|
| 392 |
+
right.appendChild(_fingerprintCallout);
|
| 393 |
+
|
| 394 |
+
const legend = _ctx.label.legend();
|
| 395 |
+
legend.style.opacity = "0.85"; legend.style.pointerEvents = "none";
|
| 396 |
+
left.appendChild(legend);
|
| 397 |
+
|
| 398 |
+
_overlay.appendChild(left); _overlay.appendChild(right);
|
| 399 |
+
(_ctx.container || document.body).appendChild(_overlay);
|
| 400 |
+
return badge;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
function clearCard(card) { while (card.children.length > 1) card.removeChild(card.lastChild); }
|
| 404 |
+
|
| 405 |
+
function renderVerdict() {
|
| 406 |
+
const ev = _state.evaluate;
|
| 407 |
+
clearCard(_verdictPanel);
|
| 408 |
+
if (!ev || ev.degraded) {
|
| 409 |
+
_verdictPanel.appendChild(row("decision", ev && ev.degraded ? "DEGRADED" : "—", "STRUCTURAL-ONLY"));
|
| 410 |
+
return;
|
| 411 |
+
}
|
| 412 |
+
const dec = ev.decision || "—";
|
| 413 |
+
const lam = ev.lambda != null ? ev.lambda.toFixed(6) : "—";
|
| 414 |
+
const floor = ev.lambda_floor != null ? ev.lambda_floor : "—";
|
| 415 |
+
_verdictPanel.appendChild(row("decision", dec));
|
| 416 |
+
_verdictPanel.appendChild(row("Λ score", lam + " (floor " + floor + ")"));
|
| 417 |
+
_verdictPanel.appendChild(row("Λ status", "Conjecture 1 · advisory"));
|
| 418 |
+
// D16: real DSSE signature lock-on (truncated sig + keyid)
|
| 419 |
+
const dsse = ev.lambda_receipt && ev.lambda_receipt.dsse;
|
| 420 |
+
if (dsse) {
|
| 421 |
+
const sig = (dsse.signatures && dsse.signatures[0] && dsse.signatures[0].sig) || "";
|
| 422 |
+
const sigShort = sig ? (sig.slice(0, 18) + "…" + sig.slice(-8)) : "—";
|
| 423 |
+
_verdictPanel.appendChild(row("DSSE", dsse.signed ? "SIGNED ✓" : "UNSIGNED", dsse.signed ? "MEASURED" : "STRUCTURAL-ONLY"));
|
| 424 |
+
_verdictPanel.appendChild(row("keyid", dsse.keyid || "—"));
|
| 425 |
+
_verdictPanel.appendChild(row("ECDSA-P256 sig", sigShort));
|
| 426 |
+
}
|
| 427 |
+
// D17: CI signing honesty disclosure (PLACEHOLDER per killinchu honest posture)
|
| 428 |
+
if (ev.signature) {
|
| 429 |
+
const ci = row("CI sign", ev.signature.indexOf("PLACEHOLDER") >= 0 ? "PLACEHOLDER" : "wired", "STRUCTURAL-ONLY");
|
| 430 |
+
_verdictPanel.appendChild(ci);
|
| 431 |
+
}
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
function renderRid() {
|
| 435 |
+
clearCard(_ridPanel);
|
| 436 |
+
const tele = _state.telemetry, cued = _state.cued;
|
| 437 |
+
// RID validator: count tracks broadcasting a Remote-ID vs those without (RID-OFF =
|
| 438 |
+
// spoofable/uncooperative). Honest: a broadcast ID is a CLAIM, not ground truth.
|
| 439 |
+
let withRid = 0, withoutRid = 0;
|
| 440 |
+
const scan = (arr, key) => (arr || []).forEach((t) => { (t[key]) ? withRid++ : withoutRid++; });
|
| 441 |
+
if (tele) { scan(tele.friendly_drones, "remote_id"); scan(tele.threat_tracks, "remote_id"); }
|
| 442 |
+
if (cued) scan(cued.tracks, "remote_id");
|
| 443 |
+
_ridPanel.appendChild(row("RID present", String(withRid)));
|
| 444 |
+
_ridPanel.appendChild(row("RID OFF (uncoop)", String(withoutRid)));
|
| 445 |
+
_ridPanel.appendChild(row("note", "broadcast ID = claim"));
|
| 446 |
+
if (tele && tele.data_kind) _ridPanel.appendChild(row("data_kind", String(tele.data_kind), tele.data_kind === "demo_mock" ? "SAMPLE" : "MEASURED"));
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
function renderPosture() {
|
| 450 |
+
clearCard(_posturePanel);
|
| 451 |
+
_posturePanel.appendChild(row("charter", "SENSE + EVIDENCE"));
|
| 452 |
+
_posturePanel.appendChild(row("defeat", "OUT-OF-SCOPE"));
|
| 453 |
+
_posturePanel.appendChild(row("no jam/spoof", "✓"));
|
| 454 |
+
_posturePanel.appendChild(row("no takeover", "✓"));
|
| 455 |
+
_posturePanel.appendChild(row("no kinetic", "✓"));
|
| 456 |
+
_posturePanel.appendChild(row("human-on-loop", "ROE gate"));
|
| 457 |
+
_posturePanel.appendChild(row("C4 strongest fit", "§6–§7 receipts"));
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
function renderGates() {
|
| 461 |
+
clearCard(_gatesPanel);
|
| 462 |
+
const g = _state.gates;
|
| 463 |
+
if (!g || g.degraded || !Array.isArray(g.gates)) {
|
| 464 |
+
_gatesPanel.appendChild(row("gates", g && g.degraded ? "DEGRADED" : "—", "STRUCTURAL-ONLY"));
|
| 465 |
+
return;
|
| 466 |
+
}
|
| 467 |
+
_gatesPanel.appendChild(row("axes", String(g.count != null ? g.count : g.gates.length)));
|
| 468 |
+
// show first few axis names live
|
| 469 |
+
g.gates.slice(0, 5).forEach((ax) => _gatesPanel.appendChild(row("Λ" + ax.axis, ax.name || "—")));
|
| 470 |
+
_gatesPanel.appendChild(row("status", "Conjecture 1 — OPEN"));
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
function renderFingerprint() {
|
| 474 |
+
clearCard(_fingerprintCallout);
|
| 475 |
+
const db = _state.db;
|
| 476 |
+
if (!db || !Array.isArray(db)) {
|
| 477 |
+
_fingerprintCallout.appendChild(row("DB", "loading…", "STRUCTURAL-ONLY"));
|
| 478 |
+
return;
|
| 479 |
+
}
|
| 480 |
+
_fingerprintCallout.appendChild(row("fingerprints", String(db.length)));
|
| 481 |
+
// D: attempt to match a SENSED threat track type to the DB (classification callout)
|
| 482 |
+
const tele = _state.telemetry;
|
| 483 |
+
let matched = null;
|
| 484 |
+
if (tele && Array.isArray(tele.threat_tracks)) {
|
| 485 |
+
for (const t of tele.threat_tracks) {
|
| 486 |
+
const ty = (t.type || "").toUpperCase();
|
| 487 |
+
matched = db.find((d) => ty && (String(d.model).toUpperCase().indexOf(ty.split("-")[0]) >= 0));
|
| 488 |
+
if (matched) break;
|
| 489 |
+
}
|
| 490 |
+
}
|
| 491 |
+
// fall back to a representative adversary fingerprint to show the callout shape
|
| 492 |
+
const ex = matched || db.find((d) => d.side === "adversary") || db[0];
|
| 493 |
+
if (ex) {
|
| 494 |
+
_fingerprintCallout.appendChild(row("model", String(ex.model)));
|
| 495 |
+
_fingerprintCallout.appendChild(row("mfr", String(ex.manufacturer)));
|
| 496 |
+
_fingerprintCallout.appendChild(row("group", String(ex.group || "—")));
|
| 497 |
+
_fingerprintCallout.appendChild(row("side", String(ex.side || "—")));
|
| 498 |
+
}
|
| 499 |
+
_fingerprintCallout.appendChild(row("source", "drones_db.json"));
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
// ── mount / unmount ───────────────────────────────────────────────────────────
|
| 503 |
+
function mount(ctx) {
|
| 504 |
+
_ctx = ctx; _stage = ctx.stage; THREE = ctx.THREE;
|
| 505 |
+
_group = new THREE.Group();
|
| 506 |
+
_stage.scene.add(_group);
|
| 507 |
+
|
| 508 |
+
// lighting so the globe reads in 3D
|
| 509 |
+
const amb = new THREE.AmbientLight(0x223344, 1.1);
|
| 510 |
+
const key = new THREE.DirectionalLight(0xbfe0ff, 1.3); key.position.set(5, 6, 7);
|
| 511 |
+
_group.add(amb); _group.add(key);
|
| 512 |
+
_track(() => { /* lights freed with group removal */ });
|
| 513 |
+
|
| 514 |
+
// build the static scene demos
|
| 515 |
+
const updaters = [];
|
| 516 |
+
const globe = buildGlobe(); _group.add(globe.group);
|
| 517 |
+
|
| 518 |
+
// protected site (matches killinchu demo fleet vicinity ~ 37.42, -122.17)
|
| 519 |
+
const siteLat = 37.4275, siteLon = -122.1697;
|
| 520 |
+
const sweep = buildRadarSweep(siteLat, siteLon); _group.add(sweep.group); updaters.push(sweep.update);
|
| 521 |
+
const zones = buildRestrictedZones(siteLat, siteLon); _group.add(zones.group); updaters.push(zones.update);
|
| 522 |
+
const beam = buildVerdictBeam(); _group.add(beam.group); updaters.push(beam.update);
|
| 523 |
+
const lock = buildDsseLock(); _group.add(lock.group); updaters.push(lock.update);
|
| 524 |
+
|
| 525 |
+
// floating honesty billboard
|
| 526 |
+
try {
|
| 527 |
+
const bb = ctx.label.billboard(THREE, "STRUCTURAL-ONLY", { text: "senses · evidences · NOT defeats", scale: 0.42, position: [0, R + 2.0, 0] });
|
| 528 |
+
_group.add(bb);
|
| 529 |
+
_track(() => { if (bb.material.map) bb.material.map.dispose(); bb.material.dispose(); });
|
| 530 |
+
} catch (_) {}
|
| 531 |
+
|
| 532 |
+
const badge = buildOverlay();
|
| 533 |
+
renderPosture(); renderVerdict(); renderRid(); renderGates(); renderFingerprint();
|
| 534 |
+
|
| 535 |
+
// per-frame animation (rotate the estate slowly + run demo updaters). The shell's
|
| 536 |
+
// Stage has no offFrame(), so we self-guard: once unmounted, _group is null and the
|
| 537 |
+
// callback no-ops (the shell disposes the renderer/loop on tab switch).
|
| 538 |
+
let _t = 0;
|
| 539 |
+
_frameOff = () => {
|
| 540 |
+
if (!_group) return;
|
| 541 |
+
_t += 0.016;
|
| 542 |
+
_group.rotation.y += 0.0009;
|
| 543 |
+
for (const u of updaters) { try { u(_t); } catch (_) {} }
|
| 544 |
+
// pulse SENSED hostile/suspect rings
|
| 545 |
+
if (_trackGroup && _trackGroup.userData.entities) {
|
| 546 |
+
for (const e of _trackGroup.userData.entities) {
|
| 547 |
+
if (e.ring) { e.ring.material.opacity = 0.35 + 0.3 * (0.5 + 0.5 * Math.sin(_t * 3)); }
|
| 548 |
+
if (e.marker) e.marker.rotation.y += 0.02;
|
| 549 |
+
}
|
| 550 |
+
}
|
| 551 |
+
};
|
| 552 |
+
_stage.onFrame(_frameOff);
|
| 553 |
+
|
| 554 |
+
// ── wire LIVE data (never fabricate; degrade gracefully) ──
|
| 555 |
+
// primary endpoint drives the badge
|
| 556 |
+
_handles.push(ctx.live.poll(EP.evaluate, 6000, (json, meta) => {
|
| 557 |
+
_state.evaluate = json; _state.meta.evaluate = meta; renderVerdict();
|
| 558 |
+
}, { badge }));
|
| 559 |
+
_handles.push(ctx.live.poll(EP.telemetry, 5000, (json, meta) => {
|
| 560 |
+
_state.telemetry = json; _state.meta.telemetry = meta; rebuildTracks(); renderRid(); renderFingerprint();
|
| 561 |
+
}));
|
| 562 |
+
_handles.push(ctx.live.poll(EP.cued, 7000, (json) => {
|
| 563 |
+
_state.cued = json; rebuildTracks(); renderRid();
|
| 564 |
+
}));
|
| 565 |
+
_handles.push(ctx.live.poll(EP.airpicture, 12000, (json) => {
|
| 566 |
+
_state.airpicture = json; rebuildTracks();
|
| 567 |
+
}));
|
| 568 |
+
_handles.push(ctx.live.poll(EP.gates, 30000, (json) => {
|
| 569 |
+
_state.gates = json; renderGates();
|
| 570 |
+
}));
|
| 571 |
+
// 53-fingerprint DB (static vendored; fetched once)
|
| 572 |
+
fetch(EP.dronesDb, { headers: { accept: "application/json" } })
|
| 573 |
+
.then((r) => r.ok ? r.json() : null)
|
| 574 |
+
.then((db) => { if (db) { _state.db = db; _state.dbCount = db.length; renderFingerprint(); } })
|
| 575 |
+
.catch(() => {});
|
| 576 |
|
| 577 |
return { id: ID, started: true };
|
| 578 |
}
|
| 579 |
|
| 580 |
function unmount() {
|
| 581 |
+
for (const h of _handles) { try { h.stop(); } catch (_) {} }
|
| 582 |
+
_handles = [];
|
| 583 |
+
_frameOff = null;
|
| 584 |
+
for (const d of _disposables) { try { d(); } catch (_) {} }
|
| 585 |
+
_disposables = [];
|
| 586 |
try { if (_overlay && _overlay.parentNode) _overlay.parentNode.removeChild(_overlay); } catch (_) {}
|
| 587 |
+
try { if (_group && _stage) _stage.scene.remove(_group); } catch (_) {}
|
| 588 |
+
_overlay = null; _group = null; _globe = null; _trackGroup = null;
|
| 589 |
+
_verdictBeam = null; _dsseLock = null; _stage = null; THREE = null; _ctx = null;
|
| 590 |
+
_state = { evaluate: null, telemetry: null, cued: null, airpicture: null, gates: null, db: null, meta: {}, dbCount: 0 };
|
| 591 |
}
|
| 592 |
|
| 593 |
+
export default { id: ID, title: TITLE, endpoints: [EP.evaluate, EP.telemetry, EP.cued, EP.airpicture, EP.gates], mount, unmount };
|
static/3d/surfaces/data/drones_db.json
ADDED
|
@@ -0,0 +1,1152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "mq9",
|
| 4 |
+
"model": "MQ-9 Reaper",
|
| 5 |
+
"manufacturer": "General Atomics",
|
| 6 |
+
"country": "USA",
|
| 7 |
+
"side": "allied",
|
| 8 |
+
"role": "strike/ISR",
|
| 9 |
+
"group": "Group 5",
|
| 10 |
+
"telemetry": [
|
| 11 |
+
"ADS-B",
|
| 12 |
+
"Link-16",
|
| 13 |
+
"SATCOM"
|
| 14 |
+
],
|
| 15 |
+
"specs": {
|
| 16 |
+
"wingspan_m": 20.1,
|
| 17 |
+
"mtow_kg": 4760,
|
| 18 |
+
"speed_kmh": 482,
|
| 19 |
+
"ceiling_m": 15240,
|
| 20 |
+
"endurance_hr": 27,
|
| 21 |
+
"range_km": 1852,
|
| 22 |
+
"unit_cost_usd": 56500000
|
| 23 |
+
},
|
| 24 |
+
"notes": "MALE hunter-killer; AGM-114 Hellfire + GBU; AFSOC inv ~50 (Jan 2025).",
|
| 25 |
+
"source": "https://www.af.mil/About-Us/Fact-Sheets/Display/Article/104470/mq-9-reaper/"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"id": "rq4",
|
| 29 |
+
"model": "RQ-4 Global Hawk",
|
| 30 |
+
"manufacturer": "Northrop Grumman",
|
| 31 |
+
"country": "USA",
|
| 32 |
+
"side": "allied",
|
| 33 |
+
"role": "ISR",
|
| 34 |
+
"group": "Group 5",
|
| 35 |
+
"telemetry": [
|
| 36 |
+
"SATCOM",
|
| 37 |
+
"SIGINT",
|
| 38 |
+
"IMINT",
|
| 39 |
+
"MTI"
|
| 40 |
+
],
|
| 41 |
+
"specs": {
|
| 42 |
+
"wingspan_m": 39.9,
|
| 43 |
+
"mtow_kg": 14628,
|
| 44 |
+
"speed_kmh": 629,
|
| 45 |
+
"ceiling_m": 18288,
|
| 46 |
+
"endurance_hr": 34,
|
| 47 |
+
"range_km": 22800
|
| 48 |
+
},
|
| 49 |
+
"notes": "HALE unarmed ISR; Blocks 20/30/40; SAR + SIGINT.",
|
| 50 |
+
"source": "https://www.af.mil/About-Us/Fact-Sheets/Display/Article/104516/rq-4-global-hawk/"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"id": "mq4c",
|
| 54 |
+
"model": "MQ-4C Triton",
|
| 55 |
+
"manufacturer": "Northrop Grumman",
|
| 56 |
+
"country": "USA",
|
| 57 |
+
"side": "allied",
|
| 58 |
+
"role": "maritime ISR",
|
| 59 |
+
"group": "Group 5",
|
| 60 |
+
"telemetry": [
|
| 61 |
+
"SATCOM",
|
| 62 |
+
"AESA radar",
|
| 63 |
+
"SIGINT"
|
| 64 |
+
],
|
| 65 |
+
"specs": {
|
| 66 |
+
"wingspan_m": 39.9,
|
| 67 |
+
"mtow_kg": 14628,
|
| 68 |
+
"speed_kmh": 610,
|
| 69 |
+
"ceiling_m": 17000,
|
| 70 |
+
"endurance_hr": 30,
|
| 71 |
+
"range_km": 15000
|
| 72 |
+
},
|
| 73 |
+
"notes": "Navy maritime variant of Global Hawk; 360\u00b0 maritime surveillance.",
|
| 74 |
+
"source": "https://en.wikipedia.org/wiki/Northrop_Grumman_MQ-4C_Triton"
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"id": "rq170",
|
| 78 |
+
"model": "RQ-170 Sentinel",
|
| 79 |
+
"manufacturer": "Lockheed Martin",
|
| 80 |
+
"country": "USA",
|
| 81 |
+
"side": "allied",
|
| 82 |
+
"role": "stealth ISR",
|
| 83 |
+
"group": "Group 5",
|
| 84 |
+
"telemetry": [
|
| 85 |
+
"LOS datalink (classified)"
|
| 86 |
+
],
|
| 87 |
+
"specs": {
|
| 88 |
+
"wingspan_m": 20.0,
|
| 89 |
+
"ceiling_m": 15000,
|
| 90 |
+
"role_note": "low-observable flying wing"
|
| 91 |
+
},
|
| 92 |
+
"notes": "'Beast of Kandahar' stealth ISR; one captured by Iran 2011.",
|
| 93 |
+
"source": "https://en.wikipedia.org/wiki/Lockheed_Martin_RQ-170_Sentinel"
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"id": "mq1c",
|
| 97 |
+
"model": "MQ-1C Gray Eagle",
|
| 98 |
+
"manufacturer": "General Atomics",
|
| 99 |
+
"country": "USA",
|
| 100 |
+
"side": "allied",
|
| 101 |
+
"role": "strike/ISR",
|
| 102 |
+
"group": "Group 4",
|
| 103 |
+
"telemetry": [
|
| 104 |
+
"ADS-B",
|
| 105 |
+
"TCDL",
|
| 106 |
+
"SATCOM"
|
| 107 |
+
],
|
| 108 |
+
"specs": {
|
| 109 |
+
"wingspan_m": 17.0,
|
| 110 |
+
"mtow_kg": 1633,
|
| 111 |
+
"speed_kmh": 309,
|
| 112 |
+
"ceiling_m": 8839,
|
| 113 |
+
"endurance_hr": 25,
|
| 114 |
+
"range_km": 370
|
| 115 |
+
},
|
| 116 |
+
"notes": "US Army ERMP; AGM-114; ~204 produced; IOC 2009.",
|
| 117 |
+
"source": "https://www.globalmilitary.net/compare/aircraft/mq-1c-gray-eagle-vs-rq-4-global-hawk/"
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"id": "mq1",
|
| 121 |
+
"model": "MQ-1 Predator",
|
| 122 |
+
"manufacturer": "General Atomics",
|
| 123 |
+
"country": "USA",
|
| 124 |
+
"side": "allied",
|
| 125 |
+
"role": "strike/ISR",
|
| 126 |
+
"group": "Group 4",
|
| 127 |
+
"telemetry": [
|
| 128 |
+
"Ku SATCOM",
|
| 129 |
+
"C-band LOS"
|
| 130 |
+
],
|
| 131 |
+
"specs": {
|
| 132 |
+
"wingspan_m": 16.8,
|
| 133 |
+
"mtow_kg": 1020,
|
| 134 |
+
"speed_kmh": 217,
|
| 135 |
+
"ceiling_m": 7620,
|
| 136 |
+
"endurance_hr": 24,
|
| 137 |
+
"range_km": 1100
|
| 138 |
+
},
|
| 139 |
+
"notes": "Retired USAF 2018; first armed UAV (Hellfire). Predecessor to Reaper.",
|
| 140 |
+
"source": "https://en.wikipedia.org/wiki/General_Atomics_MQ-1_Predator"
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"id": "mq8",
|
| 144 |
+
"model": "MQ-8 Fire Scout",
|
| 145 |
+
"manufacturer": "Northrop Grumman",
|
| 146 |
+
"country": "USA",
|
| 147 |
+
"side": "allied",
|
| 148 |
+
"role": "VTOL ISR",
|
| 149 |
+
"group": "Group 4",
|
| 150 |
+
"telemetry": [
|
| 151 |
+
"TCDL",
|
| 152 |
+
"Link-16"
|
| 153 |
+
],
|
| 154 |
+
"specs": {
|
| 155 |
+
"rotor_m": 8.4,
|
| 156 |
+
"mtow_kg": 1430,
|
| 157 |
+
"speed_kmh": 213,
|
| 158 |
+
"ceiling_m": 6100,
|
| 159 |
+
"endurance_hr": 8
|
| 160 |
+
},
|
| 161 |
+
"notes": "US Navy VTOL ISR (MQ-8B/C); shipboard.",
|
| 162 |
+
"source": "https://en.wikipedia.org/wiki/Northrop_Grumman_MQ-8_Fire_Scout"
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"id": "rq7",
|
| 166 |
+
"model": "RQ-7B Shadow",
|
| 167 |
+
"manufacturer": "AAI / Textron",
|
| 168 |
+
"country": "USA",
|
| 169 |
+
"side": "allied",
|
| 170 |
+
"role": "tactical ISR",
|
| 171 |
+
"group": "Group 3",
|
| 172 |
+
"telemetry": [
|
| 173 |
+
"C-band LOS",
|
| 174 |
+
"EO/IR"
|
| 175 |
+
],
|
| 176 |
+
"specs": {
|
| 177 |
+
"wingspan_m": 4.3,
|
| 178 |
+
"mtow_kg": 208,
|
| 179 |
+
"speed_kmh": 204,
|
| 180 |
+
"ceiling_m": 4600,
|
| 181 |
+
"endurance_hr": 9,
|
| 182 |
+
"range_km": 125
|
| 183 |
+
},
|
| 184 |
+
"notes": "US Army brigade-level tactical ISR; catapult launched.",
|
| 185 |
+
"source": "https://en.wikipedia.org/wiki/AAI_RQ-7_Shadow"
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"id": "rq21",
|
| 189 |
+
"model": "RQ-21 Blackjack",
|
| 190 |
+
"manufacturer": "Boeing / Insitu",
|
| 191 |
+
"country": "USA",
|
| 192 |
+
"side": "allied",
|
| 193 |
+
"role": "tactical ISR",
|
| 194 |
+
"group": "Group 3",
|
| 195 |
+
"telemetry": [
|
| 196 |
+
"LOS datalink",
|
| 197 |
+
"EO/IR"
|
| 198 |
+
],
|
| 199 |
+
"specs": {
|
| 200 |
+
"wingspan_m": 4.9,
|
| 201 |
+
"mtow_kg": 61,
|
| 202 |
+
"speed_kmh": 167,
|
| 203 |
+
"ceiling_m": 4900,
|
| 204 |
+
"endurance_hr": 16
|
| 205 |
+
},
|
| 206 |
+
"notes": "USMC/Navy expeditionary ISR; runway-independent (catapult/Skyhook).",
|
| 207 |
+
"source": "https://en.wikipedia.org/wiki/Boeing_Insitu_RQ-21_Blackjack"
|
| 208 |
+
},
|
| 209 |
+
{
|
| 210 |
+
"id": "scaneagle",
|
| 211 |
+
"model": "ScanEagle",
|
| 212 |
+
"manufacturer": "Boeing / Insitu",
|
| 213 |
+
"country": "USA",
|
| 214 |
+
"side": "allied",
|
| 215 |
+
"role": "ISR",
|
| 216 |
+
"group": "Group 2",
|
| 217 |
+
"telemetry": [
|
| 218 |
+
"LOS datalink",
|
| 219 |
+
"EO/IR"
|
| 220 |
+
],
|
| 221 |
+
"specs": {
|
| 222 |
+
"wingspan_m": 3.1,
|
| 223 |
+
"mtow_kg": 22,
|
| 224 |
+
"speed_kmh": 148,
|
| 225 |
+
"ceiling_m": 5950,
|
| 226 |
+
"endurance_hr": 24
|
| 227 |
+
},
|
| 228 |
+
"notes": "Long-endurance small ISR; widely exported to allies.",
|
| 229 |
+
"source": "https://en.wikipedia.org/wiki/Boeing_Insitu_ScanEagle"
|
| 230 |
+
},
|
| 231 |
+
{
|
| 232 |
+
"id": "vbat",
|
| 233 |
+
"model": "V-BAT",
|
| 234 |
+
"manufacturer": "Shield AI",
|
| 235 |
+
"country": "USA",
|
| 236 |
+
"side": "allied",
|
| 237 |
+
"role": "ISR",
|
| 238 |
+
"group": "Group 3",
|
| 239 |
+
"telemetry": [
|
| 240 |
+
"LOS datalink",
|
| 241 |
+
"EO/IR",
|
| 242 |
+
"Hivemind autonomy"
|
| 243 |
+
],
|
| 244 |
+
"specs": {
|
| 245 |
+
"height_m": 2.7,
|
| 246 |
+
"mtow_kg": 57,
|
| 247 |
+
"speed_kmh": 167,
|
| 248 |
+
"ceiling_m": 6100,
|
| 249 |
+
"endurance_hr": 10
|
| 250 |
+
},
|
| 251 |
+
"notes": "VTOL tail-sitter; AI autonomy (Hivemind) for GPS-denied ops.",
|
| 252 |
+
"source": "https://en.wikipedia.org/wiki/UAS_groups_of_the_United_States_military"
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"id": "rq11",
|
| 256 |
+
"model": "RQ-11B Raven",
|
| 257 |
+
"manufacturer": "AeroVironment",
|
| 258 |
+
"country": "USA",
|
| 259 |
+
"side": "allied",
|
| 260 |
+
"role": "ISR",
|
| 261 |
+
"group": "Group 1",
|
| 262 |
+
"telemetry": [
|
| 263 |
+
"analog/digital LOS",
|
| 264 |
+
"EO/IR"
|
| 265 |
+
],
|
| 266 |
+
"specs": {
|
| 267 |
+
"wingspan_m": 1.4,
|
| 268 |
+
"mtow_kg": 1.9,
|
| 269 |
+
"speed_kmh": 81,
|
| 270 |
+
"ceiling_m": 4267,
|
| 271 |
+
"endurance_hr": 1.5,
|
| 272 |
+
"range_km": 10
|
| 273 |
+
},
|
| 274 |
+
"notes": "Most-produced US military UAS; hand-launched.",
|
| 275 |
+
"source": "https://en.wikipedia.org/wiki/AeroVironment_RQ-11_Raven"
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"id": "rq20",
|
| 279 |
+
"model": "RQ-20 Puma",
|
| 280 |
+
"manufacturer": "AeroVironment",
|
| 281 |
+
"country": "USA",
|
| 282 |
+
"side": "allied",
|
| 283 |
+
"role": "ISR",
|
| 284 |
+
"group": "Group 1",
|
| 285 |
+
"telemetry": [
|
| 286 |
+
"digital LOS",
|
| 287 |
+
"EO/IR"
|
| 288 |
+
],
|
| 289 |
+
"specs": {
|
| 290 |
+
"wingspan_m": 2.8,
|
| 291 |
+
"mtow_kg": 6.3,
|
| 292 |
+
"speed_kmh": 83,
|
| 293 |
+
"ceiling_m": 3000,
|
| 294 |
+
"endurance_hr": 2.5
|
| 295 |
+
},
|
| 296 |
+
"notes": "All-environment hand-launched ISR (land/maritime).",
|
| 297 |
+
"source": "https://en.wikipedia.org/wiki/AeroVironment_RQ-20_Puma"
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"id": "wasp",
|
| 301 |
+
"model": "Wasp AE",
|
| 302 |
+
"manufacturer": "AeroVironment",
|
| 303 |
+
"country": "USA",
|
| 304 |
+
"side": "allied",
|
| 305 |
+
"role": "ISR",
|
| 306 |
+
"group": "Group 1",
|
| 307 |
+
"telemetry": [
|
| 308 |
+
"digital LOS",
|
| 309 |
+
"EO/IR"
|
| 310 |
+
],
|
| 311 |
+
"specs": {
|
| 312 |
+
"wingspan_m": 1.0,
|
| 313 |
+
"mtow_kg": 1.3,
|
| 314 |
+
"speed_kmh": 74,
|
| 315 |
+
"endurance_hr": 0.83
|
| 316 |
+
},
|
| 317 |
+
"notes": "Micro hand-launched ISR; squad-level.",
|
| 318 |
+
"source": "https://en.wikipedia.org/wiki/AeroVironment_Wasp_III"
|
| 319 |
+
},
|
| 320 |
+
{
|
| 321 |
+
"id": "sb300",
|
| 322 |
+
"model": "Switchblade 300 (Blk 20)",
|
| 323 |
+
"manufacturer": "AeroVironment",
|
| 324 |
+
"country": "USA",
|
| 325 |
+
"side": "allied",
|
| 326 |
+
"role": "loitering munition",
|
| 327 |
+
"group": "Group 1",
|
| 328 |
+
"telemetry": [
|
| 329 |
+
"encrypted RF",
|
| 330 |
+
"GPS",
|
| 331 |
+
"EO/IR"
|
| 332 |
+
],
|
| 333 |
+
"specs": {
|
| 334 |
+
"length_cm": 49.5,
|
| 335 |
+
"mass_kg": 3.27,
|
| 336 |
+
"speed_kmh": 161,
|
| 337 |
+
"endurance_min": 20,
|
| 338 |
+
"range_km": 30,
|
| 339 |
+
"warhead": "frag/EFP"
|
| 340 |
+
},
|
| 341 |
+
"notes": ">700 sent to Ukraine; backpackable precision strike.",
|
| 342 |
+
"source": "https://www.avinc.com/solution/switchblade-300-block-20/"
|
| 343 |
+
},
|
| 344 |
+
{
|
| 345 |
+
"id": "sb600",
|
| 346 |
+
"model": "Switchblade 600",
|
| 347 |
+
"manufacturer": "AeroVironment",
|
| 348 |
+
"country": "USA",
|
| 349 |
+
"side": "allied",
|
| 350 |
+
"role": "loitering munition",
|
| 351 |
+
"group": "Group 2",
|
| 352 |
+
"telemetry": [
|
| 353 |
+
"encrypted RF",
|
| 354 |
+
"SAASM GPS",
|
| 355 |
+
"EO/IR",
|
| 356 |
+
"DDL"
|
| 357 |
+
],
|
| 358 |
+
"specs": {
|
| 359 |
+
"length_cm": 130,
|
| 360 |
+
"mass_kg": 29.5,
|
| 361 |
+
"speed_kmh": 185,
|
| 362 |
+
"endurance_min": 40,
|
| 363 |
+
"range_km": 90,
|
| 364 |
+
"warhead": "anti-armor (Javelin-derived)"
|
| 365 |
+
},
|
| 366 |
+
"notes": "Anti-armor loitering munition; 90+ km w/ relay.",
|
| 367 |
+
"source": "https://www.avinc.com/solution/switchblade-600/"
|
| 368 |
+
},
|
| 369 |
+
{
|
| 370 |
+
"id": "phoenixghost",
|
| 371 |
+
"model": "Phoenix Ghost",
|
| 372 |
+
"manufacturer": "AEVEX Aerospace",
|
| 373 |
+
"country": "USA",
|
| 374 |
+
"side": "allied",
|
| 375 |
+
"role": "loitering munition",
|
| 376 |
+
"group": "Group 2",
|
| 377 |
+
"telemetry": [
|
| 378 |
+
"encrypted RF",
|
| 379 |
+
"EO/IR"
|
| 380 |
+
],
|
| 381 |
+
"specs": {
|
| 382 |
+
"endurance_min": 360,
|
| 383 |
+
"warhead": "anti-personnel/light-armor"
|
| 384 |
+
},
|
| 385 |
+
"notes": "Developed with US for Ukraine; long-endurance loiter.",
|
| 386 |
+
"source": "https://en.wikipedia.org/wiki/Phoenix_Ghost"
|
| 387 |
+
},
|
| 388 |
+
{
|
| 389 |
+
"id": "altius600",
|
| 390 |
+
"model": "ALTIUS-600",
|
| 391 |
+
"manufacturer": "Anduril (Area-I)",
|
| 392 |
+
"country": "USA",
|
| 393 |
+
"side": "allied",
|
| 394 |
+
"role": "loitering ISR/strike",
|
| 395 |
+
"group": "Group 2",
|
| 396 |
+
"telemetry": [
|
| 397 |
+
"mesh datalink",
|
| 398 |
+
"EO/IR"
|
| 399 |
+
],
|
| 400 |
+
"specs": {
|
| 401 |
+
"mass_kg": 12.7,
|
| 402 |
+
"endurance_hr": 4,
|
| 403 |
+
"range_km": 440
|
| 404 |
+
},
|
| 405 |
+
"notes": "Air-launched effect; tube-launched swarm-capable.",
|
| 406 |
+
"source": "https://en.wikipedia.org/wiki/Anduril_Industries"
|
| 407 |
+
},
|
| 408 |
+
{
|
| 409 |
+
"id": "altius700",
|
| 410 |
+
"model": "ALTIUS-700",
|
| 411 |
+
"manufacturer": "Anduril (Area-I)",
|
| 412 |
+
"country": "USA",
|
| 413 |
+
"side": "allied",
|
| 414 |
+
"role": "loitering strike",
|
| 415 |
+
"group": "Group 3",
|
| 416 |
+
"telemetry": [
|
| 417 |
+
"mesh datalink",
|
| 418 |
+
"EO/IR"
|
| 419 |
+
],
|
| 420 |
+
"specs": {
|
| 421 |
+
"mass_kg": 50,
|
| 422 |
+
"endurance_hr": 4,
|
| 423 |
+
"warhead": "modular"
|
| 424 |
+
},
|
| 425 |
+
"notes": "Larger ALTIUS; heavier payload / standoff strike.",
|
| 426 |
+
"source": "https://en.wikipedia.org/wiki/Anduril_Industries"
|
| 427 |
+
},
|
| 428 |
+
{
|
| 429 |
+
"id": "tb2",
|
| 430 |
+
"model": "Bayraktar TB2",
|
| 431 |
+
"manufacturer": "Baykar",
|
| 432 |
+
"country": "Turkey",
|
| 433 |
+
"side": "dual-use",
|
| 434 |
+
"role": "strike/ISR",
|
| 435 |
+
"group": "Group 4",
|
| 436 |
+
"telemetry": [
|
| 437 |
+
"LOS datalink",
|
| 438 |
+
"EO/IR",
|
| 439 |
+
"laser designator"
|
| 440 |
+
],
|
| 441 |
+
"specs": {
|
| 442 |
+
"wingspan_m": 12.0,
|
| 443 |
+
"length_m": 6.5,
|
| 444 |
+
"mtow_kg": 1200,
|
| 445 |
+
"speed_kmh": 222,
|
| 446 |
+
"ceiling_m": 8239,
|
| 447 |
+
"range_km": 300,
|
| 448 |
+
"unit_cost_usd": 5000000
|
| 449 |
+
},
|
| 450 |
+
"notes": "Used by allies AND adversaries; MAM-L/MAM-C munitions.",
|
| 451 |
+
"source": "https://www.globalmilitary.net/compare/aircraft/bayraktar-tb2-vs-shahed-136/"
|
| 452 |
+
},
|
| 453 |
+
{
|
| 454 |
+
"id": "tb2akinci",
|
| 455 |
+
"model": "Bayraktar Akinci",
|
| 456 |
+
"manufacturer": "Baykar",
|
| 457 |
+
"country": "Turkey",
|
| 458 |
+
"side": "dual-use",
|
| 459 |
+
"role": "strike/ISR",
|
| 460 |
+
"group": "Group 5",
|
| 461 |
+
"telemetry": [
|
| 462 |
+
"SATCOM",
|
| 463 |
+
"AESA radar",
|
| 464 |
+
"EO/IR"
|
| 465 |
+
],
|
| 466 |
+
"specs": {
|
| 467 |
+
"wingspan_m": 20.0,
|
| 468 |
+
"mtow_kg": 5500,
|
| 469 |
+
"speed_kmh": 361,
|
| 470 |
+
"ceiling_m": 12190,
|
| 471 |
+
"endurance_hr": 24
|
| 472 |
+
},
|
| 473 |
+
"notes": "HALE strike; cruise-missile capable.",
|
| 474 |
+
"source": "https://en.wikipedia.org/wiki/Baykar_Bayraktar_Ak%C4%B1nc%C4%B1"
|
| 475 |
+
},
|
| 476 |
+
{
|
| 477 |
+
"id": "heron",
|
| 478 |
+
"model": "IAI Heron",
|
| 479 |
+
"manufacturer": "Israel Aerospace Industries",
|
| 480 |
+
"country": "Israel",
|
| 481 |
+
"side": "allied",
|
| 482 |
+
"role": "ISR",
|
| 483 |
+
"group": "Group 4",
|
| 484 |
+
"telemetry": [
|
| 485 |
+
"SATCOM",
|
| 486 |
+
"EO/IR",
|
| 487 |
+
"SAR"
|
| 488 |
+
],
|
| 489 |
+
"specs": {
|
| 490 |
+
"wingspan_m": 16.6,
|
| 491 |
+
"mtow_kg": 1150,
|
| 492 |
+
"speed_kmh": 207,
|
| 493 |
+
"ceiling_m": 10000,
|
| 494 |
+
"endurance_hr": 52
|
| 495 |
+
},
|
| 496 |
+
"notes": "MALE ISR; widely exported to allied nations.",
|
| 497 |
+
"source": "https://en.wikipedia.org/wiki/IAI_Heron"
|
| 498 |
+
},
|
| 499 |
+
{
|
| 500 |
+
"id": "hermes900",
|
| 501 |
+
"model": "Elbit Hermes 900",
|
| 502 |
+
"manufacturer": "Elbit Systems",
|
| 503 |
+
"country": "Israel",
|
| 504 |
+
"side": "allied",
|
| 505 |
+
"role": "ISR/strike",
|
| 506 |
+
"group": "Group 4",
|
| 507 |
+
"telemetry": [
|
| 508 |
+
"SATCOM",
|
| 509 |
+
"EO/IR",
|
| 510 |
+
"SAR/GMTI"
|
| 511 |
+
],
|
| 512 |
+
"specs": {
|
| 513 |
+
"wingspan_m": 15.0,
|
| 514 |
+
"mtow_kg": 1180,
|
| 515 |
+
"speed_kmh": 220,
|
| 516 |
+
"ceiling_m": 9100,
|
| 517 |
+
"endurance_hr": 36
|
| 518 |
+
},
|
| 519 |
+
"notes": "MALE multi-payload ISR; allied export.",
|
| 520 |
+
"source": "https://en.wikipedia.org/wiki/Elbit_Hermes_900"
|
| 521 |
+
},
|
| 522 |
+
{
|
| 523 |
+
"id": "harop",
|
| 524 |
+
"model": "IAI Harop",
|
| 525 |
+
"manufacturer": "IAI",
|
| 526 |
+
"country": "Israel",
|
| 527 |
+
"side": "allied",
|
| 528 |
+
"role": "loitering munition",
|
| 529 |
+
"group": "Group 3",
|
| 530 |
+
"telemetry": [
|
| 531 |
+
"man-in-loop datalink",
|
| 532 |
+
"EO/IR"
|
| 533 |
+
],
|
| 534 |
+
"specs": {
|
| 535 |
+
"wingspan_m": 3.0,
|
| 536 |
+
"speed_kmh": 417,
|
| 537 |
+
"endurance_hr": 6,
|
| 538 |
+
"range_km": 1000,
|
| 539 |
+
"warhead": "23 kg"
|
| 540 |
+
},
|
| 541 |
+
"notes": "Anti-radiation loitering munition; SEAD.",
|
| 542 |
+
"source": "https://en.wikipedia.org/wiki/IAI_Harop"
|
| 543 |
+
},
|
| 544 |
+
{
|
| 545 |
+
"id": "shahed136",
|
| 546 |
+
"model": "Shahed-136 / Geran-2",
|
| 547 |
+
"manufacturer": "HESA (Iran)",
|
| 548 |
+
"country": "Iran",
|
| 549 |
+
"side": "adversary",
|
| 550 |
+
"role": "loitering munition",
|
| 551 |
+
"group": "Group 3",
|
| 552 |
+
"telemetry": [
|
| 553 |
+
"INS",
|
| 554 |
+
"consumer GPS",
|
| 555 |
+
"RF link ~150km"
|
| 556 |
+
],
|
| 557 |
+
"specs": {
|
| 558 |
+
"length_m": 3.5,
|
| 559 |
+
"wingspan_m": 2.5,
|
| 560 |
+
"mass_kg": 200,
|
| 561 |
+
"speed_kmh": 185,
|
| 562 |
+
"ceiling_m": 4000,
|
| 563 |
+
"range_km": 2500,
|
| 564 |
+
"warhead": "30-50 kg HE-frag"
|
| 565 |
+
},
|
| 566 |
+
"notes": "Russian-operated as Geran-2; salvo/swarm launch; MD-550 50hp pusher.",
|
| 567 |
+
"source": "https://armyrecognition.com/military-products/army/unmanned-systems/unmanned-aerial-vehicles/shahed-136-loitering-munition-kamikaze-suicide-drone-technical-data"
|
| 568 |
+
},
|
| 569 |
+
{
|
| 570 |
+
"id": "shahed131",
|
| 571 |
+
"model": "Shahed-131 / Geran-1",
|
| 572 |
+
"manufacturer": "HESA (Iran)",
|
| 573 |
+
"country": "Iran",
|
| 574 |
+
"side": "adversary",
|
| 575 |
+
"role": "loitering munition",
|
| 576 |
+
"group": "Group 2",
|
| 577 |
+
"telemetry": [
|
| 578 |
+
"INS",
|
| 579 |
+
"consumer GPS"
|
| 580 |
+
],
|
| 581 |
+
"specs": {
|
| 582 |
+
"range_km": 900,
|
| 583 |
+
"warhead": "15 kg",
|
| 584 |
+
"speed_kmh": 150
|
| 585 |
+
},
|
| 586 |
+
"notes": "Smaller predecessor to Shahed-136; upward-only fin tips.",
|
| 587 |
+
"source": "https://en.wikipedia.org/wiki/Shahed_drones"
|
| 588 |
+
},
|
| 589 |
+
{
|
| 590 |
+
"id": "shahed129",
|
| 591 |
+
"model": "Shahed-129",
|
| 592 |
+
"manufacturer": "HESA (Iran)",
|
| 593 |
+
"country": "Iran",
|
| 594 |
+
"side": "adversary",
|
| 595 |
+
"role": "strike/ISR",
|
| 596 |
+
"group": "Group 4",
|
| 597 |
+
"telemetry": [
|
| 598 |
+
"LOS datalink",
|
| 599 |
+
"EO/IR"
|
| 600 |
+
],
|
| 601 |
+
"specs": {
|
| 602 |
+
"wingspan_m": 16.0,
|
| 603 |
+
"endurance_hr": 24,
|
| 604 |
+
"range_km": 1700,
|
| 605 |
+
"warhead": "Sadid bombs"
|
| 606 |
+
},
|
| 607 |
+
"notes": "Iranian MALE Reaper-analog.",
|
| 608 |
+
"source": "https://en.wikipedia.org/wiki/HESA_Shahed_129"
|
| 609 |
+
},
|
| 610 |
+
{
|
| 611 |
+
"id": "mohajer6",
|
| 612 |
+
"model": "Mohajer-6",
|
| 613 |
+
"manufacturer": "HESA / Qods (Iran)",
|
| 614 |
+
"country": "Iran",
|
| 615 |
+
"side": "adversary",
|
| 616 |
+
"role": "strike/ISR",
|
| 617 |
+
"group": "Group 3",
|
| 618 |
+
"telemetry": [
|
| 619 |
+
"LOS datalink",
|
| 620 |
+
"EO/IR"
|
| 621 |
+
],
|
| 622 |
+
"specs": {
|
| 623 |
+
"wingspan_m": 10.0,
|
| 624 |
+
"mtow_kg": 670,
|
| 625 |
+
"endurance_hr": 12,
|
| 626 |
+
"range_km": 200,
|
| 627 |
+
"warhead": "Qaem bombs"
|
| 628 |
+
},
|
| 629 |
+
"notes": "Iranian tactical strike/ISR; supplied to Russia.",
|
| 630 |
+
"source": "https://en.wikipedia.org/wiki/Mohajer-6"
|
| 631 |
+
},
|
| 632 |
+
{
|
| 633 |
+
"id": "lancet3",
|
| 634 |
+
"model": "Lancet-3 (Izdeliye-52)",
|
| 635 |
+
"manufacturer": "ZALA / Kalashnikov (Russia)",
|
| 636 |
+
"country": "Russia",
|
| 637 |
+
"side": "adversary",
|
| 638 |
+
"role": "loitering munition",
|
| 639 |
+
"group": "Group 1",
|
| 640 |
+
"telemetry": [
|
| 641 |
+
"RF datalink",
|
| 642 |
+
"TV terminal guidance"
|
| 643 |
+
],
|
| 644 |
+
"specs": {
|
| 645 |
+
"mass_kg": 12,
|
| 646 |
+
"speed_kmh": 110,
|
| 647 |
+
"endurance_min": 40,
|
| 648 |
+
"range_km": 40,
|
| 649 |
+
"warhead": "3 kg HE/HE-frag"
|
| 650 |
+
},
|
| 651 |
+
"notes": "Catapult-launched; EO + TV terminal; anti-armor/artillery.",
|
| 652 |
+
"source": "https://armyrecognition.com/military-products/army/unmanned-systems/unmanned-aerial-vehicles/lancet-3-loitering-munition-kamikaze-drone-russia-data-fact-sheet"
|
| 653 |
+
},
|
| 654 |
+
{
|
| 655 |
+
"id": "lancet1",
|
| 656 |
+
"model": "Lancet-1",
|
| 657 |
+
"manufacturer": "ZALA (Russia)",
|
| 658 |
+
"country": "Russia",
|
| 659 |
+
"side": "adversary",
|
| 660 |
+
"role": "loitering munition",
|
| 661 |
+
"group": "Group 1",
|
| 662 |
+
"telemetry": [
|
| 663 |
+
"RF datalink",
|
| 664 |
+
"TV terminal"
|
| 665 |
+
],
|
| 666 |
+
"specs": {
|
| 667 |
+
"mass_kg": 5,
|
| 668 |
+
"warhead": "1 kg",
|
| 669 |
+
"range_km": 40
|
| 670 |
+
},
|
| 671 |
+
"notes": "Smaller Lancet variant.",
|
| 672 |
+
"source": "https://armyrecognition.com/military-products/army/unmanned-systems/unmanned-aerial-vehicles/lancet-3-loitering-munition-kamikaze-drone-russia-data-fact-sheet"
|
| 673 |
+
},
|
| 674 |
+
{
|
| 675 |
+
"id": "orlan10",
|
| 676 |
+
"model": "Orlan-10",
|
| 677 |
+
"manufacturer": "STC (Russia)",
|
| 678 |
+
"country": "Russia",
|
| 679 |
+
"side": "adversary",
|
| 680 |
+
"role": "ISR/EW",
|
| 681 |
+
"group": "Group 2",
|
| 682 |
+
"telemetry": [
|
| 683 |
+
"RF datalink",
|
| 684 |
+
"EO/IR",
|
| 685 |
+
"Leer-3 EW"
|
| 686 |
+
],
|
| 687 |
+
"specs": {
|
| 688 |
+
"wingspan_m": 3.1,
|
| 689 |
+
"mtow_kg": 18,
|
| 690 |
+
"speed_kmh": 150,
|
| 691 |
+
"ceiling_m": 5000,
|
| 692 |
+
"endurance_hr": 16,
|
| 693 |
+
"range_km": 120
|
| 694 |
+
},
|
| 695 |
+
"notes": "Russia's workhorse ISR/artillery-spotting UAV; EW variant.",
|
| 696 |
+
"source": "https://en.wikipedia.org/wiki/Orlan-10"
|
| 697 |
+
},
|
| 698 |
+
{
|
| 699 |
+
"id": "forpost",
|
| 700 |
+
"model": "Forpost-R",
|
| 701 |
+
"manufacturer": "UZGA (Russia)",
|
| 702 |
+
"country": "Russia",
|
| 703 |
+
"side": "adversary",
|
| 704 |
+
"role": "strike/ISR",
|
| 705 |
+
"group": "Group 4",
|
| 706 |
+
"telemetry": [
|
| 707 |
+
"LOS datalink",
|
| 708 |
+
"EO/IR"
|
| 709 |
+
],
|
| 710 |
+
"specs": {
|
| 711 |
+
"wingspan_m": 8.5,
|
| 712 |
+
"mtow_kg": 500,
|
| 713 |
+
"endurance_hr": 18,
|
| 714 |
+
"range_km": 250
|
| 715 |
+
},
|
| 716 |
+
"notes": "Russian licensed IAI Searcher derivative; now strike-capable.",
|
| 717 |
+
"source": "https://en.wikipedia.org/wiki/IAI_Searcher"
|
| 718 |
+
},
|
| 719 |
+
{
|
| 720 |
+
"id": "wingloong2",
|
| 721 |
+
"model": "Wing Loong II",
|
| 722 |
+
"manufacturer": "CAIG / CASC",
|
| 723 |
+
"country": "China",
|
| 724 |
+
"side": "adversary",
|
| 725 |
+
"role": "strike/ISR",
|
| 726 |
+
"group": "Group 5",
|
| 727 |
+
"telemetry": [
|
| 728 |
+
"SATCOM >1000km",
|
| 729 |
+
"EO/IR",
|
| 730 |
+
"SAR"
|
| 731 |
+
],
|
| 732 |
+
"specs": {
|
| 733 |
+
"length_m": 11.0,
|
| 734 |
+
"wingspan_m": 20.5,
|
| 735 |
+
"mtow_kg": 4200,
|
| 736 |
+
"speed_kmh": 370,
|
| 737 |
+
"ceiling_m": 9900,
|
| 738 |
+
"endurance_hr": 32,
|
| 739 |
+
"range_km": 1000
|
| 740 |
+
},
|
| 741 |
+
"notes": "Chinese MALE; up to 480 kg ordnance; widely exported.",
|
| 742 |
+
"source": "https://en.wikipedia.org/wiki/CAIG_Wing_Loong_II"
|
| 743 |
+
},
|
| 744 |
+
{
|
| 745 |
+
"id": "wingloong1",
|
| 746 |
+
"model": "Wing Loong I",
|
| 747 |
+
"manufacturer": "CAIG / CASC",
|
| 748 |
+
"country": "China",
|
| 749 |
+
"side": "adversary",
|
| 750 |
+
"role": "strike/ISR",
|
| 751 |
+
"group": "Group 4",
|
| 752 |
+
"telemetry": [
|
| 753 |
+
"LOS datalink",
|
| 754 |
+
"EO/IR"
|
| 755 |
+
],
|
| 756 |
+
"specs": {
|
| 757 |
+
"wingspan_m": 14.0,
|
| 758 |
+
"mtow_kg": 1100,
|
| 759 |
+
"endurance_hr": 20,
|
| 760 |
+
"range_km": 4000
|
| 761 |
+
},
|
| 762 |
+
"notes": "Predator-class Chinese export drone.",
|
| 763 |
+
"source": "https://en.wikipedia.org/wiki/Chengdu_Pterodactyl_I"
|
| 764 |
+
},
|
| 765 |
+
{
|
| 766 |
+
"id": "ch4",
|
| 767 |
+
"model": "CH-4 Rainbow",
|
| 768 |
+
"manufacturer": "CASC",
|
| 769 |
+
"country": "China",
|
| 770 |
+
"side": "adversary",
|
| 771 |
+
"role": "strike/ISR",
|
| 772 |
+
"group": "Group 5",
|
| 773 |
+
"telemetry": [
|
| 774 |
+
"SATCOM",
|
| 775 |
+
"EO/IR"
|
| 776 |
+
],
|
| 777 |
+
"specs": {
|
| 778 |
+
"wingspan_m": 18.0,
|
| 779 |
+
"mtow_kg": 1330,
|
| 780 |
+
"speed_kmh": 235,
|
| 781 |
+
"ceiling_m": 8000,
|
| 782 |
+
"endurance_hr": 40,
|
| 783 |
+
"range_km": 3500
|
| 784 |
+
},
|
| 785 |
+
"notes": "Reaper-class Chinese export; AR-1 missiles.",
|
| 786 |
+
"source": "https://en.wikipedia.org/wiki/CASC_Rainbow"
|
| 787 |
+
},
|
| 788 |
+
{
|
| 789 |
+
"id": "ch5",
|
| 790 |
+
"model": "CH-5 Rainbow",
|
| 791 |
+
"manufacturer": "CASC",
|
| 792 |
+
"country": "China",
|
| 793 |
+
"side": "adversary",
|
| 794 |
+
"role": "strike/ISR",
|
| 795 |
+
"group": "Group 5",
|
| 796 |
+
"telemetry": [
|
| 797 |
+
"SATCOM",
|
| 798 |
+
"EO/IR",
|
| 799 |
+
"SAR"
|
| 800 |
+
],
|
| 801 |
+
"specs": {
|
| 802 |
+
"wingspan_m": 21.0,
|
| 803 |
+
"mtow_kg": 3300,
|
| 804 |
+
"endurance_hr": 60,
|
| 805 |
+
"range_km": 10000
|
| 806 |
+
},
|
| 807 |
+
"notes": "Large Chinese MALE; 1 t payload.",
|
| 808 |
+
"source": "https://en.wikipedia.org/wiki/CASC_Rainbow"
|
| 809 |
+
},
|
| 810 |
+
{
|
| 811 |
+
"id": "gj2",
|
| 812 |
+
"model": "GJ-2 (Wing Loong II PLA)",
|
| 813 |
+
"manufacturer": "CAIG",
|
| 814 |
+
"country": "China",
|
| 815 |
+
"side": "adversary",
|
| 816 |
+
"role": "strike/ISR",
|
| 817 |
+
"group": "Group 5",
|
| 818 |
+
"telemetry": [
|
| 819 |
+
"SATCOM",
|
| 820 |
+
"EO/IR"
|
| 821 |
+
],
|
| 822 |
+
"specs": {
|
| 823 |
+
"wingspan_m": 20.5,
|
| 824 |
+
"mtow_kg": 4200,
|
| 825 |
+
"endurance_hr": 20
|
| 826 |
+
},
|
| 827 |
+
"notes": "PLA-operated armed Wing Loong II designation.",
|
| 828 |
+
"source": "https://en.wikipedia.org/wiki/CAIG_Wing_Loong_II"
|
| 829 |
+
},
|
| 830 |
+
{
|
| 831 |
+
"id": "wz7",
|
| 832 |
+
"model": "WZ-7 Soaring Dragon",
|
| 833 |
+
"manufacturer": "Guizhou",
|
| 834 |
+
"country": "China",
|
| 835 |
+
"side": "adversary",
|
| 836 |
+
"role": "HALE ISR",
|
| 837 |
+
"group": "Group 5",
|
| 838 |
+
"telemetry": [
|
| 839 |
+
"SATCOM",
|
| 840 |
+
"SAR",
|
| 841 |
+
"SIGINT"
|
| 842 |
+
],
|
| 843 |
+
"specs": {
|
| 844 |
+
"wingspan_m": 25.0,
|
| 845 |
+
"ceiling_m": 18000,
|
| 846 |
+
"endurance_hr": 10
|
| 847 |
+
},
|
| 848 |
+
"notes": "Chinese HALE ISR; joined-wing config; Global Hawk analog.",
|
| 849 |
+
"source": "https://en.wikipedia.org/wiki/Guizhou_Soar_Dragon"
|
| 850 |
+
},
|
| 851 |
+
{
|
| 852 |
+
"id": "djimavic3",
|
| 853 |
+
"model": "DJI Mavic 3",
|
| 854 |
+
"manufacturer": "DJI",
|
| 855 |
+
"country": "China",
|
| 856 |
+
"side": "dual-use",
|
| 857 |
+
"role": "commercial ISR / improvised",
|
| 858 |
+
"group": "Group 1",
|
| 859 |
+
"telemetry": [
|
| 860 |
+
"OcuSync RF",
|
| 861 |
+
"DJI AeroScope",
|
| 862 |
+
"Remote ID"
|
| 863 |
+
],
|
| 864 |
+
"specs": {
|
| 865 |
+
"mass_kg": 0.9,
|
| 866 |
+
"speed_kmh": 75,
|
| 867 |
+
"ceiling_m": 6000,
|
| 868 |
+
"endurance_min": 46,
|
| 869 |
+
"range_km": 15
|
| 870 |
+
},
|
| 871 |
+
"notes": "Dominant small-quad used by BOTH sides in Ukraine; munition drops.",
|
| 872 |
+
"source": "https://en.wikipedia.org/wiki/DJI_Mavic"
|
| 873 |
+
},
|
| 874 |
+
{
|
| 875 |
+
"id": "djimatrice300",
|
| 876 |
+
"model": "DJI Matrice 300 RTK",
|
| 877 |
+
"manufacturer": "DJI",
|
| 878 |
+
"country": "China",
|
| 879 |
+
"side": "dual-use",
|
| 880 |
+
"role": "commercial ISR",
|
| 881 |
+
"group": "Group 1",
|
| 882 |
+
"telemetry": [
|
| 883 |
+
"OcuSync RF",
|
| 884 |
+
"Remote ID"
|
| 885 |
+
],
|
| 886 |
+
"specs": {
|
| 887 |
+
"mass_kg": 6.3,
|
| 888 |
+
"speed_kmh": 82,
|
| 889 |
+
"endurance_min": 55,
|
| 890 |
+
"range_km": 15
|
| 891 |
+
},
|
| 892 |
+
"notes": "Heavy-lift commercial quad; ISR + payload.",
|
| 893 |
+
"source": "https://en.wikipedia.org/wiki/DJI"
|
| 894 |
+
},
|
| 895 |
+
{
|
| 896 |
+
"id": "djimini",
|
| 897 |
+
"model": "DJI Mini 4 Pro",
|
| 898 |
+
"manufacturer": "DJI",
|
| 899 |
+
"country": "China",
|
| 900 |
+
"side": "dual-use",
|
| 901 |
+
"role": "commercial micro ISR",
|
| 902 |
+
"group": "Group 1",
|
| 903 |
+
"telemetry": [
|
| 904 |
+
"OcuSync RF",
|
| 905 |
+
"Remote ID"
|
| 906 |
+
],
|
| 907 |
+
"specs": {
|
| 908 |
+
"mass_kg": 0.249,
|
| 909 |
+
"endurance_min": 34,
|
| 910 |
+
"range_km": 10
|
| 911 |
+
},
|
| 912 |
+
"notes": "Sub-250g micro; ubiquitous recon quad.",
|
| 913 |
+
"source": "https://en.wikipedia.org/wiki/DJI_Mini"
|
| 914 |
+
},
|
| 915 |
+
{
|
| 916 |
+
"id": "fpv7in",
|
| 917 |
+
"model": "FPV 7\" racing quad (kamikaze)",
|
| 918 |
+
"manufacturer": "various",
|
| 919 |
+
"country": "multi",
|
| 920 |
+
"side": "dual-use",
|
| 921 |
+
"role": "FPV strike",
|
| 922 |
+
"group": "Group 1",
|
| 923 |
+
"telemetry": [
|
| 924 |
+
"analog/digital VTX",
|
| 925 |
+
"ELRS RF"
|
| 926 |
+
],
|
| 927 |
+
"specs": {
|
| 928 |
+
"mass_kg": 1.0,
|
| 929 |
+
"speed_kmh": 150,
|
| 930 |
+
"range_km": 10,
|
| 931 |
+
"warhead": "RPG/shaped charge"
|
| 932 |
+
},
|
| 933 |
+
"notes": "Mass-produced FPV kamikaze; defining threat of 2024-2026 attrition warfare.",
|
| 934 |
+
"source": "https://en.wikipedia.org/wiki/First-person_view_(radio_control)"
|
| 935 |
+
},
|
| 936 |
+
{
|
| 937 |
+
"id": "autel",
|
| 938 |
+
"model": "Autel EVO Max 4T",
|
| 939 |
+
"manufacturer": "Autel Robotics",
|
| 940 |
+
"country": "China",
|
| 941 |
+
"side": "dual-use",
|
| 942 |
+
"role": "commercial ISR",
|
| 943 |
+
"group": "Group 1",
|
| 944 |
+
"telemetry": [
|
| 945 |
+
"Autel SkyLink",
|
| 946 |
+
"Remote ID"
|
| 947 |
+
],
|
| 948 |
+
"specs": {
|
| 949 |
+
"mass_kg": 1.6,
|
| 950 |
+
"endurance_min": 42,
|
| 951 |
+
"range_km": 20
|
| 952 |
+
},
|
| 953 |
+
"notes": "DJI alternative; thermal + visual ISR quad.",
|
| 954 |
+
"source": "https://en.wikipedia.org/wiki/Autel_Robotics"
|
| 955 |
+
},
|
| 956 |
+
{
|
| 957 |
+
"id": "leonidas",
|
| 958 |
+
"model": "Epirus Leonidas (HPM)",
|
| 959 |
+
"manufacturer": "Epirus",
|
| 960 |
+
"country": "USA",
|
| 961 |
+
"side": "counter-uas",
|
| 962 |
+
"role": "directed-energy C-UAS",
|
| 963 |
+
"group": "C-UAS",
|
| 964 |
+
"telemetry": [
|
| 965 |
+
"radar cue",
|
| 966 |
+
"Lattice C2"
|
| 967 |
+
],
|
| 968 |
+
"specs": {
|
| 969 |
+
"effect": "high-power microwave",
|
| 970 |
+
"target": "swarms",
|
| 971 |
+
"mode": "area electronics kill"
|
| 972 |
+
},
|
| 973 |
+
"notes": "Solid-state HPM; defeats swarms; demoed w/ Anduril Lattice for USMC.",
|
| 974 |
+
"source": "https://www.epirusinc.com/electronic-warfare"
|
| 975 |
+
},
|
| 976 |
+
{
|
| 977 |
+
"id": "lattice",
|
| 978 |
+
"model": "Anduril Lattice",
|
| 979 |
+
"manufacturer": "Anduril",
|
| 980 |
+
"country": "USA",
|
| 981 |
+
"side": "counter-uas",
|
| 982 |
+
"role": "C2 / sensor fusion",
|
| 983 |
+
"group": "C-UAS",
|
| 984 |
+
"telemetry": [
|
| 985 |
+
"multi-sensor fusion",
|
| 986 |
+
"autonomy"
|
| 987 |
+
],
|
| 988 |
+
"specs": {
|
| 989 |
+
"effect": "detect-track-classify-defeat C2",
|
| 990 |
+
"target": "all groups"
|
| 991 |
+
},
|
| 992 |
+
"notes": "Open C2 mesh tying sensors to effectors for counter-UAS.",
|
| 993 |
+
"source": "https://www.defenseone.com/business/2023/07/defense-startups-team-defeat-swarm-drones/388909/"
|
| 994 |
+
},
|
| 995 |
+
{
|
| 996 |
+
"id": "dronegun",
|
| 997 |
+
"model": "DroneShield DroneGun Mk4",
|
| 998 |
+
"manufacturer": "DroneShield",
|
| 999 |
+
"country": "Australia",
|
| 1000 |
+
"side": "counter-uas",
|
| 1001 |
+
"role": "RF jammer",
|
| 1002 |
+
"group": "C-UAS",
|
| 1003 |
+
"telemetry": [
|
| 1004 |
+
"RF detection"
|
| 1005 |
+
],
|
| 1006 |
+
"specs": {
|
| 1007 |
+
"effect": "RF/GNSS jam",
|
| 1008 |
+
"range_m": 1000,
|
| 1009 |
+
"target": "Group 1-2"
|
| 1010 |
+
},
|
| 1011 |
+
"notes": "Handheld RF defeat; integrated w/ Epirus.",
|
| 1012 |
+
"source": "https://uasmagazine.com/articles/droneshield-and-epirus-complete-integration"
|
| 1013 |
+
},
|
| 1014 |
+
{
|
| 1015 |
+
"id": "skywiper",
|
| 1016 |
+
"model": "SkyWiper EDM4S",
|
| 1017 |
+
"manufacturer": "NT Service",
|
| 1018 |
+
"country": "Lithuania",
|
| 1019 |
+
"side": "counter-uas",
|
| 1020 |
+
"role": "RF jammer",
|
| 1021 |
+
"group": "C-UAS",
|
| 1022 |
+
"telemetry": [
|
| 1023 |
+
"RF detection"
|
| 1024 |
+
],
|
| 1025 |
+
"specs": {
|
| 1026 |
+
"effect": "GNSS+control jam",
|
| 1027 |
+
"mass_kg": 5.5,
|
| 1028 |
+
"target": "Group 1"
|
| 1029 |
+
},
|
| 1030 |
+
"notes": "Handheld RF jammer used in Ukraine.",
|
| 1031 |
+
"source": "https://en.wikipedia.org/wiki/Counter-UAS"
|
| 1032 |
+
},
|
| 1033 |
+
{
|
| 1034 |
+
"id": "coyote",
|
| 1035 |
+
"model": "Raytheon Coyote Block 2",
|
| 1036 |
+
"manufacturer": "Raytheon (RTX)",
|
| 1037 |
+
"country": "USA",
|
| 1038 |
+
"side": "counter-uas",
|
| 1039 |
+
"role": "interceptor effector",
|
| 1040 |
+
"group": "C-UAS",
|
| 1041 |
+
"telemetry": [
|
| 1042 |
+
"Ku radar (KuRFS) cue"
|
| 1043 |
+
],
|
| 1044 |
+
"specs": {
|
| 1045 |
+
"effect": "kinetic interceptor",
|
| 1046 |
+
"target": "Group 1-3"
|
| 1047 |
+
},
|
| 1048 |
+
"notes": "Tube-launched interceptor; LIDS/FS-LIDS effector.",
|
| 1049 |
+
"source": "https://en.wikipedia.org/wiki/Raytheon_Coyote"
|
| 1050 |
+
},
|
| 1051 |
+
{
|
| 1052 |
+
"id": "kargu",
|
| 1053 |
+
"model": "STM Kargu-2",
|
| 1054 |
+
"manufacturer": "STM",
|
| 1055 |
+
"country": "Turkey",
|
| 1056 |
+
"side": "dual-use",
|
| 1057 |
+
"role": "loitering munition (swarm)",
|
| 1058 |
+
"group": "Group 1",
|
| 1059 |
+
"telemetry": [
|
| 1060 |
+
"RF datalink",
|
| 1061 |
+
"EO",
|
| 1062 |
+
"onboard AI"
|
| 1063 |
+
],
|
| 1064 |
+
"specs": {
|
| 1065 |
+
"mass_kg": 7,
|
| 1066 |
+
"endurance_min": 30,
|
| 1067 |
+
"warhead": "frag/shaped"
|
| 1068 |
+
},
|
| 1069 |
+
"notes": "Rotary loitering munition; cited in UN report on autonomous engagement (Libya 2020).",
|
| 1070 |
+
"source": "https://en.wikipedia.org/wiki/STM_Kargu"
|
| 1071 |
+
},
|
| 1072 |
+
{
|
| 1073 |
+
"id": "switchblade_blackwing",
|
| 1074 |
+
"model": "Blackwing",
|
| 1075 |
+
"manufacturer": "AeroVironment",
|
| 1076 |
+
"country": "USA",
|
| 1077 |
+
"side": "allied",
|
| 1078 |
+
"role": "ISR (Switchblade family)",
|
| 1079 |
+
"group": "Group 1",
|
| 1080 |
+
"telemetry": [
|
| 1081 |
+
"encrypted RF",
|
| 1082 |
+
"EO/IR"
|
| 1083 |
+
],
|
| 1084 |
+
"specs": {
|
| 1085 |
+
"mass_kg": 1.8,
|
| 1086 |
+
"endurance_min": 60,
|
| 1087 |
+
"range_km": 8
|
| 1088 |
+
},
|
| 1089 |
+
"notes": "Unarmed ISR variant of Switchblade 300; submarine-launchable.",
|
| 1090 |
+
"source": "https://en.wikipedia.org/wiki/AeroVironment_Switchblade"
|
| 1091 |
+
},
|
| 1092 |
+
{
|
| 1093 |
+
"id": "bayraktar_tb3",
|
| 1094 |
+
"model": "Bayraktar TB3",
|
| 1095 |
+
"manufacturer": "Baykar",
|
| 1096 |
+
"country": "Turkey",
|
| 1097 |
+
"side": "dual-use",
|
| 1098 |
+
"role": "strike/ISR (carrier)",
|
| 1099 |
+
"group": "Group 4",
|
| 1100 |
+
"telemetry": [
|
| 1101 |
+
"SATCOM",
|
| 1102 |
+
"EO/IR"
|
| 1103 |
+
],
|
| 1104 |
+
"specs": {
|
| 1105 |
+
"wingspan_m": 14.0,
|
| 1106 |
+
"mtow_kg": 1450,
|
| 1107 |
+
"endurance_hr": 24
|
| 1108 |
+
},
|
| 1109 |
+
"notes": "Folding-wing carrier-capable TB2 successor.",
|
| 1110 |
+
"source": "https://en.wikipedia.org/wiki/Baykar_Bayraktar_TB3"
|
| 1111 |
+
},
|
| 1112 |
+
{
|
| 1113 |
+
"id": "geran3",
|
| 1114 |
+
"model": "Geran-3 (jet Shahed)",
|
| 1115 |
+
"manufacturer": "Russia (Shahed deriv.)",
|
| 1116 |
+
"country": "Russia",
|
| 1117 |
+
"side": "adversary",
|
| 1118 |
+
"role": "loitering munition",
|
| 1119 |
+
"group": "Group 3",
|
| 1120 |
+
"telemetry": [
|
| 1121 |
+
"INS",
|
| 1122 |
+
"GNSS"
|
| 1123 |
+
],
|
| 1124 |
+
"specs": {
|
| 1125 |
+
"speed_kmh": 500,
|
| 1126 |
+
"warhead": "~50 kg",
|
| 1127 |
+
"note": "turbojet variant"
|
| 1128 |
+
},
|
| 1129 |
+
"notes": "Jet-powered Shahed-238 derivative; higher terminal speed.",
|
| 1130 |
+
"source": "https://en.wikipedia.org/wiki/Shahed_drones"
|
| 1131 |
+
},
|
| 1132 |
+
{
|
| 1133 |
+
"id": "zala421",
|
| 1134 |
+
"model": "ZALA 421-16E",
|
| 1135 |
+
"manufacturer": "ZALA Aero (Russia)",
|
| 1136 |
+
"country": "Russia",
|
| 1137 |
+
"side": "adversary",
|
| 1138 |
+
"role": "ISR",
|
| 1139 |
+
"group": "Group 2",
|
| 1140 |
+
"telemetry": [
|
| 1141 |
+
"RF datalink",
|
| 1142 |
+
"EO/IR"
|
| 1143 |
+
],
|
| 1144 |
+
"specs": {
|
| 1145 |
+
"wingspan_m": 2.8,
|
| 1146 |
+
"endurance_hr": 4,
|
| 1147 |
+
"range_km": 50
|
| 1148 |
+
},
|
| 1149 |
+
"notes": "Russian ISR UAV; pairs with Lancet targeting.",
|
| 1150 |
+
"source": "https://en.wikipedia.org/wiki/ZALA_Aero"
|
| 1151 |
+
}
|
| 1152 |
+
]
|
static/3d/vendor/VENDOR_MANIFEST.md
CHANGED
|
@@ -99,7 +99,7 @@ sha256 to this table, and extend the `_ALLOW` map in `szl3d_holographic.py`). Do
|
|
| 99 |
| Lib | Owner (dev) | Pinned version | Upstream build to vendor | Target path |
|
| 100 |
|---|---|---|---|---|
|
| 101 |
| ~~deck.gl~~ | ~~Dev1 (energy), Dev9~~ | `deck.gl@9.0.38` | **DONE — vendored by Dev1** (see "VENDORED NOW (Dev1 energy PR)" above) | `/static/3d/vendor/deck.gl/dist.min.js` |
|
| 102 |
-
| CesiumJS | Dev4 (counter-uas) | `cesium@1.123` |
|
| 103 |
| 3d-force-graph | Dev2, Dev5 | (reuse) `static-vendor/3d-force-graph.min.js` | already vendored — served at `/vendor/3d-force-graph.min.js` | (reuse) |
|
| 104 |
|
| 105 |
When a dev vendors one of these:
|
|
@@ -109,6 +109,38 @@ When a dev vendors one of these:
|
|
| 109 |
subtrees like Cesium's `Workers/`, extend `_serve_3d` to allow that prefix).
|
| 110 |
4. Re-run the selftest harness — the no-CDN grep must stay green.
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
## License notice
|
| 113 |
|
| 114 |
three.js r170 is MIT (Copyright © 2010-2024 three.js authors). The license text is
|
|
|
|
| 99 |
| Lib | Owner (dev) | Pinned version | Upstream build to vendor | Target path |
|
| 100 |
|---|---|---|---|---|
|
| 101 |
| ~~deck.gl~~ | ~~Dev1 (energy), Dev9~~ | `deck.gl@9.0.38` | **DONE — vendored by Dev1** (see "VENDORED NOW (Dev1 energy PR)" above) | `/static/3d/vendor/deck.gl/dist.min.js` |
|
| 102 |
+
| ~~CesiumJS~~ | ~~Dev4 (counter-uas)~~ | ~~`cesium@1.123`~~ | **NOT vendored — Dev4 took the three.js-globe escape hatch (see §Dev4 below).** If a future surface needs the full Cesium globe (3D Terrain / 3D Tiles), vendor `cesium@1.123` `Build/Cesium/{Cesium.js,Workers,Assets,Widgets}` under `/static/3d/vendor/cesium/` and extend `_serve_3d` to allow that prefix. | `/static/3d/vendor/cesium/` |
|
| 103 |
| 3d-force-graph | Dev2, Dev5 | (reuse) `static-vendor/3d-force-graph.min.js` | already vendored — served at `/vendor/3d-force-graph.min.js` | (reuse) |
|
| 104 |
|
| 105 |
When a dev vendors one of these:
|
|
|
|
| 109 |
subtrees like Cesium's `Workers/`, extend `_serve_3d` to allow that prefix).
|
| 110 |
4. Re-run the selftest harness — the no-CDN grep must stay green.
|
| 111 |
|
| 112 |
+
## §Dev4 — Counter-UAS / killinchu surface: vendoring decisions
|
| 113 |
+
|
| 114 |
+
The Dev0 contract (§6) offered Dev4 an explicit escape hatch: vendor full CesiumJS@1.123
|
| 115 |
+
**OR** implement a three.js globe (textured sphere + lat/long track plotting). **Dev4 chose
|
| 116 |
+
the three.js globe.** Rationale:
|
| 117 |
+
|
| 118 |
+
- **0 new MB, 0 new CDN risk.** Reuses the already-vendored three.js r170 ESM build above.
|
| 119 |
+
Full Cesium is ~3 MB JS + a `Workers/`+`Assets/`+`Widgets/` subtree (hundreds of files)
|
| 120 |
+
and a separate `CESIUM_BASE_URL` serving contract — heavy for one surface PR.
|
| 121 |
+
- **Sufficient for the technique.** The surface needs a globe + lat/long track entities +
|
| 122 |
+
restricted-airspace SDF volumes + a radar sweep cone + a signed-verdict beam. A procedural
|
| 123 |
+
graticule sphere with `llToVec(lat,lon,alt)` plotting covers all of these without Cesium's
|
| 124 |
+
terrain/imagery tile pipeline. (If a later surface needs real 3D Terrain or 3D Tiles, the
|
| 125 |
+
Cesium TODO row above is preserved for that work.)
|
| 126 |
+
- **No new `_serve_3d` allow-prefix needed** — everything is served by the existing
|
| 127 |
+
`/static/3d/{path}` route + the r170 importmap.
|
| 128 |
+
|
| 129 |
+
### Data vendored by Dev4 (real, not fabricated)
|
| 130 |
+
|
| 131 |
+
| Path (under `/static/3d/`) | What | Source | Notes |
|
| 132 |
+
|---|---|---|---|
|
| 133 |
+
| `surfaces/data/drones_db.json` | 53 verified drone fingerprints | killinchu repo `drones_db.json` (verified count = 53) | killinchu does not expose this as a JSON HTTP route (its root path serves the Cesium SPA); the surface loads the repo's own DB same-origin. Vendored verbatim, not fabricated. |
|
| 134 |
+
|
| 135 |
+
### Live-data bridge (server-side, not a vendored lib)
|
| 136 |
+
|
| 137 |
+
The Counter-UAS surface wires to REAL killinchu live data via a same-origin proxy
|
| 138 |
+
(`szl_counter_uas_proxy.py`, registered in `serve.py`) under `/api/a11oy/v1/counter-uas/*`
|
| 139 |
+
→ the killinchu Space (`https://szlholdings-killinchu.hf.space`). This is a server-side
|
| 140 |
+
forward (no browser CORS, no CDN script). It degrades gracefully (`{"degraded":true}`) so
|
| 141 |
+
`szl3d_live` renders the honest DEGRADED state. killinchu **senses and evidences; it does
|
| 142 |
+
not defeat** — the surface shows detect/track/classify/evidence + the signed verdict only.
|
| 143 |
+
|
| 144 |
## License notice
|
| 145 |
|
| 146 |
three.js r170 is MIT (Copyright © 2010-2024 three.js authors). The license text is
|