File size: 19,352 Bytes
d511c9b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d10566a
 
 
 
 
d511c9b
d10566a
d511c9b
 
d10566a
d511c9b
d10566a
 
 
 
d511c9b
d10566a
d511c9b
 
d10566a
d511c9b
d10566a
d511c9b
 
d10566a
 
 
d511c9b
d10566a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d511c9b
 
 
 
 
 
21bdc73
d10566a
 
d511c9b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bffed93
7bea2e7
7f8c86f
7577684
bc19f67
 
b3b529d
4c8ba3e
 
 
a2c4a0c
 
 
5c2e47c
 
 
f45b220
5c2e47c
760376f
 
 
 
940875a
 
 
eb3aa9a
 
 
 
e345f85
 
 
 
268dd75
 
 
 
79cbb0f
 
 
 
6abbb4e
 
 
 
d511c9b
 
 
 
 
 
 
 
5c701a1
d511c9b
2df5c1c
 
 
f8b23e3
 
 
5d27fbc
 
 
2c4baae
9c3ff39
99f88a4
6ab77f4
be4ba51
1c54290
90ba9b5
565e1db
afbb297
0631813
033bd96
212e2c8
6a3fdca
0dc1934
b034ed9
14ea796
d511c9b
 
 
 
 
 
 
 
 
 
 
21bdc73
 
 
 
 
d511c9b
 
 
5abca18
 
 
 
 
d511c9b
21bdc73
d511c9b
 
 
 
 
 
 
 
 
 
21bdc73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d511c9b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d10566a
 
 
 
 
d511c9b
 
 
21bdc73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d511c9b
 
 
 
 
 
21bdc73
 
d511c9b
 
bffed93
d511c9b
bffed93
d511c9b
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Holographic Estate — a11oy</title>
<!-- 0 runtime CDN. All 3D libs vendored in-image and served same-origin via the
     /static/3d/vendor/... importmap below (see VENDOR_MANIFEST.md). System fonts only. -->
