Spaces:
Running
Running
feat(mesh): live 45-kernel observability — per-kernel status colours (green/amber/red from /api/<organ>/v3/kernels), heartbeat pulse on each dot, last-5-heartbeats in kernel side panel, 45-kernel signed-receipt ticker. ADDITIVE. Doctrine v11 749/14/163. Sign: Yachay
320c18f verified | // Kernel orbits — 7 universal kernel dots + 2 vertical kernel dots orbit each | |
| // chakra. Click a kernel dot -> side panel shows its docs + recent activity polled | |
| // from the live flagship. Top-HUD status badges per chakra polled from /healthz | |
| // every 30s (green/amber/red). Doctrine v11 LOCKED. ZERO BANDAID. | |
| import { CHAKRAS, UNIVERSAL_KERNELS, GOLD } from './config.js'; | |
| import { openKernelPanel } from './codex.js'; | |
| import { kernelStatus, consumePulse } from './heartbeats.js'; | |
| // status → emissive tint applied on top of the chakra/gold base colour | |
| const STATUS_COLOR = { green:0x34d399, amber:0xfbbf24, red:0xf87171 }; | |
| let THREEref; | |
| const orbiters = []; // {mesh, chakraId, kernel, radius, speed, phase, tilt, vertical} | |
| export function buildKernels(scene, THREE, chakraGroups, _openCodex){ | |
| THREEref = THREE; | |
| buildBadges(); | |
| CHAKRAS.forEach(c => { | |
| const g = chakraGroups[c.id]; | |
| if (!g) return; | |
| const col = new THREE.Color(c.color); | |
| // 7 universal kernels on inner orbit | |
| UNIVERSAL_KERNELS.forEach((k, i) => { | |
| const dot = kernelDot(THREE, col, false); | |
| addOrbiter(scene, g, dot, c, k, 3.4, 0.5 + i*0.07, (i/7)*Math.PI*2, (i%2?0.4:-0.4), false); | |
| }); | |
| // 2 vertical kernels on outer orbit, slightly different glow (gold-tinted) | |
| c.vertical.forEach((k, i) => { | |
| const dot = kernelDot(THREE, new THREE.Color(GOLD), true); | |
| addOrbiter(scene, g, dot, c, k, 4.6, 0.35 + i*0.05, (i*Math.PI), 0.9, true); | |
| }); | |
| }); | |
| } | |
| function kernelDot(THREE, col, vertical){ | |
| const m = new THREE.Mesh( | |
| new THREE.SphereGeometry(vertical ? 0.22 : 0.17, 12, 12), | |
| new THREE.MeshStandardMaterial({ color:col, emissive:col, emissiveIntensity: vertical?1.0:0.7, roughness:0.3 })); | |
| if (vertical){ | |
| const halo = new THREE.Mesh(new THREE.TorusGeometry(0.34,0.025,6,16), | |
| new THREE.MeshBasicMaterial({ color:GOLD, transparent:true, opacity:0.7 })); | |
| m.add(halo); | |
| } | |
| return m; | |
| } | |
| function addOrbiter(scene, parentGroup, dot, chakra, kernel, radius, speed, phase, tilt, vertical){ | |
| parentGroup.add(dot); | |
| dot.userData.kernelClick = () => openKernelPanel(chakra, kernel, vertical); | |
| // remember the dot's resting emissive so a heartbeat pulse can flare then settle | |
| dot.userData.baseEmissive = dot.material.emissiveIntensity; | |
| dot.userData.pulse = 0; | |
| orbiters.push({ mesh:dot, chakra, kernel, radius, speed, phase, tilt, vertical }); | |
| } | |
| let _statusTick = 0; | |
| export function updateKernels(t, dt){ | |
| // refresh per-kernel status colour ~3x/sec (cheap; registry is updated by heartbeats.js) | |
| const doStatus = (++_statusTick % 20) === 0; | |
| for (const o of orbiters){ | |
| const a = t * o.speed + o.phase; | |
| o.mesh.position.set( | |
| Math.cos(a) * o.radius, | |
| Math.sin(a*0.7 + o.tilt) * o.radius * 0.35, | |
| Math.sin(a) * o.radius | |
| ); | |
| const m = o.mesh; | |
| if (doStatus){ | |
| const st = kernelStatus(o.chakra.id, o.kernel.name); | |
| const tint = STATUS_COLOR[st] || 0x6b7280; | |
| m.material.emissive.setHex(tint); | |
| // red kernels dim out; healthy ones keep their glow | |
| m.userData.restGlow = (st === 'red') ? 0.25 : (o.vertical ? 1.0 : 0.7); | |
| } | |
| // heartbeat pulse: a fresh tick flares the dot then eases back to rest glow | |
| if (consumePulse(o.chakra.id, o.kernel.name)) m.userData.pulse = 1.0; | |
| m.userData.pulse = Math.max(0, (m.userData.pulse || 0) - (dt || 0.016) * 2.0); | |
| const rest = (m.userData.restGlow != null) ? m.userData.restGlow : (o.vertical ? 1.0 : 0.7); | |
| m.material.emissiveIntensity = rest + m.userData.pulse * 1.6; | |
| m.scale.setScalar(1 + m.userData.pulse * 0.5); | |
| } | |
| } | |
| // ---------------- top-HUD status badges ---------------- | |
| function buildBadges(){ | |
| const hud = document.getElementById('topHUD'); | |
| const allBtn = document.getElementById('allCodices'); | |
| CHAKRAS.forEach(c => { | |
| const b = document.createElement('button'); | |
| b.className = 'badge'; b.id = 'badge-'+c.id; | |
| b.setAttribute('aria-label', c.label+' status — tap to open codex'); | |
| b.innerHTML = `<span class="dot" id="dot-${c.id}"></span>${c.label}`; | |
| b.onclick = () => import('./codex.js').then(m => m.openChakraCodex(c.id)); | |
| hud.insertBefore(b, allBtn); | |
| }); | |
| } | |
| export async function pollStatus(){ | |
| await Promise.all(CHAKRAS.map(async c => { | |
| const dot = document.getElementById('dot-'+c.id); | |
| if (!dot) return; | |
| try { | |
| const r = await fetch(c.health.url, { cache:'no-store' }); | |
| if (r.ok){ | |
| let ok = true; | |
| try { const j = await r.json(); ok = (j.status === 'ok' || j.status === undefined); } catch(e){} | |
| dot.className = 'dot ' + (ok ? 'green' : 'amber'); | |
| } else dot.className = 'dot amber'; | |
| } catch(e){ dot.className = 'dot red'; } | |
| })); | |
| setTimeout(pollStatus, 30000); | |
| } | |