betterwithage commited on
Commit
7e18852
·
verified ·
1 Parent(s): 048a129

chore(sync): mirror front-door files to Space (hf-sync)

Browse files

Automated front-door sync from szl-holdings/a11oy main via hf-sync.
Added/updated: cathedral.html, cathedral_genius.html, console/docs.html, console/index.html, console/pricing.html, console/throne-room.html, console/throne-room.js, pages/api-keys.html, pages/audit.html, pages/ayni.html, pages/brain-dual.html, pages/brain-jack.html, pages/brain.html, pages/chaski.html, pages/codex-kernel.html, pages/company.html, pages/compliance.html, pages/console.html, pages/counter-uas.html, pages/cued-engagement.html, pages/docs.html, pages/evidence.html, pages/gap-report.html, pages/harvest.html, pages/hatun-mcp.html, pages/hub.html, pages/integrations.html, pages/landing.html, pages/mesh.html, pages/observability.html, pages/operator_organ.html, pages/pricing.html, pages/run-all.html, pages/sdk.html, pages/security.html, pages/status.html, pages/substrate.html, pages/superpowers.html, pages/throne-room.html, pages/throne-room.js, pages/uds.html, pages/upgrades.html, pages/wallpa.html, pages/warhacker.html, pages/wasi-rikuq.html, pages/wires.html, static/a11oy_cathedral.js, static/cathedral_app.js, static/shared/szl_codename_sanitizer.js, static/shared/szl_holo3d.js, static/shared/szl_label_engine.js, static/shared/szl_receipt_cosign.js
Deleted (gone from GitHub main): (none)

