a11oy / static /3d /selftest /index.html
betterwithage's picture
sync: reconcile 33 drifted COPY'd modules from GitHub (static/3d kit + energy operator/pages); GitHub = source of truth
d511c9b verified
Raw
History Blame
12.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>szl3d Toolkit — Self-Test Harness</title>
<!-- 0 runtime CDN. Imports resolve through this local importmap (see VENDOR_MANIFEST.md). -->
<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>
body{margin:0;background:#05070d;color:#eef3f6;font:13px ui-monospace,SFMono-Regular,Menlo,monospace;padding:18px}
h1{font-size:16px;margin:0 0 4px}
.sub{color:#9fb1bf;font-size:12px;margin-bottom:14px}
#summary{font-size:14px;margin:10px 0;padding:8px 12px;border-radius:8px;border:1px solid #1b2734;background:#0b121b}
#summary.pass{border-color:#2fd07a;color:#2fd07a}
#summary.fail{border-color:#ff6b6b;color:#ff6b6b}
table{border-collapse:collapse;width:100%;max-width:900px}
td{padding:5px 10px;border-bottom:1px solid #141d27;vertical-align:top}
.ok{color:#2fd07a}.no{color:#ff6b6b}.idx{color:#6fb1ff;width:34px}
.name{color:#eef3f6}.detail{color:#9fb1bf;font-size:11.5px}
#stage{width:320px;height:200px;border:1px solid #1b2734;border-radius:8px;margin-top:14px}
#chips{margin-top:12px;display:flex;gap:6px;flex-wrap:wrap}
</style>
</head>
<body>
<h1>◇ szl3d Toolkit — Self-Test Harness</h1>
<div class="sub">Dev0 foundation · proves renderer boot + WebGL2 fallback + poll badge + 4 label chips +
degraded/404 + bloom + lazy surface load + 0-CDN. Doctrine v11. <a href="/holographic" style="color:#39d3c4">→ shell</a></div>
<div id="summary">running…</div>
<table id="results"></table>
<div id="chips"></div>
<div id="stage"></div>
<script type="module">
import szl3d, { boot, probeBackend } 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";
const results = [];
const checks = [];
function check(name, fn) { checks.push({ name, fn }); }
// ---- 1: module surface ----
check("szl3d_boot exports boot()", () => typeof boot === "function");
check("szl3d_boot reports three r170", () => {
const p = probeBackend();
if (String(p.three) !== "170") throw new Error("three rev " + p.three);
return true;
});
check("szl3d_live exports poll() + createBadge()", () =>
typeof live.poll === "function" && typeof live.createBadge === "function");
check("szl3d_label exports all 4 doctrine states", () => {
["MEASURED", "MODELED", "SAMPLE", "STRUCTURAL-ONLY"].forEach((k) => {
if (!label.LABELS[k]) throw new Error("missing " + k);
});
return true;
});
// ---- 2: renderer boots (WebGPU-or-WebGL2) ----
let bootedStage = null;
check("renderer boots (WebGPU or WebGL2)", async () => {
const el = document.createElement("div");
el.style.width = "200px"; el.style.height = "120px";
document.body.appendChild(el);
bootedStage = await boot(el, { orbit: false });
if (!bootedStage || !bootedStage.renderer) throw new Error("no renderer");
if (!["webgpu", "webgl2"].includes(bootedStage.backend)) throw new Error("bad backend " + bootedStage.backend);
return "backend=" + bootedStage.backend;
});
// ---- 3: forced WebGL2 fallback always works (Linux/mobile safety) ----
check("forced WebGL2 fallback boots", async () => {
const el = document.createElement("div");
el.style.width = "120px"; el.style.height = "80px";
document.body.appendChild(el);
const s = await boot(el, { forceWebGL: true, orbit: false });
if (s.backend !== "webgl2") throw new Error("backend=" + s.backend);
s.dispose();
return "fallback ok";
});
// ---- 4: scene + camera + render ----
check("scene/camera present and render() runs", () => {
if (!bootedStage.scene || !bootedStage.camera) throw new Error("no scene/camera");
bootedStage.render(); // must not throw
return true;
});
// ---- 5: a default-lit scene is not empty ----
check("default lighting added to scene", () => {
let lights = 0; bootedStage.scene.traverse((o) => { if (o.isLight) lights++; });
if (lights < 1) throw new Error("no lights");
return lights + " lights";
});
// ---- 6: bloom/postprocessing pipeline runs ----
check("bloom composer builds + renders (WebGL2)", async () => {
const el = document.createElement("div");
el.style.width = "160px"; el.style.height = "100px";
document.body.appendChild(el);
const s = await boot(el, { forceWebGL: true, orbit: false, bloom: { strength: 0.8 } });
if (!s.hasBloom()) throw new Error("composer not built");
s.render(); // must run the bloom pass without throwing
s.setBloom(false); s.setBloom(true);
s.dispose();
return "bloom ok";
});
// ---- 7-10: label chips render (all 4 states) ----
const chipBox = document.getElementById("chips");
["MEASURED", "MODELED", "SAMPLE", "STRUCTURAL-ONLY"].forEach((state) => {
check("label chip renders: " + state, () => {
const c = label.chip(state, { text: "demo" });
chipBox.appendChild(c);
if (c.getAttribute("data-label") !== state) throw new Error("data-label=" + c.getAttribute("data-label"));
if (c.textContent.indexOf(state) < 0) throw new Error("text=" + c.textContent);
return true;
});
});
// ---- 11: label reads honesty field straight from JSON ----
check("label normalizes API honesty fields", () => {
if (live.readHonestyLabel({ joules_label: "measured" }) !== "MEASURED") throw new Error("joules_label");
if (live.readHonestyLabel({ data_label: "MODELED" }) !== "MODELED") throw new Error("data_label");
if (label.normalize("sample-signal") !== "SAMPLE") throw new Error("normalize");
return true;
});
// ---- 12: 3D label billboard sprite ----
check("label billboard returns a THREE.Sprite", () => {
const sp = label.billboard(THREE, "MEASURED", { text: "joules" });
if (!sp || !sp.isSprite) throw new Error("not a sprite");
return true;
});
// ---- 13: poll drives LIVE badge on success (mock fetch) ----
const _realFetch = window.fetch;
function mockFetch(map) {
return (url) => {
const u = String(url);
for (const k of Object.keys(map)) { if (u.indexOf(k) >= 0) return Promise.resolve(map[k]()); }
return Promise.resolve(new Response("{}", { status: 200, headers: { "content-type": "application/json" } }));
};
}
function jsonResp(obj, status = 200) {
return new Response(JSON.stringify(obj), { status, headers: { "content-type": "application/json" } });
}
check("poll -> LIVE badge + onData on 200", async () => {
window.fetch = mockFetch({ "/mock/live": () => jsonResp({ price: 42, label: "MEASURED" }) });
let got = null;
const badge = live.createBadge();
const h = live.poll("/mock/live", 10000, (j, meta) => { got = { j, meta }; }, { badge });
await h.refresh();
h.stop();
if (!got) throw new Error("onData not called");
if (got.meta.state !== live.LIVE_STATES.LIVE) throw new Error("state=" + got.meta.state);
if (got.meta.label !== "MEASURED") throw new Error("label=" + got.meta.label);
if (badge.el.getAttribute("data-state") !== "live") throw new Error("badge=" + badge.el.getAttribute("data-state"));
return "badge=live label=MEASURED";
});
// ---- 14: poll handles degraded honestly ----
check("poll -> DEGRADED on {degraded:true}", async () => {
window.fetch = mockFetch({ "/mock/degraded": () => jsonResp({ degraded: true, data_label: "SAMPLE" }) });
let meta = null;
const h = live.poll("/mock/degraded", 10000, (j, mt) => { meta = mt; }, { onState: (m2) => { meta = m2; } });
await h.refresh(); h.stop();
if (!meta || meta.state !== live.LIVE_STATES.DEGRADED) throw new Error("state=" + (meta && meta.state));
return "degraded handled";
});
// ---- 15: poll handles 404 honestly (NO-LIVE-DATA) ----
check("poll -> MISSING on 404", async () => {
window.fetch = mockFetch({ "/mock/404": () => jsonResp({ error: "nope" }, 404) });
const badge = live.createBadge();
let meta = null;
const h = live.poll("/mock/404", 10000, () => {}, { badge, onState: (m2) => { meta = m2; } });
await h.refresh(); h.stop();
if (!meta || meta.state !== live.LIVE_STATES.MISSING) throw new Error("state=" + (meta && meta.state));
if (badge.el.textContent.indexOf("NO-LIVE-DATA") < 0) throw new Error("badge text=" + badge.el.textContent);
return "404 -> NO-LIVE-DATA";
});
// ---- 16: poll handles network error honestly ----
check("poll -> ERROR on network failure", async () => {
window.fetch = () => Promise.reject(new Error("boom"));
let meta = null;
const h = live.poll("/mock/err", 10000, () => {}, { onState: (m2) => { meta = m2; } });
await h.refresh(); h.stop();
if (!meta || meta.state !== live.LIVE_STATES.ERROR) throw new Error("state=" + (meta && meta.state));
return "error handled";
});
// ---- 17: a surface stub lazy-loads + mounts on the shared stage ----
check("surface stub lazy-loads + mounts (tab-switcher contract)", async () => {
window.fetch = mockFetch({ "/api/a11oy/v1/harvest/posture": () => jsonResp({ price_now_eur_mwh: 1, joules_label: "measured" }) });
const mod = await import("/static/3d/surfaces/energy.js");
const surface = mod.default;
if (surface.id !== "energy") throw new Error("id=" + surface.id);
if (typeof surface.mount !== "function" || typeof surface.unmount !== "function") throw new Error("contract");
const el = document.createElement("div"); el.style.width = "160px"; el.style.height = "100px";
document.body.appendChild(el);
const stage = await boot(el, { forceWebGL: true, orbit: false });
surface.mount({ stage, container: el, live, label, THREE, szl3d });
surface.unmount();
stage.dispose();
return "energy mounted+unmounted";
});
// ---- 18: all 9 surface modules import + satisfy the contract ----
check("all 9 surface modules satisfy the contract", async () => {
const ids = ["energy", "fabric", "pnt", "counter-uas", "governance", "pinn", "router", "anatomy", "estate"];
for (const id of ids) {
const mod = await import("/static/3d/surfaces/" + id + ".js");
const s = mod.default;
if (s.id !== id) throw new Error(id + " id=" + s.id);
if (typeof s.mount !== "function" || typeof s.unmount !== "function") throw new Error(id + " contract");
if (!Array.isArray(s.endpoints) || s.endpoints.length < 1) throw new Error(id + " endpoints");
}
return "9/9 ok";
});
// ---- 19: 0 runtime CDN — no fetch-shaped external URL in served toolkit/shell ----
check("0 runtime CDN in toolkit + shell + surfaces", async () => {
window.fetch = _realFetch; // restore real fetch to read our own assets
const files = [
"/static/3d/szl3d/szl3d_boot.js", "/static/3d/szl3d/szl3d_live.js",
"/static/3d/szl3d/szl3d_label.js", "/static/3d/holographic.html",
"/static/3d/selftest/index.html",
"/static/3d/surfaces/energy.js", "/static/3d/surfaces/estate.js",
];
const pats = [
/<script[^>]*\bsrc\s*=\s*['"]https?:\/\//i,
/\bimport\b[^;\n]*\bfrom\s*['"]https?:\/\//i,
/\bimport\s*\(\s*['"]https?:\/\//i,
/\bfetch\s*\(\s*['"`]https?:\/\//i,
];
for (const f of files) {
const t = await (await fetch(f)).text();
for (const p of pats) { if (p.test(t)) throw new Error("CDN-shaped ref in " + f); }
}
return "all clean";
});
// ---- run all ----
(async () => {
for (let i = 0; i < checks.length; i++) {
const c = checks[i];
try {
const r = await c.fn();
results.push({ ok: true, name: c.name, detail: (r === true ? "" : String(r)) });
} catch (e) {
results.push({ ok: false, name: c.name, detail: (e && e.message) || String(e) });
}
}
window.fetch = _realFetch;
render();
})();
function render() {
const tbl = document.getElementById("results");
tbl.innerHTML = "";
let pass = 0;
results.forEach((r, i) => {
if (r.ok) pass++;
const tr = document.createElement("tr");
tr.innerHTML =
'<td class="idx">' + (i + 1) + '</td>' +
'<td class="' + (r.ok ? "ok" : "no") + '">' + (r.ok ? "PASS" : "FAIL") + '</td>' +
'<td class="name">' + r.name + '</td>' +
'<td class="detail">' + (r.detail || "") + '</td>';
tbl.appendChild(tr);
});
const sum = document.getElementById("summary");
const all = pass === results.length;
sum.className = all ? "pass" : "fail";
sum.textContent = (all ? "✓ ALL PASS" : "✗ FAILURES") + " — " + pass + "/" + results.length + " checks";
window.__SZL3D_SELFTEST__ = { pass, total: results.length, allPass: all, results };
}
</script>
</body>
</html>