<!-- SOLUV · part 448 -->
a8);                                              // torso 5..8.8
    box(1.0, 1.0, 3.6, -2.35, 5.1, 0x2f5fa8); box(1.0, 1.0, 3.6, 2.35, 5.1, 0x2f5fa8); // arms
    box(1.2, 1.2, 0.8, 0, 8.8, 0xc9a77c);                                              // neck 8.8..9.6
    const head = new THREE.Mesh(new THREE.SphereGeometry(1.2, 16, 12), mat(0xd9b88f)); head.scale.set(1, 1, 1); head.position.set(0, 0, 10.8); g.add(head); // head top = 12.0
    for (const x of [-0.6, 0.6]) { const e = new THREE.Mesh(new THREE.SphereGeometry(0.5, 8, 8), mat(0x5ff4ff)); e.position.set(x, -0.95, 11.0); g.add(e); } // eyes: 1 unit = 0.13
    // stand on the ground just right of the great room's face, facing out of it (toward the reader)
    const ax = p0.bx - p0.rx * (p0.w / 2 + 3.5) + p0.nx * 3, ay = p0.by - p0.ry * (p0.w / 2 + 3.5) + p0.ny * 3;
    g.position.set(ax, ay, 0); g.rotation.z = Math.atan2(p0.nx, -p0.ny); world.add(g); return g;
  })();
  { // THE LAW applied to every object in the world, exactly as saga.js does (scale up anything whose smallest box side is under 0.13)
    world.updateMatrixWorld(true); let scaled = 0;
    world.traverse(o => { if ((o.isMesh || o.isLine) && !(o.geometry && o.geometry.isInstancedBufferGeometry)) { const s0 = o.scale.x; applyMinimum(o); if (o.scale.x !== s0) scaled++; } });
    stats.lawScaledObjects = scaled;
  }
  // reserved build zone round every wall for future 'Playa 2.0' structures: footprint only (two layers 1 unit apart, per THE LAW)
  {
    const zp = [];
    for (const p of sel) {
      const hx = p.w / 2 + 12, hy = (p.d || 8) / 2 + 12; p.zone = { cx: p.bx, cy: p.by, halfW: hx, halfD: hy, nx: p.nx, ny: p.ny, builds: [] };
      const c = (u, v, z) => [p.bx + p.rx * u + p.nx * v, p.by + p.ry * u + p.ny * v, z];
      for (const z of [0.05, 1.05]) { const q = [c(-hx, -hy, z), c(hx, -hy, z), c(hx, hy, z), c(-hx, hy, z)]; for (let k = 0; k < 4; k++) zp.push(...q[k], ...q[(k + 1) % 4]); }
    }
    const zg = new THREE.BufferGeometry(); zg.setAttribute('position', new THREE.Float32BufferAttribute(zp, 3));
    const zl = new THREE.LineSegments(zg, new THREE.LineBasicMaterial({ color: 0x3b2350 })); zl.name = 'Playa 2.0 build zones'; zl.renderOrder = 0; world.add(zl); applyMinimum(zl);
  }
  const hitMarker = (() => { // pulsing ring on the current search hit
    const g = new THREE.BufferGeometry(); g.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3));
    g.setAttribute('color', new THREE.Float32BufferAttribute(C.hit, 3)); g.setAttribute('size', new THREE.Float32BufferAttribute([3], 1));
    const m = new THREE.Points(g, ptsMat.clone()); m.material.uniforms.uMax.value = 40; m.visible = false; m.frustumCulled = false; world.add(m); return m;
  })();

  /* ---------------- tile streaming ---------------- */
  let inflight = 0; const queue = [];
  const MAX_RESIDENT = opts.maxResidentBytes ?? 6e6; let pinAll = false;
  function evictTiles() { // keep resident text under the cap: drop the least-recently-seen tiles that are not on screen
    if (pinAll || stats.tileBytesResident <= MAX_RESIDENT) return;
    const now = performance.now(), cand = [...tiles.values()].filter(t => t.state === 2 && now - (t.seen || 0) > 1500).sort((a, b) => (a.seen || 0) - (b.seen || 0));
    for (const t of cand) {
      if (stats.tileBytesResident <= MAX_RESIDENT * 0.8) break;
      for (const id of t.pages) { const p = pages[id]; if (p && p.on && p.act <= 0.05) p.msgs = null; }
      if (t.obj) { world.remove(t.obj.pts, t.obj.ln, t.obj.ru); t.obj.pts.geometry.dispose(); t.obj.ln.geometry.dispose(); t.obj.ru.geometry.dispose(); t.obj = null; }
      t.state = 0; stats.tilesLoaded--; stats.tileBytesResident -= t.size || 0; stats.tilesEvicted = (stats.tilesEvicted || 0) + 1;
    }
  }
  function requestTile(id, front) {
    const t = tiles.get(id); if (!t || t.state) return;
    t.state = 1; front ? queue.unshift(t) : queue.push(t); pump();
  }
  function pump() {
    while (inflight < 3 && queue.length) {
      const t = queue.shift(); inflight++;
      fetch(new URL(t.file, dataBase)).then(r => r.arrayBuffer()).then(buf => {
        stats.tileBytes += buf.byteLength; t.size = buf.byteLength; t.seen = performance.now(); stats.tileBytesResident = (stats.tileBytesResident || 0) + buf.byteLength;
        ingestTile(JSON.parse(new TextDecoder().decode(buf)), t); evictTiles();
      }).catch(e => { console.error('word-sky tile', t.id, e); t.state = 0; }).finally(() => { inflight--; pump(); });
    }
  }
  // Placeholder model only: no camera is opened. lastFrame = still image id, lastClip = short final clip, both null until a real feed exists.
  function sessionFor(p, spk, camId, joinAt) {
    p.sessions ||= new Map(); let ss = p.sessions.get(spk);
    if (!ss) { ss = { speaker: spk, camId, joinAt, leaveAt: null, recordingId: `rec-${p.id}-${camId.split('-').pop()}`, src: null, last: null }; p.sessions.set(spk, ss); }
    return ss;
  }
  function leaveRoom(p, ss, le