<script type="importmap">
{
  "imports": {
    "three": "/static/3d/vendor/three/three.module.min.js",
    "three/webgpu": "/static/3d/vendor/three/three.webgpu.min.js",
    "three/addons/": "/static/3d/vendor/three/addons/"
  }
}
</script>
<style>
 :root{--bg:#05070d;--panel:#0b121b;--line:#1b2734;--teal:#39d3c4;--gold:#e8c074;--cream:#eef3f6;--para:#9fb1bf;}
 *{box-sizing:border-box}
 html,body{margin:0;height:100%}
 /* Flex column so the 3D stage always fills the space left over by the header +
    tab strip, no matter how far they wrap. dvh (dynamic viewport height) tracks
    the mobile address-bar collapse; svh/100vh are fallbacks for older engines. */
 body{background:var(--bg);color:var(--cream);font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,Arial;
  overflow:hidden;display:flex;flex-direction:column;height:100vh;height:100svh;height:100dvh}
 header{padding:10px 16px;border-bottom:1px solid var(--line);background:linear-gradient(180deg,#08101a,#05070d);
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;flex:0 0 auto}
 h1{font-size:16px;margin:0;letter-spacing:.4px}
 .badge{display:inline-block;font:10.5px ui-monospace,monospace;padding:2px 7px;border-radius:6px;
  border:1px solid var(--line);color:var(--gold);background:#10171f;white-space:nowrap}
 #backend{color:var(--teal)}
 /* Desktop: tabs wrap. Mobile (below): one horizontally-scrollable row so the
    ~50 tabs don't build a tall wall that shoves the 3D stage off-screen. */
 .tabs{display:flex;gap:6px;flex-wrap:wrap;padding:8px 12px;border-bottom:1px solid var(--line);
  background:#070d15;flex:0 0 auto}
 .tab{font:11.5px ui-monospace,monospace;padding:6px 12px;border-radius:7px;border:1px solid var(--line);
  background:#0a121b;color:var(--para);cursor:pointer;letter-spacing:.3px;transition:.12s;white-space:nowrap}
 .tab:hover{border-color:var(--teal);color:var(--cream)}
 .tab[aria-selected="true"]{background:#12202b;color:var(--cream);border-color:var(--teal);box-shadow:0 0 0 1px var(--teal) inset}
 #stage-wrap{position:relative;flex:1 1 auto;min-height:0;width:100%}
 #stage{position:absolute;inset:0}
 #stage canvas{display:block;width:100%;height:100%;touch-action:none}
 #fallback{position:absolute;inset:0;display:none;align-items:center;justify-content:center;
  text-align:center;color:var(--para);padding:24px;font:13px ui-monospace,monospace}
 footer{padding:6px 14px;border-top:1px solid var(--line);flex:0 0 auto;
  background:#070d15;color:var(--para);font:10.5px ui-monospace,monospace;letter-spacing:.3px;z-index:6;
  white-space:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}
 a{color:var(--teal);text-decoration:none}

 /* ---- Mobile / touch (<=640px) ---------------------------------------- */
 @media (max-width:640px){
  header{padding:8px 12px;gap:8px}
  h1{font-size:14px;flex:1 1 100%}
  /* Keep only the essential badges visible; hide the noisier ones on small screens. */
  header .badge{font-size:9.5px;padding:2px 6px}
  #three-rev-badge,#cdn-badge{display:none}
  /* Horizontal scroll strip for the tabs, with the active tab kept in view. */
  .tabs{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;
   padding:7px 10px;scrollbar-width:thin}
  .tabs::-webkit-scrollbar{height:4px}
  .tabs::-webkit-scrollbar-thumb{background:var(--line);border-radius:4px}
  .tab{padding:7px 11px;font-size:11px;flex:0 0 auto}
  footer{font-size:9px;padding:5px 10px}
  /* Give the 3D stage a guaranteed usable share of the screen on phones. */
  #stage-wrap{min-height:52vh}
 }
 @media (max-width:640px) and (orientation:landscape){
  #stage-wrap{min-height:60vh}
 }
</style>
</head>
<body>
<header>
 <h1>◇ Holographic Estate</h1>
 <span class="badge" id="backend">renderer: …</span>
 <button class="badge" id="gpu-toggle" title="Toggle WebGPU compute path (falls back to WebGL2 if the browser/driver can't provide a WebGPU device). WebGPU is not production-safe on all platforms — doctrine." style="cursor:pointer;background:#10171f">⚡ WebGPU: off</button>
 <span class="badge" id="three-rev-badge">three r<span id="three-rev">…</span></span>
 <span class="badge" id="cdn-badge">0 runtime CDN</span>
 <span class="badge">Λ = Conjecture 1</span>
 <a href="/" style="margin-left:auto;font:11px ui-monospace,monospace">← a11oy</a>
</header>
<div class="tabs" id="tabs" role="tablist"></div>
<div id="stage-wrap">
 <div id="stage"></div>
 <div id="fallback">3D unavailable on this device — the holographic estate needs a WebGL2 or WebGPU canvas.<br>
  All underlying data is still available as JSON at the per-surface endpoints.</div>
</div>
<footer>Doctrine v11 · locked = 8 @ c7c0ba17 · Λ = Conjecture 1 · Khipu BFT = Conjecture 2 ·
 shared szl3d toolkit (Dev0) · every value carries its honesty label · trust &lt; 100%</footer>

<script type="module">
import szl3d from "/static/3d/szl3d/szl3d_boot.js";
import * as live from "/static/3d/szl3d/szl3d_live.js";
import * as label from "/static/3d/szl3d/szl3d_label.js";
import * as THREE from "three";

// The 9 surface slots the other devs fill. Lazy-loaded per tab.
const SURFACES = [
  { id: "atlas",       title: "Atlas",       mod: "/static/3d/surfaces/atlas.js" },
  { id: "frontier",    title: "Frontier",    mod: "/static/3d/surfaces/frontier.js" },
  { id: "neuromorphic", title: "Neuromorphic", mod: "/static/3d/surfaces/neuromorphic.js" },
  { id: "interpretability", title: "Interpretability", mod: "/static/3d/surfaces/interpretability.js" },
  { id: "worldmodel", title: "World Model", mod: "/static/3d/surfaces/worldmodel.js" },
  { id: "qec", title: "Topological QEC", mod: "/static/3d/surfaces/qec.js" },
  { id: "episodic", title: "Episodic Memory", mod: "/static/3d/surfaces/episodic.js" },
  { id: "testtime", title: "Test-Time Compute", mod: "/static/3d/surfaces/testtime.js" },
  { id: "ssm", title: "State-Space (Mamba-3)", mod: "/static/3d/surfaces/ssm.js" },
  { id: "genie", title: "Genie World-Model", mod: "/static/3d/surfaces/genie.js" },
  { id: "specdecode", title: "Speculative Decoding", mod: "/static/3d/surfaces/specdecode.js" },
  { id: "flowmatch", title: "Flow Matching", mod: "/static/3d/surfaces/flowmatch.js" },
  { id: "dllm", title: "Diffusion LLM", mod: "/static/3d/surfaces/dllm.js" },
  { id: "moe", title: "MoE Router", mod: "/static/3d/surfaces/moe.js" },
  { id: "formalmath", title: "Formal-Math Retrieval", mod: "/static/3d/surfaces/formalmath.js" },
  { id: "ccattest", title: "Confidential-Compute Attest", mod: "/static/3d/surfaces/ccattest.js" },
  { id: "attestinfer", title: "Attested Inference", mod: "/static/3d/surfaces/attestinfer.js" },
  { id: "ringattn", title: "Ring Attention", mod: "/static/3d/surfaces/ringattn.js" },
  { id: "grpo", title: "GRPO Reward Dynamics", mod: "/static/3d/surfaces/grpo.js" },
  { id: "kvcache", title: "KV-Cache H2O", mod: "/static/3d/surfaces/kvcache.js" },
  { id: "mla", title: "Latent Attention (MLA)", mod: "/static/3d/surfaces/mla.js" },
  { id: "blt", title: "Byte-Latent Patching", mod: "/static/3d/surfaces/blt.js" },
  { id: "nsa", title: "Native Sparse Attention", mod: "/static/3d/surfaces/nsa.js" },
  { id: "kan", title: "Kolmogorov-Arnold Network", mod: "/static/3d/surfaces/kan.js" },
  { id: "steering", title: "Steering Vectors (ActAdd / RepE)", mod: "/static/3d/surfaces/steering.js" },
  { id: "titans", title: "Titans Neural Long-Term Memory", mod: "/static/3d/surfaces/titans.js" },
  { id: "mor", title: "Mixture-of-Recursions (adaptive depth)", mod: "/static/3d/surfaces/mor.js" },
  { id: "goat", title: "GOAT Optimal-Transport Attention", mod: "/static/3d/surfaces/goat.js" },
  { id: "kla", title: "Kaczmarz Linear Attention", mod: "/static/3d/surfaces/kla.js" },
  { id: "hrm", title: "Hierarchical Reasoning Model", mod: "/static/3d/surfaces/hrm.js" },
  { id: "pfield", title: "Pressure-Field Coordination", mod: "/static/3d/surfaces/pfield.js" },
  { id: "qhall", title: "Quantum-Inspired Hallucination UQ", mod: "/static/3d/surfaces/qhall.js" },
  { id: "aimc", title: "Analog In-Memory Attention", mod: "/static/3d/surfaces/aimc.js" },
  { id: "ternary", title: "Ternary 1.58-bit Weights", mod: "/static/3d/surfaces/ternary.js" },
  { id: "sement", title: "Semantic-Entropy Uncertainty", mod: "/static/3d/surfaces/sement.js" },
  { id: "inplacettt", title: "In-Place Test-Time Training", mod: "/static/3d/surfaces/inplacettt.js" },
  { id: "nested", title: "Nested Learning (multi-timescale)", mod: "/static/3d/surfaces/nested.js" },
  { id: "matgran", title: "Matryoshka Representation Granularity", mod: "/static/3d/surfaces/matgran.js" },
  { id: "graphmem", title: "Multi-Graph Agentic Memory", mod: "/static/3d/surfaces/graphmem.js" },
  { id: "elf", title: "Continuous-Embedding Flow LM", mod: "/static/3d/surfaces/elf.js" },
  { id: "s3search", title: "Stratified Denoise Search", mod: "/static/3d/surfaces/s3search.js" },
  { id: "slidesparse", title: "Structured-Sparse Layout Packing", mod: "/static/3d/surfaces/slidesparse.js" },
  { id: "catq", title: "Calibration Ternary Quant", mod: "/static/3d/surfaces/catq.js" },
  { id: "rauq", title: "Attention-Pattern Uncertainty", mod: "/static/3d/surfaces/rauq.js" },
  { id: "agentcoh", title: "Multi-Agent Memory Coherence", mod: "/static/3d/surfaces/agentcoh.js" },
  { id: "energy",      title: "Energy",      mod: "/static/3d/surfaces/energy.js" },
  { id: "fabric",      title: "Fabric",      mod: "/static/3d/surfaces/fabric.js" },
  { id: "pnt",         title: "PNT",         mod: "/static/3d/surfaces/pnt.js" },
  { id: "counter-uas", title: "Counter-UAS", mod: "/static/3d/surfaces/counter-uas.js" },
  { id: "governance",  title: "Governance",  mod: "/static/3d/surfaces/governance.js" },
  { id: "pinn",        title: "PINN",        mod: "/static/3d/surfaces/pinn.js" },
  { id: "router",      title: "Router",      mod: "/static/3d/surfaces/router.js" },
  { id: "anatomy",     title: "Anatomy",     mod: "/static/3d/surfaces/anatomy.js" },
  { id: "anatomy_body", title: "Anatomy · Living Body", mod: "/static/3d/surfaces/anatomy_body.js" },
  { id: "estate",      title: "Estate",      mod: "/static/3d/surfaces/estate.js" },
  { id: "muon",        title: "Muon Orthogonalized-Momentum Optimizer", mod: "/static/3d/surfaces/muon.js" },
  { id: "specexec",    title: "Tree Speculative Execution",             mod: "/static/3d/surfaces/specexec.js" },
  { id: "nvfp4",       title: "NVFP4 4-bit Training Format",            mod: "/static/3d/surfaces/nvfp4.js" },
  { id: "keyless",     title: "Keyless Attention (Value-Only Cache)",     mod: "/static/3d/surfaces/keyless.js" },
  { id: "gitthoughts", title: "GitOfThoughts (Version-Controlled Reasoning)", mod: "/static/3d/surfaces/gitthoughts.js" },
  { id: "herotq",      title: "HeRo-Q Hessian-Conditioned Quantization",    mod: "/static/3d/surfaces/herotq.js" },
  { id: "dla",         title: "Dynamic Linear Attention",                    mod: "/static/3d/surfaces/dla.js" },
  { id: "ctxready",    title: "Context-Ready Transformer",                   mod: "/static/3d/surfaces/ctxready.js" },
  { id: "opera",       title: "OPERA Perplexity-Reward Alignment",           mod: "/static/3d/surfaces/opera.js" },
  { id: "brain",       title: "Brain — live knowledge graph",                mod: "/static/3d/surfaces/brain.js" },
  { id: "zkinfer",     title: "zkML Proof-of-Inference (Cryptographic Receipts)", mod: "/static/3d/surfaces/zkinfer.js" },
  { id: "flower", title: "Flower Brain", mod: "/static/3d/surfaces/flower.js" },
  { id: "agentmem",    title: "AgentMem · Λ-Governed Agent Memory (synthesis)",   mod: "/static/3d/surfaces/agentmem.js" },
  { id: "loopforge", title: "Loop Forge", mod: "/static/3d/surfaces/loopforge.js" },
  { id: "edgefusion",  title: "EdgeFusion · Λ-Gated Energy-Proportional Sensor Fusion (synthesis)", mod: "/static/3d/surfaces/edgefusion.js" },
  { id: "evalarena", title: "Eval Arena · Governed Eval / Red-Team", mod: "/static/3d/surfaces/evalarena.js" },
  { id: "vqc",         title: "Governed VQC · Parameter-Shift Hybrid QML (MODELED)", mod: "/static/3d/surfaces/vqc.js" },
  { id: "harness",     title: "Governed Model Harness · Behavior Transfer",         mod: "/static/3d/surfaces/harness.js" },
  { id: "aigov",       title: "AI Governance Conformance · Λ-Advisory Readiness (crosswalk)", mod: "/static/3d/surfaces/aigov.js" },
  { id: "fmverif",     title: "Proof-Carrying Inference (Machine-Checkable Certificates)", mod: "/static/3d/surfaces/fmverif.js" },
  { id: "supplychain", title: "Model-Artifact Provenance (SLSA / in-toto / Rekor / C2PA)", mod: "/static/3d/surfaces/supplychain.js" },
  { id: "hybridssm",   title: "HybridSSM · Attention vs State-Space vs Hybrid Frontier (synthesis)", mod: "/static/3d/surfaces/hybridssm.js" },
  { id: "governedagent", title: "Governed Agent Loop · plan→act→self-eval→gate→retry", mod: "/static/3d/surfaces/governedagent.js" },
  { id: "governedrag", title: "Governed RAG · Retrieval-with-Receipts",                mod: "/static/3d/surfaces/governedrag.js" },
  { id: "ecosystem",   title: "Harness · Ecosystem Status",                            mod: "/static/3d/surfaces/ecosystem.js" },
];

const stageEl = document.getElementById("stage");
const tabsEl = document.getElementById("tabs");
const fallbackEl = document.getElementById("fallback");
document.getElementById("three-rev").textContent = THREE.REVISION;

let stage = null;
let current = null;       // { def, instance }
let loadToken = 0;

// WebGPU opt-in: seeded from ?webgpu=1, flippable live via the header toggle. WebGPU is not
// production-safe on all browsers/drivers yet (doctrine) so WebGL2 stays the default; the
// toolkit's probe is timeout-guarded and falls back honestly if no device is available.
let wantWebGPU = /[?&]webgpu=1\b/.test(location.search);

async function ensureStage() {
  if (stage) return stage;
  try {
    stage = await szl3d.boot(stageEl, {
      bloom: { strength: 0.85, radius: 0.5, threshold: 0.18 },
      cameraPos: [0, 5, 16],
      forceWebGL: !wantWebGPU,
    });
    document.getElementById("backend").textContent = "renderer: " + stage.backend.toUpperCase();
    _syncGpuToggle();
    stage.start();
  } catch (e) {
    fallbackEl.style.display = "flex";
    document.getElementById("backend").textContent = "renderer: UNAVAILABLE";
    console.error("[holographic] stage boot failed:", e);
    throw e;
  }
  return stage;
}

function _syncGpuToggle() {
  const btn = document.getElementById("gpu-toggle");
  if (!btn) return;
  const on = stage && stage.backend === "webgpu";
  btn.textContent = "\u26a1 WebGPU: " + (on ? "on" : (wantWebGPU ? "requested" : "off"));
  btn.style.color = on ? "var(--proof, #3af4c8)" : "var(--gold)";
  btn.style.borderColor = on ? "var(--teal)" : "var(--line)";
}

// Tear down the current stage + surface and re-boot with the new renderer preference, then
// re-mount the active surface. Used by the header WebGPU toggle so users can engage the
// compute path on capable browsers without editing the URL.
async function rebootStage() {
  const active = current ? current.def.id : (SURFACES[0] && SURFACES[0].id);
  try { if (current && current.instance && current.instance.unmount) current.instance.unmount(); } catch (_) {}
  current = null;
  try { if (stage && stage.dispose) stage.dispose(); } catch (_) {}
  stage = null;
  // The toolkit's dispose() releases the GL context but leaves its <canvas> in the DOM;
  // a fresh boot() appends a new one. Remove the orphaned canvas(es) here so a toggle
  // never accumulates duplicate canvases / leaked contexts.
  try { stageEl.querySelectorAll("canvas").forEach((c) => c.remove()); } catch (_) {}
  fallbackEl.style.display = "none";
  await selectSurface(active);
}

function clearScene() {
  if (!stage) return;
  // Remove everything the previous surface added except the default lights.
  const keep = new Set();
  stage.scene.traverse((o) => { if (o.isLight) keep.add(o); });
  for (let i = stage.scene.children.length - 1; i >= 0; i--) {
    const o = stage.scene.children[i];
    if (!keep.has(o)) stage.scene.remove(o);
  }
  // reset per-frame callbacks by re-booting frame list is overkill; surfaces guard on null.
}

async function selectSurface(id) {
  const def = SURFACES.find((s) => s.id === id);
  if (!def) return;
  const token = ++loadToken;
  // visual selection state
  [...tabsEl.children].forEach((t) => t.setAttribute("aria-selected", String(t.dataset.id === id)));

  await ensureStage();
  // tear down previous
  if (current && current.instance && current.instance.unmount) {
    try { current.instance.unmount(); } catch (_) {}
  }
  clearScene();

  let mod;
  try { mod = await import(def.mod); }
  catch (e) { console.error("[holographic] failed to load surface", id, e); return; }
  if (token !== loadToken) return; // a newer tab click superseded us

  const surface = mod.default || mod;
  const ctx = { stage, container: stageEl, live, label, THREE, szl3d };
  try {
    surface.mount(ctx);
    current = { def, instance: surface };
    history.replaceState(null, "", "#" + id);
  } catch (e) {
    console.error("[holographic] surface mount threw", id, e);
  }
}

// build tabs
SURFACES.forEach((s) => {
  const b = document.createElement("button");
  b.className = "tab"; b.textContent = s.title; b.dataset.id = s.id;
  b.setAttribute("role", "tab"); b.setAttribute("aria-selected", "false");
  b.addEventListener("click", () => {
    selectSurface(s.id);
    // On the mobile horizontal tab strip, keep the tapped tab in view.
    try { b.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" }); } catch (_) {}
  });
  tabsEl.appendChild(b);
});

// WebGPU toggle: flip the preference and re-boot the renderer + re-mount the active surface.
// Honest: if the browser/driver yields no WebGPU device the toolkit falls back to WebGL2 and
// the badge reports the real backend ("WebGPU: requested" but renderer stays WEBGL2).
{
  const gpuBtn = document.getElementById("gpu-toggle");
  if (gpuBtn) {
    gpuBtn.addEventListener("click", async () => {
      wantWebGPU = !wantWebGPU;
      gpuBtn.disabled = true;
      const prev = gpuBtn.textContent; gpuBtn.textContent = "\u26a1 switching\u2026";
      try { await rebootStage(); } catch (_) { document.getElementById("backend").textContent = "renderer: UNAVAILABLE"; }
      finally { gpuBtn.disabled = false; _syncGpuToggle(); }
    });
    _syncGpuToggle();
  }
}

// expose a tiny API for the selftest harness to drive the shell headlessly.
window.__SZL3D_SHELL__ = {
  surfaces: SURFACES,
  select: selectSurface,
  get stage() { return stage; },
  get current() { return current; },
  get wantWebGPU() { return wantWebGPU; },
  ensureStage, rebootStage,
};

// initial surface from hash or default to the Atlas front door (Wave 27 unifier)
const initial = (location.hash || "").replace("#", "");
selectSurface(SURFACES.some((s) => s.id === initial) ? initial : "atlas");
</script>
</body>
</html>