betterwithage Claude Opus 4.7 commited on
Commit
34baeda
·
verified ·
1 Parent(s): 76d6137

deploy(hf): sync szl-holdings/a11oy@main derived COPY set

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy main.
Files: 825 Pruned: 0
Derived from Dockerfile COPY sources (NO hand-maintained allowlist).

Signed-off-by: SZL Holdings <noreply@szlholdings.ai>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Files changed (1) hide show
  1. web/hologram.html +184 -16
web/hologram.html CHANGED
@@ -65,13 +65,38 @@
65
  padding:7px 12px;letter-spacing:.05em;background:linear-gradient(0deg,#03060ce0,#03060c00)}
66
  #foot code{color:var(--dim)}
67
  a{color:var(--teal);text-decoration:none}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  /* no-webgl fallback */
69
  #nogl{position:fixed;inset:0;display:none;place-items:center;text-align:center;padding:40px;
70
  font-family:var(--mono);color:var(--dim)}
71
  #nogl.show{display:grid}
72
- @media(max-width:760px){
73
- #title{max-width:90vw} #mind,#flow{width:min(92vw,300px)}
74
- #legend{display:none}
75
  }
76
  </style>
77
  </head>
@@ -88,15 +113,22 @@
88
  </div>
89
 
90
  <div class="hud" id="title">
91
- <h1>HOLOGRAPHIC COMMAND BRIDGE</h1>
92
  <div class="sub">
93
- The living <b>agentic-GPU organism</b> — RTX&nbsp;5000 @ betterwithage as the <b>MIND</b>,
94
- six proven round9 <b>organs</b> as the body, energy flow as its <b>circulation</b>.<br/>
95
- Reads the real endpoints; renders nothing it cannot confirm.
 
96
  </div>
97
  <span class="badge" id="conn">● connecting…</span>
98
  </div>
99
 
 
 
 
 
 
 
100
  <div class="hud" id="mind">
101
  <h2>MIND CORE — /code/healthz</h2>
102
  <div class="row"><span class="k">Posture</span><span class="v"><span class="pill grey" id="m_posture">unknown</span></span></div>
@@ -105,6 +137,14 @@
105
  <div class="row"><span class="k">Λ trust</span><span class="v" id="m_lambda">Conjecture&nbsp;1</span></div>
106
  </div>
107
 
 
 
 
 
 
 
 
 
108
  <div class="hud" id="flow">
109
  <h2>CIRCULATION — /v1/energy/budget</h2>
110
  <div class="row"><span class="k">Dominant source</span><span class="v" id="f_src">—</span></div>
@@ -113,6 +153,15 @@
113
  <div class="row"><span class="k">Bekenstein</span><span class="v" id="f_bek">—</span></div>
114
  </div>
115
 
 
 
 
 
 
 
 
 
 
116
  <div class="hud" id="legend">
117
  <h3>ENERGY SOURCES (flow color)</h3>
118
  <div class="lg-row"><span class="dot" style="color:var(--solar)"></span>solar
@@ -124,15 +173,19 @@
124
  <div class="lg-row"><span class="dot" style="color:var(--green)"></span>active&nbsp;(pulses)
125
  <span class="dot" style="color:var(--amber);margin-left:10px"></span>degraded
126
  <span class="dot" style="color:var(--grey);margin-left:10px"></span>unknown&nbsp;(unreachable — honest dim)</div>
 
 
 
 
127
  </div>
128
 
129
  <div id="tip"></div>
130
 
131
  <div class="hud" id="foot">
132
- Three.js r128 vendored locally — <b>0 runtime CDN</b>. Energy figures are <b>SAMPLE/ESTIMATE</b>
133
- (no power meter wired). Sovereign glow only when <code>/code/healthz</code> reports
134
- <code>sovereign:true</code>. Λ trust = <b>Conjecture 1</b> (advisory, never a theorem).
135
- Unreachable organs render dim — <b>no fabricated data</b>. Consent-only swarm.
136
  </div>
137
 
138
  <!-- THREE r128 (MIT), vendored locally at /vendor/three.min.js — NO CDN (matches fleet-c2.html). -->
@@ -178,6 +231,23 @@
178
  return 0x9aa6bd; // unknown source -> grid grey (honest neutral)
