<!-- SOLUV · part 446 -->
 c++) { atlas.get(String.fromCharCode(c), false); atlas.get(String.fromCharCode(c), true); }
  for (const ch of '‘’“”—–…·éèêàâçôöüïñ★●') atlas.get(ch, false);
  for (let c = 32; c < 127; c++) atlas.get(String.fromCharCode(c), 2);
  atlas.get('\u265b', true); atlas.get('⚜', 2);

  // text batch: one instanced draw call
  const STRIDE = 23;
  const textMat = new THREE.ShaderMaterial({ vertexShader: TEXT_VS, fragmentShader: TEXT_FS, transparent: true, depthTest: false, depthWrite: false,
    uniforms: { uAtlas: { value: atlas.tex }, uRight: { value: new THREE.Vector3(1, 0, 0) }, uUp: { value: new THREE.Vector3(0, 1, 0) } },
    blending: THREE.CustomBlending, blendSrc: THREE.OneFactor, blendDst: THREE.OneMinusSrcAlphaFactor });
  const faceMat = textMat.clone(); faceMat.depthTest = true; faceMat.uniforms.uAtlas.value = atlas.tex;
  function makeBatch(mat, order) {
    const data = new Float32Array(MAX_GLYPHS * STRIDE);
    const ib = new THREE.InstancedInterleavedBuffer(data, STRIDE, 1); ib.setUsage(THREE.DynamicDrawUsage);
    const g = new THREE.InstancedBufferGeometry();
    g.setAttribute('position', new THREE.BufferAttribute(new Float32Array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0]), 3)); g.setIndex([0, 1, 2, 0, 2, 3]);
    [['aAnchor', 3, 0], ['aRect', 4, 3], ['aUV', 4, 7], ['aColor', 4, 11], ['aSG', 2, 15], ['aR', 3, 17], ['aU', 3, 20]].forEach(([n, k, o]) => g.setAttribute(n, new THREE.InterleavedBufferAttribute(ib, k, o)));
    g.instanceCount = 0; const mesh = new THREE.Mesh(g, mat); mesh.frustumCulled = false; mesh.renderOrder = order; world.add(mesh);
    return { data, ib, g, n: 0 };
  }
  const faceBatch = makeBatch(faceMat, 9), capBatch = makeBatch(textMat, 10);

  const ptsMat = new THREE.ShaderMaterial({ vertexShader: PTS_VS, fragmentShader: PTS_FS, transparent: true, depthWrite: false, depthTest: true,
    blending: THREE.CustomBlending, blendSrc: THREE.OneFactor, blendDst: THREE.OneMinusSrcAlphaFactor,
    uniforms: { uFocal: { value: 500 }, uDpr: { value: dpr }, uMax: { value: 8 } } });
  const glowMat = new THREE.ShaderMaterial({ vertexShader: GLOW_VS, fragmentShader: GLOW_FS, transparent: true, depthWrite: false, depthTest: true,
    blending: THREE.CustomBlending, blendSrc: THREE.OneFactor, blendDst: THREE.OneFactor,
    uniforms: { uFocal: { value: 500 }, uDpr: { value: dpr }, uTime: { value: 0 } } });
  let glow = null;
  const linkMat = new THREE.LineBasicMaterial({ color: 0x4a4038 });
  const outlineMat = new THREE.LineBasicMaterial({ color: 0x48e6ff }); // thin luminous edges
  const ruleMat = new THREE.LineBasicMaterial({ color: 0x123c4c });
  // a rule across the face that wraps 1 layout unit (= MINIMUM) round both front corners, so it has real depth
  function wrapRule(dst, p, ly, off) {
    const pts = [[-0.3, -1], [-0.3, off], [p.w + 0.3, off], [p.w + 0.3, -1]].map(([lx, o]) => { const q = wp(p, lx, ly, o); return [q.x, q.y, q.z]; });
    dst.push(...pts[0], ...pts[1], ...pts[1], ...pts[2], ...pts[2], ...pts[3]);
  }
  { // pillars (boxes in the dense room's ink), frames (gilded for locked pillars), ground + court rings, activity glow at each cap
    const vpos = [], ipos = [], opos = [], gpos = [], spos = [], ssz = [], vband = [], vbandOn = [], aring = [], aringOn = [];
    let vi = 0;
    for (const p of sel) {
      const hw = p.w / 2 + 0.3, hd = (p.d || 8) / 2, H = p.h + p.capH, bx = p.bx, by = p.by;
      const c = (u, v, z) => [bx + p.rx * u + p.nx * v, by + p.ry * u + p.ny * v, z];
      const P8 = [c(-hw, hd, 0), c(hw, hd, 0), c(hw, hd, H), c(-hw, hd, H), c(-hw, -hd, 0), c(hw, -hd, 0), c(hw, -hd, H), c(-hw, -hd, H)];
      for (const q of P8) vpos.push(...q);
      for (const f of [[0, 1, 2, 3], [5, 4, 7, 6], [1, 5, 6, 2], [4, 0, 3, 7], [3, 2, 6, 7]]) ipos.push(vi + f[0], vi + f[1], vi + f[2], vi + f[0], vi + f[2], vi + f[3]);
      vi += 8;
      const dst = p.locked ? gpos : opos; // all 12 edges of the pillar (gilded when sealed): a 3-D frame, never a flat one
      for (const [a, b] of [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]]) dst.push(...P8[a], ...P8[b]);
      wrapRule(dst, p, p.hdr - 0.6, 0.12); // rule under the headline block, wrapping round the column's front corners
      { // reserved video band: a 5-wide grid of 16:9 tile frames in the capital (up to maxCams); bright only when the room is a video room
        const B = p.band, dst2 = p.mode === 'video' ? vbandOn : vband;
        const rect = (x0, y0, x1, y1) => { // a shallow 3-D tile frame: front and back rims 1 unit (= MINIMUM) apart, joined at the corners
          const fr = o => [[x0, y0], [x1, y0], [x1, y1], [x0, y1]].map(([u, v]) => { const q = wp(p, u, v, o); return [q.x, q.y, q.z]; });
          const a = fr(1.14), b = fr(0.14); for (let k = 0; k < 4; k++) { dst2.push(...a[k], ...a[(k + 1) % 4], ...b[k], ...b[(k + 1) % 4], ...a[k], ...b[k]); } };
        for (let i = 0; i < p.maxCams; i++) { const c = i % B.cols, r = (i