<!-- SOLUV · part 486 -->
y \u00b7 ' + (s.state === 'live' ? 'LIVE \u00b7 ' : '') + s.text + ' \u00b7 tower ' + i.widthWorld + '\u00d7' + Math.round(i.heightWorld);
    statusEl.dataset.state = s.state;
  }

  function resize() { const w = cssW(), h = cssH(); renderer.setSize(w, h, false); camera.aspect = w / h; camera.updateProjectionMatrix(); need = true; }
  const ro = new ResizeObserver(resize); ro.observe(container); ro.observe(canvas); resize();

  const ease = t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
  let frames = 0, alive = true, lastT = 0, lastDraw = 0;
  const _q = new THREE.Vector3();
  function tick(now) {
    if (!alive) return; requestAnimationFrame(tick);
    const dt = Math.min(0.1, (now - (lastT || now)) / 1000); lastT = now;
    if (anim) { const t = Math.min(1, (now - anim.t0) / anim.ms), e = ease(t); for (const k of ['x', 'y', 'z', 'pitch']) st[k] = anim.a[k] + (anim.b[k] - anim.a[k]) * e; let dy = anim.b.yaw - anim.a.yaw; dy = Math.atan2(Math.sin(dy), Math.cos(dy)); st.yaw = anim.a.yaw + dy * e; applyCamera(); if (t >= 1) anim = null; }
    if (rideGoal !== null && !anim && !rise) { const y0 = st.y, d = rideGoal - st.y; st.y += Math.abs(d) < 1e-3 ? d : d * (1 - Math.exp(-dt * 12)); if (reading) scrollClamp(); applyCamera(); if (Math.abs(rideGoal - st.y) < 1e-3 || Math.abs(st.y - y0) < 1e-6) rideGoal = null; }
    if (reading && fling && !ptr.size && !anim) { st.y += fling * dt; fling *= Math.pow(0.04, dt); if (Math.abs(fling) < 0.05) fling = 0; scrollClamp(); applyCamera(); }
    if (rise) { const t = Math.min(1, (now - rise.t0) / rise.ms); Object.assign(st, risePose(t)); applyCamera(); if (t >= 1) { rise = null; setOn(); } }
    // ambient: stars twinkle, rings turn, light climbs the tower (about 30 fps when nothing else moves; off with ?still=1)
    if (!STILL && !document.hidden && now - lastDraw > 33) need = true;
    if (!need) return; need = false; lastDraw = now;
    if (!STILL) { uT.value = now / 1000; rings.children.forEach(g => { g.rotation.y += dt * 0.05 * g.userData.spin; }); crown.children.forEach(o => { if (o.userData.tilt) { o.rotation.y += dt * 0.2; o.rotation.x = 0.12 * Math.sin(now / 2400); } }); }
    beacon.position.set(0, topY + 18, 0);
    for (const g of [rings, crown]) g.traverse(o => { if (o.userData.billboard) { o.getWorldPosition(_q); const p = o.parent; const a = Math.atan2(camera.position.x - _q.x, camera.position.z - _q.z); o.rotation.y = a - (p.rotation.y || 0); } });
    wall.wall.update(camera, renderer);
    renderer.render(scene, camera); frames++;
    updateHud();
  }

  // start: the base looking up (the King's favourite), or ?view=
  const startView = qs.get('view') === 'read' ? 'live' : qs.get('view');
  if (startView === 'live') setReading(true);
  if (startView === 'rise') { Object.assign(st, views.base()); applyCamera(); setTimeout(() => { if (!rise) startRise(); }, 600); }
  else if (startView && views[startView]) {
    Object.assign(st, views[startView]()); applyCamera();
    if (startView === 'live') { const reaim = () => { if (wall.wall.info().lines > 0 || wall.wall.status.state === 'error') { wall.removeEventListener('status', reaim); if (!anim && !rise && !userMoved) { Object.assign(st, views.live()); applyCamera(); } } }; wall.addEventListener('status', reaim); }
  }
  else { Object.assign(st, views.base()); applyCamera(); }
  refreshStatus();
  requestAnimationFrame(tick);

  const api = {
    THREE, scene, camera, renderer, walls, wall, views, MINIMUM,
    setView(vv) { userMoved = true; stopMotion(); Object.assign(st, typeof vv === 'function' ? vv() : vv); applyCamera(); },
    get view() { return { ...st }; }, get reading() { return reading; }, wrapEm, READ_PPE, setReading, flyTo, stopMotion, startRise, get rising() { return !!rise; }, risePose,
    get frames() { return frames; }, readyMs: Math.round(performance.now() - t0), get topY() { return topY; },
    async settle(ms = 6000) {
      const end = performance.now() + ms;
      while (performance.now() < end) { need = true; await new Promise(r => setTimeout(r, 150)); const t = wall.wall.thrift(); if (t.pending === 0 && wall.wall.status.state !== 'loading' && wall.wall.status.state !== 'idle' || !t.started && t.wallPpe < 0.8) { need = true; await new Promise(r => setTimeout(r, 300)); return true; } }
      return false;
    },
    lawCheck() {   // every solid mesh >= MINIMUM on its smallest axis (text glyphs: em = MINIMUM); the sky dome and points are not solids
      scene.updateMatrixWorld(true);
      const box = new THREE.Box3(), sz = new THREE.Vector3(); let minObject = null; const violations = [];
      scene.traverse(o => { if (!o.isMesh || o.userData.text || o === sky) return; box.setFromObject(o); box.getSize(sz); const m = Math.min(sz.x, sz.y, sz.z); const rec = { name: o.name || (o.parent && o.parent.name), min: +m.toFixed(4) }; if (!minObject || m < minObject.min) minObject = rec; if (m < MINIMUM - 1e-6) violations.push(rec); });
      const wl