// SPDX-License-Identifier: Apache-2.0 // © 2026 Lutar, Stephen P. Jr. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11 // // surfaces/pinn.js — PINN Thermal/Field surface (Dev6). // // Leader/technique modeled: Kitware VTK.js / VolView cinematic volume rendering + // three.js TSL compute (frontier). Implemented here as a WebGPU-attempt / WebGL2-fallback // GLSL ray-marched 3D scalar-field volume + GPU-instanced Gaussian-splat scalar field // (novel holographic presentation per the viz-leaders research §6 + §8), an isosurface // shell, a PDE-residual displacement heatmap, an instanced vector arrow field, and the // compute_bounds physical-ceiling ladder (Landauer / Margolus-Levitin / Bremermann / // Bekenstein) rendered straight from the MEASURED+SIGNED physical-bounds certificate. // // DOCTRINE v11 HONESTY (load-bearing — do not soften): // * The certificate is MEASURED + SIGNED: avg_power_w / wall_time_s / temperature_k are // real on-metal NVML samples (sovereign GPU betterwithage), the energy is DERIVED // (P×t), the envelope is signed with a real Ed25519 DSSE signature (FA-001 on-metal), // cosign.pub-anchored (ECDSA-P256), and anchored in the public Rekor transparency log. // We render that proudly AND accurately — exact algs, keyids, Rekor log_index/uuid. // * The rendered 3D field is a *visualization of the model* — a deterministic analytic // thermal/PDE field seeded by the MEASURED scalars. It is labelled MODELED, never // MEASURED. If no cert value is available we fall to an explicitly-labelled SAMPLE // field. We NEVER fabricate a field number or claim the rendered voxels are measured. // * The agentic-PINN residual trail (/pinn/residual) is AWAITING_GPU_SOLVE in this // environment — we render the honest AWAITING state, never a fabricated residual. // // LIVE DATA (never hardcoded — read via ctx.live.poll): // /api/a11oy/v1/pinn/certificate MEASURED+SIGNED physical-bounds certificate (primary) // /api/a11oy/v1/pnt/limits compute_bounds pillar (4-pillar fundamental-limits index) // /api/a11oy/v1/pinn/residual governed agentic solve residual trail (AWAITING here) // // CONTRACT: default-export { id, title, endpoints[], mount(ctx), unmount() }. // The shell shares ONE Stage across surfaces; we add objects to ctx.stage.scene, register // per-frame work via ctx.stage.onFrame, and on unmount stop every poll + remove what we added. const ID = "pinn"; const TITLE = "PINN Thermal/Field"; const EP_CERT = "/api/a11oy/v1/pinn/certificate"; const EP_LIMITS = "/api/a11oy/v1/pnt/limits"; const EP_RESIDUAL = "/api/a11oy/v1/pinn/residual"; // Volume sampling resolution for the GPU-built 3D scalar-field texture. const VOX = 48; // --------------------------------------------------------------------------- // Module state (single active surface at a time per the shell contract). // --------------------------------------------------------------------------- let _ctx = null, _stage = null, _THREE = null; let _group = null; // root group holding all scene objects we add let _handles = []; // poll handles to stop on unmount let _overlay = null; // DOM HUD let _frameReg = null; // our onFrame closure (guards on null after unmount) let _disposables = []; // geometries / materials / textures to dispose // live cert-derived state (all MODELED-for-render, sourced from MEASURED scalars) const F = { haveCert: false, certLabel: "SAMPLE", // honesty token for the FIELD render (MEASURED scalars → MODELED field) signed: false, // MEASURED scalars (from cert.measured.*_MEASURED) — null until live tempK: null, powerW: null, wallS: null, energyJ: null, // DERIVED bounds (from cert.*) landauerMult: null, mlFrac: null, bremFrac: null, bekFrac: null, bounded: null, // signatures / anchors ed25519Keyid: null, cosignKeyid: null, cosignPubUrl: null, certSha: null, rekorUuid: null, rekorIndex: null, rekorTime: null, rekorProvider: null, khipuDigest: null, // residual trail residualState: "INIT", residualRounds: [], // controls isoThreshold: 0.55, splatOn: true, arrowsOn: true, residualDisp: 0.0, backend: "…", t: 0, }; // SAMPLE seed values — used ONLY for the field shape before the live cert arrives, and // then ONLY labelled SAMPLE. These are illustrative, never presented as measurement. const SAMPLE = { tempK: 320, powerW: 30, wallS: 60 }; // --------------------------------------------------------------------------- // Honest accessor: the scalars that drive the field. Returns {tempK,powerW,label}. // When the MEASURED cert is live → real scalars, field labelled MODELED. // Before that → SAMPLE seed, field labelled SAMPLE. Never fabricates. // --------------------------------------------------------------------------- function _fieldScalars() { if (F.haveCert && F.tempK != null) { return { tempK: F.tempK, powerW: F.powerW != null ? F.powerW : SAMPLE.powerW, label: "MODELED" }; } return { tempK: SAMPLE.tempK, powerW: SAMPLE.powerW, label: "SAMPLE" }; } // --------------------------------------------------------------------------- // Analytic PINN-style thermal field f(x,y,z) ∈ [0,1] — a deterministic heat-kernel // surrogate (sum of Gaussian thermal sources + a steady diffusion gradient). This is // the MODELED field the volume/isosurface/splats/arrows all read. Seeded by the // MEASURED temperature so the hot core scales with real telemetry. Pure CPU mirror of // the GLSL so isosurface + arrows + splats agree with the ray-march. // --------------------------------------------------------------------------- function _sampleField(x, y, z, hot) { // x,y,z ∈ [-1,1]. hot ∈ ~[0.4,1.1] scales the central source from measured temp. const r2 = x * x + y * y + z * z; const core = Math.exp(-2.4 * r2) * (0.85 + 0.35 * hot); // two offset thermal lobes (conduction toward edges) const dx1 = x - 0.45, dy1 = y - 0.15; const lobe1 = 0.45 * Math.exp(-5.0 * (dx1 * dx1 + dy1 * dy1 + z * z)); const dx2 = x + 0.4, dz2 = z + 0.35; const lobe2 = 0.4 * Math.exp(-5.5 * (dx2 * dx2 + y * y + dz2 * dz2)); // a gentle diffusion gradient (cooler at +y, the "exhaust" direction) const grad = 0.12 * (1.0 - (y + 1.0) * 0.5); let v = core + lobe1 + lobe2 + grad; return Math.max(0, Math.min(1, v)); } // PDE residual surrogate r(x,y,z) ∈ [0,1] — large where the analytic field has high // curvature (the steep flank of the hot core), which is exactly where a real PINN's // physics-loss collocation would densify (RAR/RAD). MODELED bound, not measured. function _sampleResidual(x, y, z, hot) { const r2 = x * x + y * y + z * z; const flank = Math.exp(-2.4 * r2) * r2 * 4.0; // peaks on the gradient flank return Math.max(0, Math.min(1, flank * (0.7 + 0.5 * hot))); } // --------------------------------------------------------------------------- // Build the 3D scalar-field texture (Data3DTexture, R channel = scalar in [0,1]). // On WebGPU/WebGL2 alike this is sampled by the ray-march material. Rebuilt when the // measured temperature changes (rare), not per-frame. // --------------------------------------------------------------------------- function _buildVolumeTexture(hot) { const THREE = _THREE; const n = VOX, data = new Uint8Array(n * n * n); let i = 0; for (let zi = 0; zi < n; zi++) { const z = (zi / (n - 1)) * 2 - 1; for (let yi = 0; yi < n; yi++) { const y = (yi / (n - 1)) * 2 - 1; for (let xi = 0; xi < n; xi++) { const x = (xi / (n - 1)) * 2 - 1; data[i++] = Math.round(_sampleField(x, y, z, hot) * 255); } } } const tex = new THREE.Data3DTexture(data, n, n, n); tex.format = THREE.RedFormat; tex.type = THREE.UnsignedByteType; tex.minFilter = THREE.LinearFilter; tex.magFilter = THREE.LinearFilter; tex.unpackAlignment = 1; tex.needsUpdate = true; return tex; } // --------------------------------------------------------------------------- // GLSL ray-march volume material. Front-face cull off / back-face render of a unit // cube; the fragment shader marches camera→fragment through the 3D texture and // accumulates a temperature transfer function (blue→cyan→amber→white-hot). Works on // WebGL2 (the Linux fallback) AND WebGPU (three compiles GLSL nodeless materials on // the WebGL2 path; on a true WebGPU device the shell still renders via the same Mesh // because we use ShaderMaterial which three's WebGPURenderer supports via its WGSL // transpile for raw GLSL ShaderMaterial in r170's backend-compat path). The fallback // is honest: if the device is WebGPU and ShaderMaterial is unsupported we still show // the isosurface + splats, which use standard materials. // --------------------------------------------------------------------------- function _volumeMaterial(tex) { const THREE = _THREE; return new THREE.ShaderMaterial({ glslVersion: THREE.GLSL3, transparent: true, depthWrite: false, side: THREE.BackSide, uniforms: { uVol: { value: tex }, uThreshold: { value: F.isoThreshold }, uSteps: { value: 96 }, uTime: { value: 0 }, uOpacity: { value: 0.92 }, uCamPos: { value: new THREE.Vector3() }, uInvModel: { value: new THREE.Matrix4() }, }, vertexShader: /* glsl */` out vec3 vLocal; void main(){ vLocal = position; // unit cube in [-0.5,0.5] gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } `, fragmentShader: /* glsl */` precision highp float; precision highp sampler3D; in vec3 vLocal; out vec4 fragColor; uniform sampler3D uVol; uniform float uThreshold; uniform int uSteps; uniform float uTime; uniform float uOpacity; uniform vec3 uCamPos; uniform mat4 uInvModel; // temperature transfer function: cold blue -> cyan -> amber -> white-hot vec3 tf(float t){ t = clamp(t,0.0,1.0); vec3 cold = vec3(0.05,0.12,0.35); vec3 mid = vec3(0.13,0.72,0.74); vec3 warm = vec3(0.91,0.62,0.28); vec3 hot = vec3(1.0,0.96,0.86); vec3 c = mix(cold, mid, smoothstep(0.0,0.45,t)); c = mix(c, warm, smoothstep(0.4,0.75,t)); c = mix(c, hot, smoothstep(0.75,1.0,t)); return c; } // intersect ray with unit box [-0.5,0.5]^3 vec2 boxHit(vec3 ro, vec3 rd){ vec3 inv = 1.0/rd; vec3 a = (vec3(-0.5)-ro)*inv; vec3 b = (vec3( 0.5)-ro)*inv; vec3 tmin = min(a,b), tmax = max(a,b); float t0 = max(max(tmin.x,tmin.y),tmin.z); float t1 = min(min(tmax.x,tmax.y),tmax.z); return vec2(t0,t1); } void main(){ // ray in local cube space vec3 ro = (uInvModel * vec4(uCamPos,1.0)).xyz; vec3 rd = normalize(vLocal - ro); vec2 hit = boxHit(ro, rd); float t0 = max(hit.x, 0.0), t1 = hit.y; if (t1 <= t0){ discard; } int steps = uSteps; float dt = (t1 - t0)/float(steps); vec3 col = vec3(0.0); float alpha = 0.0; float t = t0 + dt*fract(sin(dot(vLocal.xy,vec2(12.9898,78.233)))*43758.5453); // jitter for (int i=0;i<256;i++){ if (i>=steps || alpha>0.98) break; vec3 p = ro + rd*t; // [-0.5,0.5] vec3 uv = p + 0.5; // [0,1] float s = texture(uVol, uv).r; // emphasise voxels above the iso threshold; pulse subtly for the holographic feel float w = smoothstep(uThreshold-0.12, uThreshold+0.04, s); float dens = s*0.55 + w*0.85; dens *= (0.85 + 0.15*sin(uTime*1.4 + s*8.0)); vec3 c = tf(s); float a = dens * uOpacity * dt * 6.0; a = clamp(a,0.0,1.0); col += (1.0-alpha) * a * c; alpha += (1.0-alpha) * a; t += dt; } if (alpha < 0.003) discard; fragColor = vec4(col, alpha); } `, }); } // --------------------------------------------------------------------------- // Gaussian-splat scalar field (novel holographic) — one GPU-instanced additive // billboard quad per high-scalar voxel; opacity + color = field value. This is the // research §8 "Gaussian Splatting for scalar fields" technique applied to the MODELED // PINN field. Built once (a fixed sparse voxel set above a low cutoff), recolored live. // --------------------------------------------------------------------------- function _buildSplats(hot) { const THREE = _THREE; const pts = []; const n = 26; // coarse splat lattice for (let zi = 0; zi < n; zi++) { const z = (zi / (n - 1)) * 2 - 1; for (let yi = 0; yi < n; yi++) { const y = (yi / (n - 1)) * 2 - 1; for (let xi = 0; xi < n; xi++) { const x = (xi / (n - 1)) * 2 - 1; const s = _sampleField(x, y, z, hot); if (s > 0.34) pts.push([x, y, z, s]); } } } const count = pts.length; const geo = new THREE.PlaneGeometry(1, 1); const inst = new THREE.InstancedMesh(geo, _splatMaterial(), count); const m = new THREE.Matrix4(); const col = new THREE.Color(); for (let k = 0; k < count; k++) { const [x, y, z, s] = pts[k]; const sc = 0.10 + s * 0.42; m.makeScale(sc, sc, sc); m.setPosition(x * 2.0, y * 2.0, z * 2.0); inst.setMatrixAt(k, m); _tfColor(col, s); inst.setColorAt(k, col); } inst.instanceMatrix.needsUpdate = true; if (inst.instanceColor) inst.instanceColor.needsUpdate = true; inst.userData.splatCount = count; inst.userData.pts = pts; _disposables.push(geo); return inst; } function _splatMaterial() { const THREE = _THREE; // additive radial-gaussian sprite via a small canvas texture const cnv = document.createElement("canvas"); cnv.width = cnv.height = 64; const g = cnv.getContext("2d"); const grad = g.createRadialGradient(32, 32, 0, 32, 32, 32); grad.addColorStop(0, "rgba(255,255,255,1)"); grad.addColorStop(0.4, "rgba(255,255,255,0.5)"); grad.addColorStop(1, "rgba(255,255,255,0)"); g.fillStyle = grad; g.fillRect(0, 0, 64, 64); const tex = new THREE.CanvasTexture(cnv); const mat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, vertexColors: true, opacity: 0.9, }); _disposables.push(tex, mat); return mat; } function _tfColor(col, t) { // CPU mirror of the GLSL transfer function (cold→hot) t = Math.max(0, Math.min(1, t)); const lerp = (a, b, k) => a + (b - a) * k; const sm = (e0, e1, x) => { const k = Math.max(0, Math.min(1, (x - e0) / (e1 - e0))); return k * k * (3 - 2 * k); }; let r = lerp(0.05, 0.13, sm(0, 0.45, t)), gn = lerp(0.12, 0.72, sm(0, 0.45, t)), b = lerp(0.35, 0.74, sm(0, 0.45, t)); r = lerp(r, 0.91, sm(0.4, 0.75, t)); gn = lerp(gn, 0.62, sm(0.4, 0.75, t)); b = lerp(b, 0.28, sm(0.4, 0.75, t)); r = lerp(r, 1.0, sm(0.75, 1, t)); gn = lerp(gn, 0.96, sm(0.75, 1, t)); b = lerp(b, 0.86, sm(0.75, 1, t)); col.setRGB(r, gn, b); return col; } // --------------------------------------------------------------------------- // Isosurface shell — a marching-cubes-style threshold surface. We approximate it with // an icosphere whose vertices are displaced to the radius where the field crosses the // iso threshold along that direction (a star-shaped level-set, cheap + interactive). // Recomputed when the slider moves. Real MC on the GPU is the TSL-compute TODO; this is // the honest interactive fallback that runs on WebGL2 too. // --------------------------------------------------------------------------- function _buildIsosurface(hot, threshold) { const THREE = _THREE; const geo = new THREE.IcosahedronGeometry(1, 5); const pos = geo.attributes.position; const v = new THREE.Vector3(); const colors = new Float32Array(pos.count * 3); const col = new THREE.Color(); for (let i = 0; i < pos.count; i++) { v.fromBufferAttribute(pos, i).normalize(); // march outward to find where field == threshold along this ray let rHit = 0.18; for (let s = 0; s <= 64; s++) { const r = 0.05 + (s / 64) * 1.4; const f = _sampleField(v.x * r, v.y * r, v.z * r, hot); if (f < threshold) { rHit = r; break; } rHit = r; } const R = rHit * 2.0; pos.setXYZ(i, v.x * R, v.y * R, v.z * R); _tfColor(col, threshold); colors[i * 3] = col.r; colors[i * 3 + 1] = col.g; colors[i * 3 + 2] = col.b; } geo.setAttribute("color", new THREE.BufferAttribute(colors, 3)); geo.computeVertexNormals(); pos.needsUpdate = true; const mat = new THREE.MeshStandardMaterial({ vertexColors: true, transparent: true, opacity: 0.34, metalness: 0.2, roughness: 0.4, emissive: 0x163040, emissiveIntensity: 0.5, side: THREE.DoubleSide, wireframe: false, }); const mesh = new THREE.Mesh(geo, mat); mesh.userData.iso = true; _disposables.push(geo, mat); return mesh; } // --------------------------------------------------------------------------- // Residual heatmap displacement shell — a second icosphere whose vertices protrude // outward + turn orange/red where the MODELED PDE residual is high (research §6 step 4: // "high residual = surface protrudes + turns orange/red"). residualDisp slider scales it. // --------------------------------------------------------------------------- function _buildResidualShell(hot) { const THREE = _THREE; const geo = new THREE.IcosahedronGeometry(2.6, 5); const pos = geo.attributes.position; const base = pos.array.slice(); const colors = new Float32Array(pos.count * 3); const v = new THREE.Vector3(), col = new THREE.Color(); for (let i = 0; i < pos.count; i++) { v.set(base[i * 3], base[i * 3 + 1], base[i * 3 + 2]); const n = v.clone().normalize(); const res = _sampleResidual(n.x, n.y, n.z, hot); // residual → red/orange ramp (separate from the temperature TF, so it reads as "error") col.setRGB(0.2 + res * 0.8, 0.18 + res * 0.35, 0.1 + (1 - res) * 0.2); colors[i * 3] = col.r; colors[i * 3 + 1] = col.g; colors[i * 3 + 2] = col.b; } geo.setAttribute("color", new THREE.BufferAttribute(colors, 3)); geo.userData.base = base; geo.userData.hot = hot; geo.computeVertexNormals(); const mat = new THREE.MeshStandardMaterial({ vertexColors: true, transparent: true, opacity: 0.0, // hidden until residualDisp>0 metalness: 0.1, roughness: 0.6, side: THREE.DoubleSide, emissive: 0x401505, emissiveIntensity: 0.4, wireframe: true, }); const mesh = new THREE.Mesh(geo, mat); mesh.userData.residual = true; _disposables.push(geo, mat); return mesh; } function _applyResidualDisp(mesh, disp) { const geo = mesh.geometry; const base = geo.userData.base, hot = geo.userData.hot; const pos = geo.attributes.position; const v = _THREE ? new _THREE.Vector3() : null; for (let i = 0; i < pos.count; i++) { const bx = base[i * 3], by = base[i * 3 + 1], bz = base[i * 3 + 2]; v.set(bx, by, bz); const len = v.length(); v.normalize(); const res = _sampleResidual(v.x, v.y, v.z, hot); const R = len + res * disp * 1.4; pos.setXYZ(i, v.x * R, v.y * R, v.z * R); } pos.needsUpdate = true; geo.computeVertexNormals(); mesh.material.opacity = disp > 0.01 ? 0.55 : 0.0; } // --------------------------------------------------------------------------- // Vector arrow field — instanced cones pointing along -∇field (heat-flow direction), // length ∝ |∇field|. Research §6 step 5 "velocity as a vector arrow field, instanced // ConeGeometry". MODELED gradient of the MODELED field. // --------------------------------------------------------------------------- function _buildArrows(hot) { const THREE = _THREE; const dirs = []; const n = 7; for (let zi = 0; zi < n; zi++) for (let yi = 0; yi < n; yi++) for (let xi = 0; xi < n; xi++) { const x = (xi / (n - 1)) * 2 - 1, y = (yi / (n - 1)) * 2 - 1, z = (zi / (n - 1)) * 2 - 1; const e = 0.04; const gx = (_sampleField(x + e, y, z, hot) - _sampleField(x - e, y, z, hot)) / (2 * e); const gy = (_sampleField(x, y + e, z, hot) - _sampleField(x, y - e, z, hot)) / (2 * e); const gz = (_sampleField(x, y, z + e, hot) - _sampleField(x, y, z - e, hot)) / (2 * e); const g = new THREE.Vector3(-gx, -gy, -gz); // heat flows down-gradient const mag = g.length(); if (mag < 0.06) continue; dirs.push({ p: new THREE.Vector3(x * 2, y * 2, z * 2), d: g.normalize(), mag }); } const count = dirs.length; const geo = new THREE.ConeGeometry(0.045, 0.28, 6); geo.translate(0, 0.14, 0); const mat = new THREE.MeshStandardMaterial({ color: 0x8fd7ff, emissive: 0x2a5a72, emissiveIntensity: 0.6, metalness: 0.3, roughness: 0.4 }); const inst = new THREE.InstancedMesh(geo, mat, count); const m = new THREE.Matrix4(), q = new THREE.Quaternion(), up = new THREE.Vector3(0, 1, 0), scl = new THREE.Vector3(); for (let k = 0; k < count; k++) { const { p, d, mag } = dirs[k]; q.setFromUnitVectors(up, d); const L = 0.5 + Math.min(2.0, mag) * 0.9; scl.set(1, L, 1); m.compose(p, q, scl); inst.setMatrixAt(k, m); } inst.instanceMatrix.needsUpdate = true; inst.userData.arrows = true; _disposables.push(geo, mat); return inst; } // --------------------------------------------------------------------------- // compute_bounds physical-ceiling ladder — vertical bars on a log axis showing where // the MEASURED job sits between the Landauer floor and the Margolus-Levitin / // Bremermann / Bekenstein ceilings. Heights/positions are DERIVED from the cert's // real fractions. A genius "physical-bounds ladder" rendered in 3D, every rung labelled. // --------------------------------------------------------------------------- function _buildBoundsLadder() { const THREE = _THREE; const g = new THREE.Group(); g.position.set(4.6, -1.2, 0); g.userData.ladder = true; // a tall reference spine const spineGeo = new THREE.CylinderGeometry(0.015, 0.015, 4.4, 8); const spineMat = new THREE.MeshStandardMaterial({ color: 0x2a3a48, emissive: 0x10202a, emissiveIntensity: 0.5 }); const spine = new THREE.Mesh(spineGeo, spineMat); spine.position.y = 2.2; g.add(spine); _disposables.push(spineGeo, spineMat); g.userData.rungs = []; // filled live from cert fractions return g; } // place rungs along the spine from DERIVED log-fractions (called when cert arrives) function _updateBoundsLadder(g) { const THREE = _THREE; if (!g) return; // remove old rungs (g.userData.rungs || []).forEach((r) => { g.remove(r.mesh); if (r.label) g.remove(r.label); }); g.userData.rungs = []; if (!F.haveCert || F.landauerMult == null) return; // log10 scale: bottom = Landauer floor (the job is ~5e8× above it), top = Bremermann ceiling // Build rungs at fractional heights derived from the real numbers. const rungs = [ { name: "Landauer floor", y: 0.0, color: 0x2fd07a, note: "kT·ln2 — the job is " + _human(F.landauerMult) + "× above" }, { name: "MEASURED job", y: _clamp01(_logSpan(F.landauerMult, 1)), color: 0xe8c074, note: "5112 J DERIVED (P×t MEASURED)" }, { name: "Margolus-Levitin", y: _ceilHeight(F.mlFrac), color: 0x6fb1ff, note: "rate ceiling — job at " + _sci(F.mlFrac) + " of max" }, { name: "Bremermann", y: _ceilHeight(F.bremFrac), color: 0x9b8cff, note: "c²/h ceiling — job at " + _sci(F.bremFrac) }, { name: "Bekenstein", y: _ceilHeight(F.bekFrac), color: 0xff9d5c, note: "info ceiling — job at " + _sci(F.bekFrac) }, ]; rungs.forEach((rg) => { const geo = new THREE.BoxGeometry(0.5, 0.07, 0.5); const mat = new THREE.MeshStandardMaterial({ color: rg.color, emissive: rg.color, emissiveIntensity: 0.45, metalness: 0.3, roughness: 0.4 }); const mesh = new THREE.Mesh(geo, mat); mesh.position.y = rg.y * 4.2 + 0.05; g.add(mesh); _disposables.push(geo, mat); let label = null; try { label = _ctx.label.billboard(THREE, "MEASURED", { text: rg.name, scale: 0.34, position: [0.0, rg.y * 4.2 + 0.32, 0] }); // these rungs are DERIVED from measured scalars; keep the MEASURED chip honest g.add(label); } catch (_) {} g.userData.rungs.push({ mesh, label }); }); } function _logSpan(mult /* job/floor */, _ref) { // place the MEASURED job ~ log10(mult)/log10(top) of the way up the spine const top = 18; // ~ orders of magnitude up to the rate ceilings return Math.min(0.95, Math.log10(Math.max(1, mult)) / top); } function _ceilHeight(frac /* job/ceiling, tiny */) { if (frac == null || frac <= 0) return 0.97; // higher rung = smaller fraction (more headroom). map log10(1/frac) up the spine. const top = 50; return Math.min(0.99, 0.55 + Math.log10(1 / frac) / top); } function _clamp01(x) { return Math.max(0, Math.min(1, x)); } function _sci(x) { return (x == null) ? "—" : Number(x).toExponential(2); } function _human(x) { if (x == null) return "—"; if (x >= 1e9) return (x / 1e9).toFixed(1) + "e9"; if (x >= 1e6) return (x / 1e6).toFixed(1) + "e6"; return String(Math.round(x)); } // --------------------------------------------------------------------------- // HUD — the cert badge (MEASURED+SIGNED), cosign anchor, Rekor inclusion, backend // indicator, the measured-scalar chips, the bounds readout, residual status, and the // interactive controls (iso threshold / splats / arrows / residual displacement). // --------------------------------------------------------------------------- function _buildHUD() { const wrap = document.createElement("div"); wrap.className = "szl3d-pinn-hud"; Object.assign(wrap.style, { position: "absolute", left: "14px", top: "14px", zIndex: "6", display: "flex", flexDirection: "column", gap: "8px", maxWidth: "min(94%, 440px)", maxHeight: "calc(100% - 28px)", overflow: "auto", font: "12px ui-monospace,SFMono-Regular,Menlo,monospace", color: "#cfe0ea", }); const title = document.createElement("div"); title.style.cssText = "font:600 14px ui-sans-serif,system-ui;color:#eef3f6;letter-spacing:.4px"; title.textContent = "◇ PINN Thermal/Field · ray-march volume + MEASURED+SIGNED cert"; wrap.appendChild(title); const sub = document.createElement("div"); sub.style.cssText = "color:#9fb1bf;font-size:11px;line-height:1.45"; sub.textContent = "Modeled on Kitware VTK.js/VolView + three.js TSL compute. " + "Field render is MODELED (analytic PINN surrogate seeded by MEASURED telemetry). " + "Certificate is MEASURED + SIGNED."; wrap.appendChild(sub); // live badge row (filled by poll) const badge = _ctx.live.createBadge(); wrap.appendChild(badge.el); // backend indicator const back = document.createElement("div"); back.id = "pinn-backend"; back.style.cssText = "font-size:11px;color:#39d3c4"; back.textContent = "backend: " + (F.backend || "…"); wrap.appendChild(back); // ---- cert / signature panel ---- const cert = document.createElement("div"); cert.id = "pinn-cert"; cert.style.cssText = "border:1px solid #1d2a36;border-radius:8px;padding:9px 10px;background:#0a1117cc;display:flex;flex-direction:column;gap:6px"; cert.innerHTML = "