Keeps the served front-door (pages/*.html, console/*.html) identical
to GitHub main so an HF factory rebuild never drops a GitHub edit or
keeps serving a page that was deleted on GitHub.

Files changed (1) hide show
  1. static/shared/szl_holo3d.js +723 -0
static/shared/szl_holo3d.js ADDED
@@ -0,0 +1,723 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* SPDX-License-Identifier: Apache-2.0
2
+ * © 2026 SZL Holdings. szl_holo3d.js — the SZL 3D / HOLOGRAPHIC SUBSTRATE KIT.
3
+ * =====================================================================================
4
+ * THE reusable holographic substrate every SZL frontier surface renders on.
5
+ * Vendored locally, served at /static/shared/szl_holo3d.js — ZERO RUNTIME CDN.
6
+ * Byte-identical across a11oy and killinchu (shared-source drift guard enforced).
7
+ *
8
+ * DESIGN INTENT (Surface 10 of FRONTIER_RESEARCH.md): a defense-grade dark-glass
9
+ * holographic scene shell with a node/edge GRAPH renderer (proof-DAGs, factory
10
+ * graphs, ontology organisms), a lightweight GLOBE renderer, a Λ-driven TRUST
11
+ * SPHERE primitive, a SIGNED-PULSE edge animation (light flows along an edge when
12
+ * a receipt is signed), and a time-slider replay hook.
13
+ *
14
+ * RENDERER STRATEGY (honest, air-gap-clean, 0-CDN):
15
+ * - SELF-CONTAINED: the kit ships its OWN minimal WebGL2 renderer (no hard
16
+ * dependency on three.js). This guarantees byte-identical behaviour on BOTH
17
+ * apps regardless of whether a heavy 3D vendor bundle is present.
18
+ * - WebGL2 BASELINE: feature-detected; the default render path.
19
+ * - WebGPU PROGRESSIVE ENHANCEMENT: feature-detected (navigator.gpu). When
20
+ * present we expose it via caps.webgpu = true and (ROADMAP) a WGSL compute
21
+ * layout path; we DO NOT claim a WebGPU pipeline we have not built — the
22
+ * baseline WebGL2 path renders identically. Honestly labelled ROADMAP.
23
+ * - CPU / OLD-GPU FALLBACK: if WebGL2 is unavailable the kit renders an HONEST
24
+ * 2D <canvas> projection (never a blank canvas) with a visible
25
+ * "2D FALLBACK" badge. Graph, trust sphere, globe and signed-pulse all have
26
+ * a 2D path so the surface is always legible.
27
+ *
28
+ * DOCTRINE: 0 runtime CDN; Λ = Conjecture 1 (a bounded score < 1.0, NOT a
29
+ * theorem); trust < 100%; honest SAMPLE labels on demo data; never claim live
30
+ * data; 0 visible character codenames. window.SZLLabels drives honest badges.
31
+ *
32
+ * Citations (build references, not runtime calls):
33
+ * three.js / R3F — https://github.com/pmndrs/react-three-fiber
34
+ * deck.gl (vis.gl)— https://github.com/visgl/deck.gl
35
+ * CesiumJS — https://github.com/CesiumGS/cesium
36
+ *
37
+ * Public API: window.SZLHolo
38
+ * SZLHolo.version
39
+ * SZLHolo.capabilities() -> { webgl2, webgpu, mode, dpr }
40
+ * SZLHolo.createScene(mountEl, opts) -> Scene
41
+ * Scene.addGraph(spec) -> GraphHandle (nodes/edges DAG)
42
+ * Scene.addGlobe(spec) -> GlobeHandle (lightweight globe)
43
+ * Scene.addTrustSphere(spec) -> TrustSphereHandle (morph by Λ)
44
+ * Scene.signPulse(edgeId, opts) -> animate light along an edge
45
+ * Scene.timeSlider(opts) -> replay hook (onTime callback)
46
+ * Scene.setLambda(value) -> drive global Λ (< 1.0)
47
+ * Scene.start() / Scene.stop() / Scene.dispose()
48
+ * =====================================================================================
49
+ */
50
+ (function (root, factory) {
51
+ "use strict";
52
+ var mod = factory();
53
+ if (root) { root.SZLHolo = mod; }
54
+ if (typeof module !== "undefined" && module.exports) { module.exports = mod; }
55
+ })(typeof self !== "undefined" ? self : (typeof window !== "undefined" ? window : null), function () {
56
+ "use strict";
57
+
58
+ var VERSION = "1.0.0";
59
+
60
+ /* ----------------------------------------------------------------------- *
61
+ * Honest palette — Palantir/Datadog-style dark glass, defense-grade.
62
+ * ----------------------------------------------------------------------- */
63
+ var PALETTE = {
64
+ bg0: [0.020, 0.027, 0.043], // deep space navy
65
+ bg1: [0.043, 0.063, 0.098], // glass tint
66
+ grid: "rgba(90,140,200,0.10)",
67
+ node: "#7fd4ff",
68
+ nodeLock: "#ffd479",
69
+ edge: "rgba(120,170,230,0.45)",
70
+ pulse: "#aef6ff",
71
+ text: "#cfe6ff",
72
+ ok: "#46d39a",
73
+ warn: "#ffcf5c",
74
+ bad: "#ff6b6b"
75
+ };
76
+
77
+ /* Λ -> colour. Λ is a bounded score in [0,1) (Conjecture 1; NOT a theorem).
78
+ * Lower Λ = healthier (green); higher = riskier (red). Trust is never 100%. */
79
+ function lambdaColor(lam) {
80
+ lam = clamp01(lam == null ? 0.5 : lam);
81
+ // green (low) -> amber (mid) -> red (high)
82
+ var r, g, b;
83
+ if (lam < 0.5) { var t = lam / 0.5; r = lerp(70, 255, t); g = lerp(211, 207, t); b = lerp(154, 92, t); }
84
+ else { var u = (lam - 0.5) / 0.5; r = lerp(255, 255, u); g = lerp(207, 107, u); b = lerp(92, 107, u); }
85
+ return "rgb(" + (r | 0) + "," + (g | 0) + "," + (b | 0) + ")";
86
+ }
87
+ function clamp01(x) { return x < 0 ? 0 : (x > 1 ? 1 : x); }
88
+ function lerp(a, b, t) { return a + (b - a) * t; }
89
+
90
+ /* ----------------------------------------------------------------------- *
91
+ * Capability detection. WebGL2 baseline, WebGPU enhancement, 2D fallback.
92
+ * ----------------------------------------------------------------------- */
93
+ function detectCaps() {
94
+ var webgl2 = false, webgpu = false;
95
+ try {
96
+ var c = document.createElement("canvas");
97
+ var gl = c.getContext("webgl2");
98
+ webgl2 = !!gl;
99
+ if (gl && gl.getExtension) { /* probe ok */ }
100
+ } catch (e) { webgl2 = false; }
101
+ try { webgpu = (typeof navigator !== "undefined" && !!navigator.gpu); } catch (e2) { webgpu = false; }
102
+ var dpr = (typeof window !== "undefined" && window.devicePixelRatio) ? Math.min(window.devicePixelRatio, 2) : 1;
103
+ var mode = webgl2 ? "webgl2" : "2d";
104
+ return { webgl2: webgl2, webgpu: webgpu, mode: mode, dpr: dpr,
105
+ // WebGPU is feature-detected but the compute-layout pipeline is ROADMAP;
106
+ // we honestly do NOT switch the render path to an unbuilt WebGPU path.
107
+ webgpuPipeline: false };
108
+ }
109
+
110
+ /* ----------------------------------------------------------------------- *
111
+ * Honest label badge helper (reuses window.SZLLabels when present).
112
+ * ----------------------------------------------------------------------- */
113
+ function badgeHTML(key, text) {
114
+ try {
115
+ if (typeof window !== "undefined" && window.SZLLabels && window.SZLLabels.badgeHTML) {
116
+ return window.SZLLabels.badgeHTML(key, { text: text });
117
+ }
118
+ } catch (e) {}
119
+ // honest inline fallback if the shared label engine is not loaded
120
+ var label = text || key;
121
+ return '<span class="szl-holo-badge" data-k="' + esc(key) + '">' + esc(label) + "</span>";
122
+ }
123
+ function esc(s) {
124
+ return String(s == null ? "" : s)
125
+ .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
126
+ .replace(/"/g, "&quot;").replace(/'/g, "&#39;");
127
+ }
128
+
129
+ /* small style sheet injected once (self-styled, 0 external font/css) */
130
+ var STYLE_ID = "szl-holo3d-style";
131
+ function ensureStyle(doc) {
132
+ if (!doc || doc.getElementById(STYLE_ID)) { return; }
133
+ var css =
134
+ ".szl-holo-root{position:relative;width:100%;height:100%;min-height:240px;" +
135
+ "background:radial-gradient(120% 120% at 50% 0%,#0a1326 0%,#050a14 60%,#03060d 100%);" +
136
+ "overflow:hidden;border-radius:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:" + PALETTE.text + ";}" +
137
+ ".szl-holo-root canvas{display:block;width:100%;height:100%;}" +
138
+ ".szl-holo-hud{position:absolute;top:8px;left:10px;z-index:5;font-size:11px;letter-spacing:.04em;" +
139
+ "text-shadow:0 1px 3px rgba(0,0,0,.8);pointer-events:none;}" +
140
+ ".szl-holo-hud .row{display:flex;gap:6px;align-items:center;margin-bottom:3px;flex-wrap:wrap;}" +
141
+ ".szl-holo-badge{display:inline-block;padding:1px 6px;border-radius:6px;font-size:10px;font-weight:600;" +
142
+ "background:rgba(70,150,230,.18);border:1px solid rgba(120,180,240,.35);color:#bfe0ff;}" +
143
+ ".szl-holo-badge[data-k='SAMPLE']{background:rgba(90,160,240,.16);}" +
144
+ ".szl-holo-badge[data-k='2DFALLBACK']{background:rgba(255,180,90,.18);border-color:rgba(255,190,110,.4);color:#ffd9a8;}" +
145
+ ".szl-holo-lambda{position:absolute;bottom:8px;left:10px;z-index:5;font-size:11px;pointer-events:none;}" +
146
+ ".szl-holo-slider{position:absolute;bottom:8px;right:10px;left:auto;z-index:6;width:42%;max-width:260px;}" +
147
+ ".szl-holo-slider input{width:100%;}" +
148
+ ".szl-holo-slider label{font-size:10px;display:block;margin-bottom:2px;opacity:.85;}";
149
+ var el = doc.createElement("style");
150
+ el.id = STYLE_ID;
151
+ el.textContent = css;
152
+ (doc.head || doc.documentElement).appendChild(el);
153
+ }
154
+
155
+ /* ----------------------------------------------------------------------- *
156
+ * Minimal 3x perspective math (no matrix lib needed for our point cloud).
157
+ * We project 3D points to 2D with a simple rotating camera; both the WebGL2
158
+ * path and the 2D fallback use the SAME projection so visuals match.
159
+ * ----------------------------------------------------------------------- */
160
+ function project(p, cam, w, h) {
161
+ // rotate around Y then X
162
+ var cy = Math.cos(cam.yaw), sy = Math.sin(cam.yaw);
163
+ var cx = Math.cos(cam.pitch), sx = Math.sin(cam.pitch);
164
+ var x = p[0], y = p[1], z = p[2];
165
+ var x1 = x * cy + z * sy;
166
+ var z1 = -x * sy + z * cy;
167
+ var y1 = y * cx - z1 * sx;
168
+ var z2 = y * sx + z1 * cx;
169
+ var d = cam.dist;
170
+ var zc = z2 + d;
171
+ var f = (cam.fov * Math.min(w, h)) / (zc <= 0.05 ? 0.05 : zc);
172
+ return {
173
+ x: w / 2 + x1 * f,
174
+ y: h / 2 - y1 * f,
175
+ z: zc,
176
+ s: f / cam.fov // relative scale for size/alpha
177
+ };
178
+ }
179
+
180
+ /* ----------------------------------------------------------------------- *
181
+ * Scene — the holographic shell + all primitives.
182
+ * ----------------------------------------------------------------------- */
183
+ function Scene(mountEl, opts) {
184
+ opts = opts || {};
185
+ this.mount = mountEl;
186
+ this.doc = (mountEl && mountEl.ownerDocument) || (typeof document !== "undefined" ? document : null);
187
+ this.caps = detectCaps();
188
+ this.opts = opts;
189
+ this.lambda = (typeof opts.lambda === "number") ? clamp01(opts.lambda) : 0.42;
190
+ this.cam = { yaw: 0.6, pitch: -0.35, dist: 3.2, fov: 1.7, autoRotate: opts.autoRotate !== false };
191
+ this.graphs = [];
192
+ this.globes = [];
193
+ this.spheres = [];
194
+ this.pulses = [];
195
+ this._running = false;
196
+ this._raf = null;
197
+ this._lastT = 0;
198
+ this._t0 = (typeof performance !== "undefined" ? performance.now() : Date.now());
199
+ this._replay = null; // { from,to,value,playing }
200
+ this._build();
201
+ }
202
+
203
+ Scene.prototype._build = function () {
204
+ var doc = this.doc;
205
+ ensureStyle(doc);
206
+ var root = doc.createElement("div");
207
+ root.className = "szl-holo-root";
208
+ root.setAttribute("data-szl-holo", VERSION);
209
+ this.root = root;
210
+
211
+ var canvas = doc.createElement("canvas");
212
+ canvas.setAttribute("aria-label", "SZL holographic scene");
213
+ this.canvas = canvas;
214
+ root.appendChild(canvas);
215
+
216
+ // HUD with honest mode + SAMPLE badge (demo data is SAMPLE)
217
+ var hud = doc.createElement("div");
218
+ hud.className = "szl-holo-hud";
219
+ var modeBadge = this.caps.webgl2 ? badgeHTML("EXPERIMENTAL", "WEBGL2") : badgeHTML("2DFALLBACK", "2D FALLBACK");
220
+ var gpuBadge = this.caps.webgpu ? badgeHTML("EXPERIMENTAL", "WEBGPU·ROADMAP") : "";
221
+ hud.innerHTML =
222
+ '<div class="row">' + (this.opts.title ? '<strong>' + esc(this.opts.title) + '</strong>' : '') +
223
+ ' ' + modeBadge + ' ' + gpuBadge + '</div>' +
224
+ '<div class="row" data-szl-holo-labels>' + badgeHTML("SAMPLE", "SAMPLE DATA") + '</div>';
225
+ this.hud = hud;
226
+ root.appendChild(hud);
227
+
228
+ // Λ readout (Conjecture 1, < 1.0) — never claims a theorem, never 100%.
229
+ var lam = doc.createElement("div");
230
+ lam.className = "szl-holo-lambda";
231
+ this.lamEl = lam;
232
+ root.appendChild(lam);
233
+ this._renderLambdaReadout();
234
+
235
+ this.mount.appendChild(root);
236
+
237
+ // renderer init
238
+ if (this.caps.webgl2) {
239
+ this._initGL();
240
+ }
241
+ this.ctx2d = canvas.getContext("2d"); // always available for HUD/2D fallback layer
242
+ this._resize();
243
+ var self = this;
244
+ this._onResize = function () { self._resize(); };
245
+ if (typeof window !== "undefined") { window.addEventListener("resize", this._onResize); }
246
+ };
247
+
248
+ Scene.prototype._renderLambdaReadout = function () {
249
+ var col = lambdaColor(this.lambda);
250
+ this.lamEl.innerHTML =
251
+ 'Λ <span style="color:' + col + ';font-weight:700">' + this.lambda.toFixed(3) + '</span> ' +
252
+ '<span style="opacity:.6">· Conjecture 1 (&lt;1.0) · trust &lt;100%</span>';
253
+ };
254
+
255
+ Scene.prototype._initGL = function () {
256
+ var gl = null;
257
+ try { gl = this.canvas.getContext("webgl2", { antialias: true, alpha: false, premultipliedAlpha: false }); }
258
+ catch (e) { gl = null; }
259
+ if (!gl) { this.caps.webgl2 = false; this.caps.mode = "2d"; return; }
260
+ this.gl = gl;
261
+ // point + line shader (instanced-free, simple gl.POINTS / gl.LINES)
262
+ var vs =
263
+ "#version 300 es\n" +
264
+ "layout(location=0) in vec3 aPos;\n" +
265
+ "layout(location=1) in vec4 aColor;\n" +
266
+ "layout(location=2) in float aSize;\n" +
267
+ "uniform mat4 uMVP;\n" +
268
+ "out vec4 vColor;\n" +
269
+ "void main(){ gl_Position = uMVP * vec4(aPos,1.0); gl_PointSize = aSize; vColor = aColor; }\n";
270
+ var fs =
271
+ "#version 300 es\nprecision highp float;\n" +
272
+ "in vec4 vColor; out vec4 frag;\n" +
273
+ "void main(){\n" +
274
+ " vec2 d = gl_PointCoord - vec2(0.5);\n" +
275
+ " float r = length(d);\n" +
276
+ " float a = smoothstep(0.5,0.1,r);\n" +
277
+ " frag = vec4(vColor.rgb, vColor.a * max(a, 0.18));\n" +
278
+ "}\n";
279
+ this.prog = this._linkProgram(gl, vs, fs);
280
+ this.uMVP = gl.getUniformLocation(this.prog, "uMVP");
281
+ this.vbo = gl.createBuffer();
282
+ gl.enable(gl.BLEND);
283
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE); // additive glow
284
+ };
285
+
286
+ Scene.prototype._linkProgram = function (gl, vsSrc, fsSrc) {
287
+ function sh(type, src) {
288
+ var s = gl.createShader(type);
289
+ gl.shaderSource(s, src); gl.compileShader(s);
290
+ if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
291
+ // honest degrade: drop to 2D rather than throw
292
+ return null;
293
+ }
294
+ return s;
295
+ }
296
+ var v = sh(gl.VERTEX_SHADER, vsSrc), f = sh(gl.FRAGMENT_SHADER, fsSrc);
297
+ if (!v || !f) { this.caps.webgl2 = false; this.caps.mode = "2d"; return null; }
298
+ var p = gl.createProgram();
299
+ gl.attachShader(p, v); gl.attachShader(p, f); gl.linkProgram(p);
300
+ if (!gl.getProgramParameter(p, gl.LINK_STATUS)) { this.caps.webgl2 = false; this.caps.mode = "2d"; return null; }
301
+ return p;
302
+ };
303
+
304
+ Scene.prototype._resize = function () {
305
+ var dpr = this.caps.dpr;
306
+ var w = this.root.clientWidth || 640, h = this.root.clientHeight || 360;
307
+ this.W = w; this.H = h;
308
+ this.canvas.width = Math.max(1, (w * dpr) | 0);
309
+ this.canvas.height = Math.max(1, (h * dpr) | 0);
310
+ if (this.gl) { this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); }
311
+ };
312
+
313
+ /* -------- primitives ------------------------------------------------ */
314
+
315
+ // GRAPH: { nodes:[{id,pos:[x,y,z],label,locked,lambda}], edges:[{id,from,to}] }
316
+ Scene.prototype.addGraph = function (spec) {
317
+ spec = spec || {};
318
+ var nodes = (spec.nodes || []).map(function (n, i) {
319
+ return {
320
+ id: n.id != null ? n.id : ("n" + i),
321
+ pos: n.pos || sphereLayout(i, (spec.nodes || []).length, 1.1),
322
+ label: n.label || "",
323
+ locked: !!n.locked,
324
+ lambda: (typeof n.lambda === "number") ? clamp01(n.lambda) : null
325
+ };
326
+ });
327
+ var index = {}; nodes.forEach(function (n, i) { index[n.id] = i; });
328
+ var edges = (spec.edges || []).map(function (e, i) {
329
+ return { id: e.id != null ? e.id : ("e" + i), from: index[e.from], to: index[e.to],
330
+ fromId: e.from, toId: e.to };
331
+ }).filter(function (e) { return e.from != null && e.to != null; });
332
+ var h = { kind: "graph", nodes: nodes, edges: edges, index: index };
333
+ this.graphs.push(h);
334
+ return h;
335
+ };
336
+
337
+ // GLOBE: lightweight wireframe sphere + lat/long graticule + optional points.
338
+ // Honest: this is a procedural SAMPLE globe, NOT a CesiumJS tile basemap.
339
+ // A real basemap (CesiumJS local tiles / three-globe) is ROADMAP and must be
340
+ // vendored 0-CDN before any tile call is made.
341
+ Scene.prototype.addGlobe = function (spec) {
342
+ spec = spec || {};
343
+ var pts = (spec.points || []).map(function (p, i) {
344
+ var la = (p.lat || 0) * Math.PI / 180, lo = (p.lon || 0) * Math.PI / 180;
345
+ var r = 1.0;
346
+ return { pos: [r * Math.cos(la) * Math.cos(lo), r * Math.sin(la), r * Math.cos(la) * Math.sin(lo)],
347
+ label: p.label || "", lambda: (typeof p.lambda === "number") ? clamp01(p.lambda) : 0.4 };
348
+ });
349
+ var h = { kind: "globe", radius: spec.radius || 1.0, points: pts,
350
+ sample: true, basemap: "PROCEDURAL_SAMPLE" };
351
+ this.globes.push(h);
352
+ return h;
353
+ };
354
+
355
+ // TRUST SPHERE: a sphere whose radius/colour morph with Λ (Conjecture 1).
356
+ // Lower Λ -> larger, greener (more trust); higher Λ -> smaller, redder.
357
+ Scene.prototype.addTrustSphere = function (spec) {
358
+ spec = spec || {};
359
+ var h = { kind: "sphere",
360
+ center: spec.center || [0, 0, 0],
361
+ baseR: spec.radius || 0.6,
362
+ lambda: (typeof spec.lambda === "number") ? clamp01(spec.lambda) : this.lambda,
363
+ label: spec.label || "TRUST",
364
+ segs: spec.segments || 16 };
365
+ this.spheres.push(h);
366
+ return h;
367
+ };
368
+
369
+ // SIGNED-PULSE: animate a light packet flowing along an edge (receipt signed).
370
+ Scene.prototype.signPulse = function (edgeRef, opts) {
371
+ opts = opts || {};
372
+ var graph = this.graphs[0];
373
+ var edge = null;
374
+ if (graph) {
375
+ for (var i = 0; i < graph.edges.length; i++) {
376
+ if (graph.edges[i].id === edgeRef || i === edgeRef) { edge = graph.edges[i]; break; }
377
+ }
378
+ }
379
+ if (!edge && graph && graph.edges.length) { edge = graph.edges[0]; }
380
+ if (!edge) { return null; }
381
+ var p = { graph: graph, edge: edge, t: 0, dur: opts.duration || 1200,
382
+ color: opts.color || PALETTE.pulse, started: this._now(), loop: !!opts.loop };
383
+ this.pulses.push(p);
384
+ return p;
385
+ };
386
+
387
+ // TIME-SLIDER replay hook. Injects a slider; calls opts.onTime(normalized,absolute).
388
+ Scene.prototype.timeSlider = function (opts) {
389
+ opts = opts || {};
390
+ var doc = this.doc, self = this;
391
+ var box = doc.createElement("div");
392
+ box.className = "szl-holo-slider";
393
+ var from = opts.from != null ? opts.from : 0;
394
+ var to = opts.to != null ? opts.to : 100;
395
+ box.innerHTML = '<label>REPLAY ' + badgeHTML("SAMPLE", "SAMPLE") + '</label>' +
396
+ '<input type="range" min="0" max="1000" value="1000" step="1" aria-label="time replay">';
397
+ var input = box.querySelector("input");
398
+ input.addEventListener("input", function () {
399
+ var n = (+input.value) / 1000;
400
+ self._replay = { from: from, to: to, value: from + (to - from) * n, norm: n };
401
+ if (typeof opts.onTime === "function") { opts.onTime(n, self._replay.value); }
402
+ });
403
+ this.root.appendChild(box);
404
+ this._replay = { from: from, to: to, value: to, norm: 1 };
405
+ return { el: box, set: function (n) { input.value = String(Math.round(clamp01(n) * 1000)); input.dispatchEvent(new Event("input")); } };
406
+ };
407
+
408
+ Scene.prototype.setLambda = function (v) {
409
+ this.lambda = clamp01(v);
410
+ // trust sphere(s) without an explicit lambda track the global Λ
411
+ for (var i = 0; i < this.spheres.length; i++) {
412
+ if (this.spheres[i]._tracksGlobal !== false && this.spheres[i].lambda == null) {
413
+ this.spheres[i].lambda = this.lambda;
414
+ }
415
+ }
416
+ this._renderLambdaReadout();
417
+ return this.lambda;
418
+ };
419
+
420
+ Scene.prototype._now = function () { return (typeof performance !== "undefined" ? performance.now() : Date.now()); };
421
+
422
+ /* -------- run loop -------------------------------------------------- */
423
+ Scene.prototype.start = function () {
424
+ if (this._running) { return; }
425
+ this._running = true;
426
+ var self = this;
427
+ function loop(t) {
428
+ if (!self._running) { return; }
429
+ self._frame(t || self._now());
430
+ self._raf = (typeof requestAnimationFrame !== "undefined")
431
+ ? requestAnimationFrame(loop) : setTimeout(function () { loop(self._now()); }, 33);
432
+ }
433
+ this._raf = (typeof requestAnimationFrame !== "undefined")
434
+ ? requestAnimationFrame(loop) : setTimeout(function () { loop(self._now()); }, 33);
435
+ return this;
436
+ };
437
+ Scene.prototype.stop = function () {
438
+ this._running = false;
439
+ if (this._raf != null) {
440
+ if (typeof cancelAnimationFrame !== "undefined") { cancelAnimationFrame(this._raf); }
441
+ else { clearTimeout(this._raf); }
442
+ this._raf = null;
443
+ }
444
+ return this;
445
+ };
446
+
447
+ Scene.prototype._frame = function (t) {
448
+ var dt = this._lastT ? (t - this._lastT) : 16;
449
+ this._lastT = t;
450
+ if (this.cam.autoRotate) { this.cam.yaw += dt * 0.00018; }
451
+ this._drawCount = 0;
452
+ if (this.caps.webgl2 && this.gl && this.prog) {
453
+ this._renderGL(t);
454
+ }
455
+ // 2D overlay always draws labels/pulses/slider markers; in fallback it draws everything.
456
+ this._render2D(t);
457
+ // expose a draw count so verifiers can confirm >0 draws
458
+ this.root.setAttribute("data-szl-draws", String(this._drawCount));
459
+ };
460
+
461
+ /* Build the full point/line set in scene space (shared by GL + 2D). */
462
+ Scene.prototype._collectGeometry = function (t) {
463
+ var pts = []; // {pos, color, size, label}
464
+ var lines = []; // {a, b, color}
465
+ var self = this;
466
+ var replayN = this._replay ? this._replay.norm : 1;
467
+
468
+ // graphs
469
+ this.graphs.forEach(function (g) {
470
+ g.edges.forEach(function (e) {
471
+ var a = g.nodes[e.from], b = g.nodes[e.to];
472
+ if (!a || !b) { return; }
473
+ lines.push({ a: a.pos, b: b.pos, color: PALETTE.edge });
474
+ });
475
+ g.nodes.forEach(function (n, i) {
476
+ // replay: reveal nodes progressively by index
477
+ var reveal = (i / Math.max(1, g.nodes.length - 1)) <= (replayN + 0.0001);
478
+ if (!reveal) { return; }
479
+ var col = n.locked ? PALETTE.nodeLock : (n.lambda != null ? lambdaColor(n.lambda) : PALETTE.node);
480
+ pts.push({ pos: n.pos, color: col, size: n.locked ? 16 : 12, label: n.label });
481
+ });
482
+ });
483
+
484
+ // globes
485
+ this.globes.forEach(function (gl) {
486
+ var R = gl.radius, rings = 10, seg = 22;
487
+ for (var i = 1; i < rings; i++) {
488
+ var lat = (i / rings - 0.5) * Math.PI;
489
+ var prev = null;
490
+ for (var j = 0; j <= seg; j++) {
491
+ var lon = (j / seg) * Math.PI * 2;
492
+ var p = [R * Math.cos(lat) * Math.cos(lon), R * Math.sin(lat), R * Math.cos(lat) * Math.sin(lon)];
493
+ if (prev) { lines.push({ a: prev, b: p, color: "rgba(110,160,220,0.18)" }); }
494
+ prev = p;
495
+ }
496
+ }
497
+ for (var k = 0; k < seg; k++) {
498
+ var lon2 = (k / seg) * Math.PI * 2; var prev2 = null;
499
+ for (var m = 0; m <= rings; m++) {
500
+ var lat2 = (m / rings - 0.5) * Math.PI;
501
+ var q = [R * Math.cos(lat2) * Math.cos(lon2), R * Math.sin(lat2), R * Math.cos(lat2) * Math.sin(lon2)];
502
+ if (prev2) { lines.push({ a: prev2, b: q, color: "rgba(110,160,220,0.12)" }); }
503
+ prev2 = q;
504
+ }
505
+ }
506
+ gl.points.forEach(function (p) {
507
+ pts.push({ pos: p.pos, color: lambdaColor(p.lambda), size: 14, label: p.label });
508
+ });
509
+ });
510
+
511
+ // trust spheres — radius morphs with Λ (lower Λ = larger/greener)
512
+ this.spheres.forEach(function (s) {
513
+ var lam = (s.lambda != null) ? s.lambda : self.lambda;
514
+ var R = s.baseR * (1.25 - 0.5 * lam); // morph
515
+ var col = lambdaColor(lam);
516
+ var rings = s.segs, seg = s.segs;
517
+ for (var i = 0; i <= rings; i++) {
518
+ var lat = (i / rings - 0.5) * Math.PI; var prev = null;
519
+ for (var j = 0; j <= seg; j++) {
520
+ var lon = (j / seg) * Math.PI * 2;
521
+ var p = [s.center[0] + R * Math.cos(lat) * Math.cos(lon),
522
+ s.center[1] + R * Math.sin(lat),
523
+ s.center[2] + R * Math.cos(lat) * Math.sin(lon)];
524
+ if (prev) { lines.push({ a: prev, b: p, color: hexA(col, 0.30) }); }
525
+ prev = p;
526
+ }
527
+ }
528
+ pts.push({ pos: s.center, color: col, size: 10 + 14 * (1 - lam), label: s.label + " Λ" + lam.toFixed(2) });
529
+ });
530
+
531
+ // signed pulses — moving light packet along an edge
532
+ var alive = [];
533
+ this.pulses.forEach(function (p) {
534
+ var age = self._now() - p.started;
535
+ var f = age / p.dur;
536
+ if (f >= 1) { if (p.loop) { p.started = self._now(); f = 0; } else { return; } }
537
+ var a = p.graph.nodes[p.edge.from], b = p.graph.nodes[p.edge.to];
538
+ if (!a || !b) { return; }
539
+ var pos = [lerp(a.pos[0], b.pos[0], f), lerp(a.pos[1], b.pos[1], f), lerp(a.pos[2], b.pos[2], f)];
540
+ pts.push({ pos: pos, color: p.color, size: 18, label: "" , pulse: true });
541
+ // glowing trail
542
+ for (var q = 1; q <= 4; q++) {
543
+ var tf = Math.max(0, f - q * 0.05);
544
+ pts.push({ pos: [lerp(a.pos[0], b.pos[0], tf), lerp(a.pos[1], b.pos[1], tf), lerp(a.pos[2], b.pos[2], tf)],
545
+ color: hexA(p.color, 0.5 - q * 0.1), size: 14 - q * 2, pulse: true });
546
+ }
547
+ alive.push(p);
548
+ });
549
+ this.pulses = alive;
550
+
551
+ return { pts: pts, lines: lines };
552
+ };
553
+
554
+ Scene.prototype._renderGL = function (t) {
555
+ var gl = this.gl, geo = this._collectGeometry(t);
556
+ gl.clearColor(PALETTE.bg0[0], PALETTE.bg0[1], PALETTE.bg0[2], 1);
557
+ gl.clear(gl.COLOR_BUFFER_BIT);
558
+ var mvp = this._mvp();
559
+ gl.useProgram(this.prog);
560
+ gl.uniformMatrix4fv(this.uMVP, false, mvp);
561
+
562
+ // lines
563
+ var lineArr = [];
564
+ geo.lines.forEach(function (ln) {
565
+ var c = parseColor(ln.color);
566
+ lineArr.push(ln.a[0], ln.a[1], ln.a[2], c[0], c[1], c[2], c[3], 2.0);
567
+ lineArr.push(ln.b[0], ln.b[1], ln.b[2], c[0], c[1], c[2], c[3], 2.0);
568
+ });
569
+ this._drawArray(gl, lineArr, gl.LINES);
570
+ this._drawCount += geo.lines.length;
571
+
572
+ // points
573
+ var ptArr = [];
574
+ geo.pts.forEach(function (pt) {
575
+ var c = parseColor(pt.color);
576
+ ptArr.push(pt.pos[0], pt.pos[1], pt.pos[2], c[0], c[1], c[2], c[3], pt.size * 1.0);
577
+ });
578
+ this._drawArray(gl, ptArr, gl.POINTS);
579
+ this._drawCount += geo.pts.length;
580
+ this._lastGeo = geo; // for 2D label overlay
581
+ };
582
+
583
+ Scene.prototype._drawArray = function (gl, arr, mode) {
584
+ if (!arr.length) { return; }
585
+ var data = new Float32Array(arr);
586
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
587
+ gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);
588
+ var stride = 8 * 4;
589
+ gl.enableVertexAttribArray(0); gl.vertexAttribPointer(0, 3, gl.FLOAT, false, stride, 0);
590
+ gl.enableVertexAttribArray(1); gl.vertexAttribPointer(1, 4, gl.FLOAT, false, stride, 3 * 4);
591
+ gl.enableVertexAttribArray(2); gl.vertexAttribPointer(2, 1, gl.FLOAT, false, stride, 7 * 4);
592
+ gl.drawArrays(mode, 0, arr.length / 8);
593
+ };
594
+
595
+ // Build a perspective * view matrix consistent with our project() camera.
596
+ Scene.prototype._mvp = function () {
597
+ var cam = this.cam;
598
+ var aspect = this.canvas.width / Math.max(1, this.canvas.height);
599
+ var f = 1.0 / Math.tan(cam.fov / 2);
600
+ var near = 0.1, far = 100;
601
+ var proj = [
602
+ f / aspect, 0, 0, 0,
603
+ 0, f, 0, 0,
604
+ 0, 0, (far + near) / (near - far), -1,
605
+ 0, 0, (2 * far * near) / (near - far), 0
606
+ ];
607
+ var cy = Math.cos(cam.yaw), sy = Math.sin(cam.yaw);
608
+ var cx = Math.cos(cam.pitch), sx = Math.sin(cam.pitch);
609
+ var ry = [cy, 0, -sy, 0, 0, 1, 0, 0, sy, 0, cy, 0, 0, 0, 0, 1];
610
+ var rx = [1, 0, 0, 0, 0, cx, sx, 0, 0, -sx, cx, 0, 0, 0, 0, 1];
611
+ var trans = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -cam.dist, 1];
612
+ return mul(proj, mul(trans, mul(rx, ry)));
613
+ };
614
+
615
+ /* 2D layer: in fallback mode renders the full scene; otherwise draws labels. */
616
+ Scene.prototype._render2D = function (t) {
617
+ var ctx = this.ctx2d; if (!ctx) { return; }
618
+ var dpr = this.caps.dpr, w = this.canvas.width, h = this.canvas.height;
619
+ if (!this.caps.webgl2) {
620
+ // FULL 2D FALLBACK — never a blank canvas.
621
+ var grd = ctx.createRadialGradient(w / 2, h * 0.2, 10, w / 2, h * 0.2, h);
622
+ grd.addColorStop(0, "#0a1326"); grd.addColorStop(1, "#03060d");
623
+ ctx.fillStyle = grd; ctx.fillRect(0, 0, w, h);
624
+ // faint grid
625
+ ctx.strokeStyle = PALETTE.grid; ctx.lineWidth = 1;
626
+ for (var gx = 0; gx < w; gx += 40 * dpr) { ctx.beginPath(); ctx.moveTo(gx, 0); ctx.lineTo(gx, h); ctx.stroke(); }
627
+ for (var gy = 0; gy < h; gy += 40 * dpr) { ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(w, gy); ctx.stroke(); }
628
+ var geo = this._collectGeometry(t);
629
+ var self = this;
630
+ ctx.lineWidth = 1.4 * dpr;
631
+ geo.lines.forEach(function (ln) {
632
+ var pa = project(ln.a, self.cam, w, h), pb = project(ln.b, self.cam, w, h);
633
+ ctx.strokeStyle = ln.color; ctx.beginPath(); ctx.moveTo(pa.x, pa.y); ctx.lineTo(pb.x, pb.y); ctx.stroke();
634
+ self._drawCount++;
635
+ });
636
+ geo.pts.slice().sort(function (a, b) {
637
+ return project(b.pos, self.cam, w, h).z - project(a.pos, self.cam, w, h).z;
638
+ }).forEach(function (pt) {
639
+ var p = project(pt.pos, self.cam, w, h);
640
+ var r = Math.max(2, pt.size * 0.5 * dpr * p.s);
641
+ var glow = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 2.4);
642
+ glow.addColorStop(0, pt.color); glow.addColorStop(1, "rgba(0,0,0,0)");
643
+ ctx.fillStyle = glow; ctx.beginPath(); ctx.arc(p.x, p.y, r * 2.4, 0, Math.PI * 2); ctx.fill();
644
+ ctx.fillStyle = pt.color; ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, Math.PI * 2); ctx.fill();
645
+ if (pt.label) {
646
+ ctx.fillStyle = PALETTE.text; ctx.font = (11 * dpr) + "px ui-monospace,monospace";
647
+ ctx.fillText(pt.label, p.x + r + 3, p.y + 3);
648
+ }
649
+ self._drawCount++;
650
+ });
651
+ return;
652
+ }
653
+ // WebGL2 mode: overlay node labels on a transparent 2D layer is not possible
654
+ // on the same canvas (GL owns it). Labels are shown via the HUD; here we just
655
+ // keep the draw count honest from the GL pass. (No second canvas to stay 0-CDN
656
+ // and simple.)
657
+ };
658
+
659
+ Scene.prototype.dispose = function () {
660
+ this.stop();
661
+ if (typeof window !== "undefined" && this._onResize) { window.removeEventListener("resize", this._onResize); }
662
+ if (this.root && this.root.parentNode) { this.root.parentNode.removeChild(this.root); }
663
+ this.gl = null; this.ctx2d = null;
664
+ };
665
+
666
+ /* ----------------------------------------------------------------------- *
667
+ * helpers
668
+ * ----------------------------------------------------------------------- */
669
+ function sphereLayout(i, n, r) {
670
+ // fibonacci sphere for pleasing graph node distribution
671
+ n = Math.max(1, n);
672
+ var phi = Math.acos(1 - 2 * (i + 0.5) / n);
673
+ var theta = Math.PI * (1 + Math.sqrt(5)) * i;
674
+ return [r * Math.sin(phi) * Math.cos(theta), r * Math.cos(phi), r * Math.sin(phi) * Math.sin(theta)];
675
+ }
676
+ function parseColor(c) {
677
+ // returns [r,g,b,a] in 0..1
678
+ if (Array.isArray(c)) { return [c[0], c[1], c[2], c[3] == null ? 1 : c[3]]; }
679
+ c = String(c).trim();
680
+ var m;
681
+ if ((m = c.match(/^#([0-9a-f]{6})$/i))) {
682
+ var n = parseInt(m[1], 16);
683
+ return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255, 1];
684
+ }
685
+ if ((m = c.match(/^rgba?\(([^)]+)\)$/i))) {
686
+ var p = m[1].split(",").map(function (x) { return parseFloat(x); });
687
+ return [(p[0] || 0) / 255, (p[1] || 0) / 255, (p[2] || 0) / 255, p[3] == null ? 1 : p[3]];
688
+ }
689
+ return [0.5, 0.8, 1, 1];
690
+ }
691
+ function hexA(c, a) {
692
+ var p = parseColor(c);
693
+ return "rgba(" + (p[0] * 255 | 0) + "," + (p[1] * 255 | 0) + "," + (p[2] * 255 | 0) + "," + a + ")";
694
+ }
695
+ function mul(a, b) {
696
+ var out = new Array(16);
697
+ for (var r = 0; r < 4; r++) {
698
+ for (var c = 0; c < 4; c++) {
699
+ var s = 0;
700
+ for (var k = 0; k < 4; k++) { s += a[k * 4 + c] * b[r * 4 + k]; }
701
+ out[r * 4 + c] = s;
702
+ }
703
+ }
704
+ return out;
705
+ }
706
+
707
+ /* ----------------------------------------------------------------------- *
708
+ * public module surface
709
+ * ----------------------------------------------------------------------- */
710
+ return {
711
+ version: VERSION,
712
+ palette: PALETTE,
713
+ lambdaColor: lambdaColor,
714
+ capabilities: function () { return detectCaps(); },
715
+ createScene: function (mountEl, opts) {
716
+ if (!mountEl) { throw new Error("SZLHolo.createScene requires a mount element"); }
717
+ return new Scene(mountEl, opts || {});
718
+ },
719
+ // expose for advanced lanes (F2-F5) that build their own scene types
720
+ _Scene: Scene,
721
+ _helpers: { sphereLayout: sphereLayout, project: project }
722
+ };
723
+ });