179
  }
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  // ---------- WebGL guard ----------
182
  if(!window.THREE || !(function(){try{var c=document.createElement("canvas");
183
  return !!(window.WebGLRenderingContext && (c.getContext("webgl")||c.getContext("experimental-webgl")));
@@ -196,7 +266,7 @@
196
  var renderer = new THREE.WebGLRenderer({canvas:canvas, antialias:true, alpha:true});
197
  renderer.setPixelRatio(Math.min(window.devicePixelRatio||1, 2));
198
  var scene = new THREE.Scene();
199
- scene.fog = new THREE.FogExp2(0x03060c, 0.018);
200
 
201
  var camera = new THREE.PerspectiveCamera(52, 1, 0.1, 400);
202
  camera.position.set(0, 9, 34);
@@ -311,9 +381,64 @@
311
  }
312
  buildSwarm(7); // default 7-node fabric; replaced by live count when available
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  // ---------- interaction: drag-rotate, wheel-zoom, hover organs ----------
315
  var ray = new THREE.Raycaster(), mouse = new THREE.Vector2();
316
- var rotY=0.0, rotX=0.0, dragging=false, lx=0, ly=0, autoRot=true, zoom=34;
 
 
 
 
 
 
 
 
 
 
 
 
317
  canvas.addEventListener("pointerdown", function(e){ dragging=true; autoRot=false; lx=e.clientX; ly=e.clientY; });
318
  window.addEventListener("pointerup", function(){ dragging=false; });
319
  window.addEventListener("pointermove", function(e){
@@ -322,8 +447,8 @@
322
  // hover tooltip
323
  mouse.x=(e.clientX/window.innerWidth)*2-1; mouse.y=-(e.clientY/window.innerHeight)*2+1;
324
  ray.setFromCamera(mouse, camera);
325
- var hit = ray.intersectObjects(organMeshes.map(function(g){return g.userData.node;}));
326
  var tip=$("tip");
 
327
  if(hit.length){
328
  var g = organMeshes.filter(function(gg){return gg.userData.node===hit[0].object;})[0];
329
  var u=g.userData, st=u.status;
@@ -332,9 +457,20 @@
332
  '<div class="d">'+esc(u.organ.role)+'</div>'+
333
  '<div class="st"><span style="color:'+stc+'">●</span> '+esc(st)+' · <span style="color:var(--dim)">'+esc(u.organ.note)+'</span></div>';
334
  tip.style.left=(e.clientX+14)+"px"; tip.style.top=(e.clientY+12)+"px"; tip.style.opacity=1;
335
- } else tip.style.opacity=0;
 
 
 
 
 
 
 
 
 
 
 
336
  });
337
- canvas.addEventListener("wheel", function(e){ e.preventDefault(); zoom=Math.max(16,Math.min(70, zoom+e.deltaY*0.02)); }, {passive:false});
338
 
339
  function resize(){
340
  var w=window.innerWidth, h=window.innerHeight;
@@ -347,6 +483,7 @@
347
  function tick(now){
348
  var t=(now-t0)/1000;
349
  if(autoRot) rotY += 0.0016;
 
350
  // camera framing from rot + zoom
351
  camera.position.x = Math.sin(rotY)*zoom*Math.cos(rotX);
352
  camera.position.z = Math.cos(rotY)*zoom*Math.cos(rotX);
@@ -389,6 +526,15 @@
389
  flowGeo.attributes.position.needsUpdate=true; flowGeo.attributes.color.needsUpdate=true;
390
 
391
  swarmGrp.rotation.y += 0.0006;
 
 
 
 
 
 
 
 
 
392
  renderer.render(scene,camera);
393
  requestAnimationFrame(tick);
394
  }
@@ -461,9 +607,31 @@
461
  g.userData.status = (ok===true)?"active":(ok===false?"degraded":"unknown");
462
  }
463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  function pollData(){
465
  getJSON(API+"/code/healthz").then(applyPosture);
466
  getJSON(API+"/v1/energy/budget").then(applyBudget);
 
467
  // probe each organ endpoint; honest dim on null. Never blocks the scene.
468
  organMeshes.forEach(function(g){
469
  getJSON(g.userData.organ.probe).then(function(j){
 
65
  padding:7px 12px;letter-spacing:.05em;background:linear-gradient(0deg,#03060ce0,#03060c00)}
66
  #foot code{color:var(--dim)}
67
  a{color:var(--teal);text-decoration:none}
68
+ /* council (Ayllu) card — mid right, under MIND */
69
+ #council{top:190px;right:22px;width:300px;border:1px solid var(--line);border-radius:14px;
70
+ background:linear-gradient(180deg,#0a1626dd,#060d18ee);padding:13px 16px;pointer-events:auto;backdrop-filter:blur(4px)}
71
+ #council h2{margin:0 0 8px;font-size:11px;letter-spacing:.16em;color:var(--dim);font-weight:600}
72
+ #council .row{display:flex;justify-content:space-between;font-size:12px;padding:3px 0;border-bottom:1px dashed #13203660}
73
+ #council .row:last-child{border-bottom:0}
74
+ #council .k{color:var(--dim)} #council .v{color:var(--ink);text-align:right}
75
+ #council .who{margin-top:7px;font-size:10.5px;color:var(--violet);line-height:1.5;word-spacing:1px}
76
+ /* lab (PINN research) card — mid left, under TITLE */
77
+ #lab{top:150px;left:22px;width:292px;border:1px solid var(--line);border-radius:14px;
78
+ background:linear-gradient(180deg,#0a1626dd,#060d18ee);padding:13px 16px;pointer-events:auto;backdrop-filter:blur(4px)}
79
+ #lab h2{margin:0 0 3px;font-size:11px;letter-spacing:.16em;color:var(--dim);font-weight:600}
80
+ #lab .rig{font-size:10.5px;color:var(--teal);margin-bottom:7px;line-height:1.5}
81
+ #lab .row{display:flex;justify-content:space-between;font-size:12px;padding:3px 0;border-bottom:1px dashed #13203660}
82
+ #lab .row:last-child{border-bottom:0}
83
+ #lab .k{color:var(--dim)} #lab .v{color:var(--ink);font-family:var(--mono)}
84
+ .measured{display:inline-block;margin-left:6px;padding:1px 6px;border:1px solid #123a2c;border-radius:6px;
85
+ font-size:9px;letter-spacing:.06em;color:var(--green);background:#08241a}
86
+ /* zoom preset bar — top center */
87
+ #zoombar{top:16px;left:50%;transform:translateX(-50%);display:flex;gap:6px;pointer-events:auto;
88
+ background:#070f1ccc;border:1px solid var(--line);border-radius:999px;padding:5px 6px;backdrop-filter:blur(3px)}
89
+ #zoombar button{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;color:var(--dim);
90
+ background:transparent;border:0;border-radius:999px;padding:5px 12px;cursor:pointer;transition:.15s}
91
+ #zoombar button:hover{color:var(--ink)}
92
+ #zoombar button.on{color:var(--bg0);background:var(--teal);font-weight:600}
93
  /* no-webgl fallback */
94
  #nogl{position:fixed;inset:0;display:none;place-items:center;text-align:center;padding:40px;
95
  font-family:var(--mono);color:var(--dim)}
96
  #nogl.show{display:grid}
97
+ @media(max-width:900px){
98
+ #title{max-width:90vw} #mind,#flow,#council,#lab{width:min(92vw,300px)}
99
+ #legend,#council,#lab{display:none}
100
  }
101
  </style>
102
  </head>
 
113
  </div>
114
 
115
  <div class="hud" id="title">
116
+ <h1>SZL ESTATE — HOLOGRAPHIC BRIDGE</h1>
117
  <div class="sub">
118
+ The living <b>agentic-GPU organism</b>, zoomed out to the whole estate — RTX&nbsp;5000 @ betterwithage as the <b>MIND</b>,
119
+ six proven <b>organs</b> as the body, the <b>Ayllu council</b> as its deliberating minds,
120
+ and your <b>hardware &amp; stack</b> as the surrounding estate.<br/>
121
+ Live nodes glow; declared nodes are labelled; nothing is fabricated.
122
  </div>
123
  <span class="badge" id="conn">● connecting…</span>
124
  </div>
125
 
126
+ <div class="hud" id="zoombar">
127
+ <button data-zoom="council">COUNCIL</button>
128
+ <button data-zoom="organism" class="on">ORGANISM</button>
129
+ <button data-zoom="estate">ESTATE</button>
130
+ </div>
131
+
132
  <div class="hud" id="mind">
133
  <h2>MIND CORE — /code/healthz</h2>
134
  <div class="row"><span class="k">Posture</span><span class="v"><span class="pill grey" id="m_posture">unknown</span></span></div>
 
137
  <div class="row"><span class="k">Λ trust</span><span class="v" id="m_lambda">Conjecture&nbsp;1</span></div>
138
  </div>
139
 
140
+ <div class="hud" id="council">
141
+ <h2>COUNCIL — /v1/ayllu/roster</h2>
142
+ <div class="row"><span class="k">Personas</span><span class="v" id="cl_n">—</span></div>
143
+ <div class="row"><span class="k">Backend</span><span class="v" id="cl_be">—</span></div>
144
+ <div class="row"><span class="k">Loop</span><span class="v">bounded · Λ-gated</span></div>
145
+ <div class="who" id="cl_who">convening the core council…</div>
146
+ </div>
147
+
148
  <div class="hud" id="flow">
149
  <h2>CIRCULATION — /v1/energy/budget</h2>
150
  <div class="row"><span class="k">Dominant source</span><span class="v" id="f_src">—</span></div>
 
153
  <div class="row"><span class="k">Bekenstein</span><span class="v" id="f_bek">—</span></div>
154
  </div>
155
 
156
+ <div class="hud" id="lab">
157
+ <h2>LAB — PhysicsNeMo PINN<span class="measured">MEASURED</span></h2>
158
+ <div class="rig">rosie · RTX&nbsp;5050 (Blackwell sm_120)<br/>PhysicsNeMo 2.1.1 · torch 2.12.1+cu130</div>
159
+ <div class="row"><span class="k">Poisson rel-L2</span><span class="v">2.99e-4</span></div>
160
+ <div class="row"><span class="k">Burgers rel-L2</span><span class="v">0.644</span></div>
161
+ <div class="row"><span class="k">Duffing α err</span><span class="v">1.2e-5</span></div>
162
+ <div class="row"><span class="k">Snapshot</span><span class="v" style="color:var(--dim)">2026-07-02 · local</span></div>
163
+ </div>
164
+
165
  <div class="hud" id="legend">
166
  <h3>ENERGY SOURCES (flow color)</h3>
167
  <div class="lg-row"><span class="dot" style="color:var(--solar)"></span>solar
 
173
  <div class="lg-row"><span class="dot" style="color:var(--green)"></span>active&nbsp;(pulses)
174
  <span class="dot" style="color:var(--amber);margin-left:10px"></span>degraded
175
  <span class="dot" style="color:var(--grey);margin-left:10px"></span>unknown&nbsp;(unreachable — honest dim)</div>
176
+ <h3 style="margin-top:9px">SHELLS (zoom out)</h3>
177
+ <div class="lg-row"><span class="dot" style="color:var(--violet)"></span>Ayllu council&nbsp;(minds)
178
+ <span class="dot" style="color:var(--gold);margin-left:10px"></span>estate&nbsp;— hardware &amp; stack</div>
179
+ <div class="lg-row" style="color:var(--grey)">estate nodes are <b style="color:var(--dim)">declared</b> (no live probe) unless marked</div>
180
  </div>
181
 
182
  <div id="tip"></div>
183
 
184
  <div class="hud" id="foot">
185
+ Three.js r128 vendored locally — <b>0 runtime CDN</b>. Energy figures are <b>SAMPLE/ESTIMATE</b>;
186
+ PINN results are a <b>MEASURED</b> local snapshot (not live). Council is read live from <code>/v1/ayllu/roster</code>.
187
+ Estate nodes are <b>declared</b> (no live probe) unless marked. Sovereign glow only when <code>/code/healthz</code> reports
188
+ <code>sovereign:true</code>. Λ trust = <b>Conjecture 1</b> (advisory). Unreachable organs render dim — <b>no fabricated data</b>.
189
  </div>
190
 
191
  <!-- THREE r128 (MIT), vendored locally at /vendor/three.min.js — NO CDN (matches fleet-c2.html). -->
 
231
  return 0x9aa6bd; // unknown source -> grid grey (honest neutral)
232
  }
233
 
234
+ // ---- Ayllu council (deliberating minds) — the 3 core personas as an honest
235
+ // fallback; the live count/names come from /v1/ayllu/roster when reachable.
236
+ var COUNCIL_FALLBACK = ["Amaru","Kamachiq","Qhatuq"];
237
+ var COUNCIL_COL = 0xb98cff;
238
+
239
+ // ---- Estate shell: Rosa's real hardware & stack. These are DECLARED nodes
240
+ // (no per-node live probe) unless noted — labelled honestly, never faked.
241
+ var ESTATE = [
242
+ {id:"rosie · RTX 5050", col:0x34f5b0, kind:"GPU · research", note:"Blackwell sm_120 — PINN lab (PhysicsNeMo)", live:false},
243
+ {id:"betterwithage · RTX 5000", col:0x39d7ff, kind:"GPU · sovereign", note:"the MIND core — inference (probed at centre)", live:true},
244
+ {id:"Hetzner · prod box", col:0xffd66e, kind:"compute · host", note:"alloy-api + agents · /opt/alloyscape", live:false},
245
+ {id:"Neon · Postgres 17", col:0x5ad6ff, kind:"data · database", note:"managed Postgres — ledgers & state", live:false},
246
+ {id:"Hugging Face · Spaces", col:0xffcb57, kind:"edge · deploy", note:"a11oy / anatomy / killinchu / immune", live:false},
247
+ {id:"Backblaze B2 · backups", col:0x9aa6bd, kind:"storage · DR", note:"daily pg_dump safety net", live:false}
248
+ ];
249
+ var ESTATE_COL_DEFAULT = 0xffd66e;
250
+
251
  // ---------- WebGL guard ----------
252
  if(!window.THREE || !(function(){try{var c=document.createElement("canvas");
253
  return !!(window.WebGLRenderingContext && (c.getContext("webgl")||c.getContext("experimental-webgl")));
 
266
  var renderer = new THREE.WebGLRenderer({canvas:canvas, antialias:true, alpha:true});
267
  renderer.setPixelRatio(Math.min(window.devicePixelRatio||1, 2));
268
  var scene = new THREE.Scene();
269
+ scene.fog = new THREE.FogExp2(0x03060c, 0.010); // gentler so the estate shell stays readable when zoomed out
270
 
271
  var camera = new THREE.PerspectiveCamera(52, 1, 0.1, 400);
272
  camera.position.set(0, 9, 34);
 
381
  }
382
  buildSwarm(7); // default 7-node fabric; replaced by live count when available
383
 
384
+ // ============ COUNCIL RING (Ayllu — deliberating minds) ============
385
+ var COUNCIL_R = 15.5;
386
+ var councilGrp = new THREE.Group(); scene.add(councilGrp);
387
+ var councilNodes = [];
388
+ function buildCouncil(names){
389
+ if(!councilGrp) return; // no-op in the no-WebGL path
390
+ while(councilGrp.children.length) councilGrp.remove(councilGrp.children[0]);
391
+ councilNodes = [];
392
+ names = (names && names.length) ? names : COUNCIL_FALLBACK;
393
+ names.forEach(function(nm,i){
394
+ var a=(i/names.length)*Math.PI*2 + 0.5;
395
+ var m=new THREE.Mesh(new THREE.TetrahedronGeometry(0.7,0),
396
+ new THREE.MeshStandardMaterial({color:COUNCIL_COL, emissive:COUNCIL_COL,
397
+ emissiveIntensity:0.4, metalness:0.3, roughness:0.4, transparent:true, opacity:0.8}));
398
+ m.position.set(Math.cos(a)*COUNCIL_R, Math.sin(i*1.3)*1.6, Math.sin(a)*COUNCIL_R);
399
+ m.userData={hover:{title:esc(nm), role:"Ayllu council persona", note:"bounded, Λ-gated deliberation", status:"council"}};
400
+ councilGrp.add(m); councilNodes.push(m);
401
+ });
402
+ }
403
+ buildCouncil(COUNCIL_FALLBACK);
404
+
405
+ // ============ ESTATE SHELL (hardware & stack — declared) ============
406
+ var ESTATE_R = 25;
407
+ var estateGrp = new THREE.Group(); scene.add(estateGrp);
408
+ var estateNodes = [];
409
+ ESTATE.forEach(function(o,i){
410
+ var a=(i/ESTATE.length)*Math.PI*2;
411
+ var g=new THREE.Group();
412
+ var col = o.col || ESTATE_COL_DEFAULT;
413
+ var box=new THREE.Mesh(new THREE.BoxGeometry(1.1,1.1,1.1),
414
+ new THREE.MeshStandardMaterial({color:col, emissive:col,
415
+ emissiveIntensity:o.live?0.4:0.16, metalness:0.5, roughness:0.4,
416
+ transparent:true, opacity:o.live?0.85:0.5, flatShading:true}));
417
+ var ring=new THREE.Mesh(new THREE.TorusGeometry(1.5,0.045,6,32),
418
+ new THREE.MeshBasicMaterial({color:col, transparent:true, opacity:o.live?0.4:0.18}));
419
+ ring.rotation.x=Math.PI/2;
420
+ g.add(box); g.add(ring);
421
+ g.position.set(Math.cos(a)*ESTATE_R, Math.sin(i*1.9)*2.6, Math.sin(a)*ESTATE_R);
422
+ box.userData={hover:{title:esc(o.id), role:esc(o.kind), note:esc(o.note),
423
+ status:o.live?"live (probed at centre)":"declared — no live probe"}};
424
+ estateGrp.add(g); estateNodes.push(box);
425
+ });
426
+
427
  // ---------- interaction: drag-rotate, wheel-zoom, hover organs ----------
428
  var ray = new THREE.Raycaster(), mouse = new THREE.Vector2();
429
+ var rotY=0.0, rotX=0.0, dragging=false, lx=0, ly=0, autoRot=true, zoom=34, zoomTarget=34;
430
+ var ZOOM_PRESET={council:22, organism:34, estate:80};
431
+ (function(){
432
+ var bar=$("zoombar"); if(!bar) return;
433
+ bar.addEventListener("click", function(e){
434
+ var b=e.target.closest("button"); if(!b) return;
435
+ var z=ZOOM_PRESET[b.getAttribute("data-zoom")]; if(z==null) return;
436
+ zoomTarget=z;
437
+ if(b.getAttribute("data-zoom")==="estate") autoRot=true; // let the estate slowly reveal itself
438
+ Array.prototype.forEach.call(bar.querySelectorAll("button"),function(x){x.classList.remove("on");});
439
+ b.classList.add("on");
440
+ });
441
+ })();
442
  canvas.addEventListener("pointerdown", function(e){ dragging=true; autoRot=false; lx=e.clientX; ly=e.clientY; });
443
  window.addEventListener("pointerup", function(){ dragging=false; });
444
  window.addEventListener("pointermove", function(e){
 
447
  // hover tooltip
448
  mouse.x=(e.clientX/window.innerWidth)*2-1; mouse.y=-(e.clientY/window.innerHeight)*2+1;
449
  ray.setFromCamera(mouse, camera);
 
450
  var tip=$("tip");
451
+ var hit = ray.intersectObjects(organMeshes.map(function(g){return g.userData.node;}));
452
  if(hit.length){
453
  var g = organMeshes.filter(function(gg){return gg.userData.node===hit[0].object;})[0];
454
  var u=g.userData, st=u.status;
 
457
  '<div class="d">'+esc(u.organ.role)+'</div>'+
458
  '<div class="st"><span style="color:'+stc+'">●</span> '+esc(st)+' · <span style="color:var(--dim)">'+esc(u.organ.note)+'</span></div>';
459
  tip.style.left=(e.clientX+14)+"px"; tip.style.top=(e.clientY+12)+"px"; tip.style.opacity=1;
460
+ } else {
461
+ // council + estate nodes carry their own honest hover card
462
+ var xh = ray.intersectObjects(councilNodes.concat(estateNodes));
463
+ if(xh.length && xh[0].object.userData.hover){
464
+ var h=xh[0].object.userData.hover;
465
+ var col=(xh[0].object.material&&xh[0].object.material.color)?xh[0].object.material.color.getHexString():"dbe7ff";
466
+ tip.innerHTML='<div class="t" style="color:#'+col+'">'+h.title+'</div>'+
467
+ '<div class="d">'+h.role+'</div>'+
468
+ '<div class="st"><span style="color:var(--dim)">●</span> '+h.status+' · <span style="color:var(--dim)">'+h.note+'</span></div>';
469
+ tip.style.left=(e.clientX+14)+"px"; tip.style.top=(e.clientY+12)+"px"; tip.style.opacity=1;
470
+ } else tip.style.opacity=0;
471
+ }
472
  });
473
+ canvas.addEventListener("wheel", function(e){ e.preventDefault(); zoomTarget=Math.max(16,Math.min(90, zoomTarget+e.deltaY*0.03)); }, {passive:false});
474
 
475
  function resize(){
476
  var w=window.innerWidth, h=window.innerHeight;
 
483
  function tick(now){
484
  var t=(now-t0)/1000;
485
  if(autoRot) rotY += 0.0016;
486
+ zoom += (zoomTarget - zoom) * 0.08; // smooth glide between zoom presets
487
  // camera framing from rot + zoom
488
  camera.position.x = Math.sin(rotY)*zoom*Math.cos(rotX);
489
  camera.position.z = Math.cos(rotY)*zoom*Math.cos(rotX);
 
526
  flowGeo.attributes.position.needsUpdate=true; flowGeo.attributes.color.needsUpdate=true;
527
 
528
  swarmGrp.rotation.y += 0.0006;
529
+ // council minds — slow counter-orbit, each tetra spins + breathes
530
+ councilGrp.rotation.y -= 0.0012;
531
+ councilNodes.forEach(function(m,i){
532
+ m.rotation.y += 0.02; m.rotation.x += 0.012;
533
+ m.material.emissiveIntensity = 0.35 + Math.abs(Math.sin(t*1.1 + i))*0.35;
534
+ });
535
+ // estate shell — gentle drift; each node slowly tumbles
536
+ estateGrp.rotation.y += 0.0004;
537
+ estateNodes.forEach(function(m){ m.rotation.y += 0.01; });
538
  renderer.render(scene,camera);
539
  requestAnimationFrame(tick);
540
  }
 
607
  g.userData.status = (ok===true)?"active":(ok===false?"degraded":"unknown");
608
  }
609
 
610
+ function applyCouncil(j){
611
+ var nEl=$("cl_n"), beEl=$("cl_be"), whoEl=$("cl_who");
612
+ if(!j){ // roster unreachable -> honest fallback to the declared core 3
613
+ if(nEl) nEl.textContent="3 (core · declared)";
614
+ if(beEl) beEl.innerHTML='<span style="color:var(--grey)">roster unreachable</span>';
615
+ if(whoEl) whoEl.textContent=COUNCIL_FALLBACK.join(" · ");
616
+ buildCouncil(COUNCIL_FALLBACK);
617
+ return;
618
+ }
619
+ var people = j.personas || j.roster || [];
620
+ if(!Array.isArray(people)) people=[];
621
+ var names = people.map(function(p){ return (p && (p.name||p.persona||p.id)) || String(p); }).filter(Boolean);
622
+ if(!names.length) names=COUNCIL_FALLBACK;
623
+ var be = (j.backend && (j.backend.mode||j.backend.status)) || j.backend || j.mode || "—";
624
+ var beLive = /live/i.test(String(be));
625
+ if(nEl) nEl.textContent=String(j.count!=null?j.count:names.length);
626
+ if(beEl) beEl.innerHTML='<span style="color:'+(beLive?"var(--green)":"var(--amber)")+'">'+esc(String(be))+'</span>';
627
+ if(whoEl) whoEl.textContent=names.join(" · ");
628
+ buildCouncil(names);
629
+ }
630
+
631
  function pollData(){
632
  getJSON(API+"/code/healthz").then(applyPosture);
633
  getJSON(API+"/v1/energy/budget").then(applyBudget);
634
+ getJSON(API+"/v1/ayllu/roster").then(applyCouncil);
635
  // probe each organ endpoint; honest dim on null. Never blocks the scene.
636
  organMeshes.forEach(function(g){
637
  getJSON(g.userData.organ.probe).then(function(j){