<!-- SOLID CITY · part 3092 -->
 = new T.Mesh(gemGeo, gemMat); gm.position.set(sx, 0.15 + sh, sz);
    gm.userData.baseY = 0.15 + sh; gm.userData.phase = i * 0.83;
    root.add(gm); gems.push(gm);
    linePts.push(new T.Vector3(sx, 0.15 + sh, sz));
  }
  // Faint constellation thread joining the lights
  var lines = new T.Line(new T.BufferGeometry().setFromPoints(linePts),
    new T.LineBasicMaterial({ color: C.violet, transparent: true, opacity: 0.22 }));
  root.add(lines);

  root.userData = {
    footprint: [], center: { x: 0, z: 0 }, radius: 12, height: 4, // nothing to collide; plinths are tiny
    update: function (t) {
      for (var k = 0; k < gems.length; k++) {
        var gm = gems[k];
        gm.position.y = gm.userData.baseY + Math.sin(t * 0.8 + gm.userData.phase) * 0.12;
        gm.rotation.y = t * 0.6 + k;
      }
    }
  };
  return root;
}
export default createRoostKeel;


===== FILE: public/city/safe-md.js =====
/* safe-md.js: a tiny, SAFE markdown renderer for Solid City University library entries (browser + Node).
   Everything is HTML-escaped first; only a fixed set of tags is produced (h3-h5, p, hr, strong, em, code, pre, ul, ol, li,
   blockquote, a). Links must be http(s) and get rel="noopener nofollow ugc" target="_blank". No raw HTML, ever. */
(function (root) {
'use strict';
function esc(s) { return String(s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
var URL_OK = /^https?:\/\/[^\s<>"'`]+$/i;
function link(href, text) { return URL_OK.test(href) ? '<a href="' + href + '" rel="noopener nofollow ugc" target="_blank">' + text + '</a>' : text; }
function inline(t) {   // t is already escaped
  var codes = [];
  t = t.replace(/`([^`\n]{1,200})`/g, function (m, c) { codes.push(c); return '\u0000' + (codes.length - 1) + '\u0000'; });
  t = t.replace(/\[([^\]\n]{1,200})\]\((https?:\/\/[^\s)]{1,500})\)/gi, function (m, txt, href) { return link(href, txt); });
  t = t.replace(/(^|[\s(])(https?:\/\/[^\s<]{3,500}[^\s<.,;:!?)])/gi, function (m, pre, href) { return pre + link(href, href); });
  t = t.replace(/\*\*([^*\n]{1,300})\*\*/g, '<strong>$1</strong>').replace(/(^|[^*\w])\*([^*\n]{1,300})\*(?!\w)/g, '$1<em>$2</em>');
  return t.replace(/\u0000(\d+)\u0000/g, function (m, i) { return '<code>' + codes[+i] + '</code>'; });
}
function render(md) {
  var lines = esc(String(md || '').replace(/\r\n?/g, '\n')).split('\n'), out = [], i = 0, para = [];
  function flush() { if (para.length) { out.push('<p>' + inline(para.join(' ')) + '</p>'); para = []; } }
  while (i < lines.length) {
    var L = lines[i];
    if (/^```/.test(L)) { flush(); var code = []; i++; while (i < lines.length && !/^```/.test(lines[i])) code.push(lines[i++]); i++; out.push('<pre><code>' + code.join('\n') + '</code></pre>'); continue; }
    var h = /^(#{1,3})\s+(.+)$/.exec(L); if (h) { flush(); var n = h[1].length + 2; out.push('<h' + n + '>' + inline(h[2]) + '</h' + n + '>'); i++; continue; }
    if (/^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/.test(L)) { flush(); out.push('<hr>'); i++; continue; }   // section rule
    if (/^\s*[-*]\s+/.test(L) || /^\s*\d+[.)]\s+/.test(L)) {
      flush(); var ord = /^\s*\d/.test(L), items = [];
      while (i < lines.length && (ord ? /^\s*\d+[.)]\s+/ : /^\s*[-*]\s+/).test(lines[i])) items.push('<li>' + inline(lines[i++].replace(/^\s*(?:[-*]|\d+[.)])\s+/, '')) + '</li>');
      out.push((ord ? '<ol>' : '<ul>') + items.join('') + (ord ? '</ol>' : '</ul>')); continue;
    }
    if (/^&gt;\s?/.test(L)) { flush(); var q = []; while (i < lines.length && /^&gt;\s?/.test(lines[i])) q.push(lines[i++].replace(/^&gt;\s?/, '')); out.push('<blockquote>' + inline(q.join(' ')) + '</blockquote>'); continue; }
    if (!L.trim()) { flush(); i++; continue; }
    para.push(L.trim()); i++;
  }
  flush(); return out.join('\n');
}
var api = { render: render, esc: esc };
if (typeof module !== 'undefined' && module.exports) module.exports = api; else root.SafeMD = api;
})(typeof window !== 'undefined' ? window : this);


===== FILE: public/city/sanctuary-place.js =====
/* Solid City — places PROVOLTA'S SANCTUARY (public/city/sanctuary-provolta.js) on the east cliff.
   Lazy: imported ~2.5 s after the city is built; any import/build error is caught and the city carries on.
   Adds: collision (userData.footprint), a radar marker 'PROV', a talk point at the centre (Provolta's own
   citizen persona via CityCitizens.open('provolta')), a Hyper Controller stop with narration, a Watch
   (GRAND TOUR) caption, and the pool/orb animation. No-build zone: forge-core.js ZONES (shared with the server). */
(function () {
'use strict';
if (!window.SC || !SC.api) return;
var A = SC.api, S = A.S, Y0 = A.CITY_Y;
var POS = { x: 735, z: 165 }, NAME = 'Provolta\u2019s Sanctuary', TALK_R = 9;
var M = { state: 'idle', group: null, wait: 2.5, said: false };
function addBox(cx, cz, w, d, rot, y0, y1) {   // rotated local box -> axis-aligned cells (city solids are AABBs)
 