<!-- SOLID CITY · part 2858 -->
window per system and reports ticks, ms per
   * second, p95 and worst beside the frame's own numbers. */
  var systems = {};
  function system(name, ms) {
    if (!isFinite(ms) || ms < 0) return;
    var s = systems[name] || (systems[name] = { n: 0, total: 0, worst: 0, arr: [] });
    s.n++; s.total += ms; if (ms > s.worst) s.worst = ms;
    s.arr.push(ms); if (s.arr.length > WIN) s.arr.shift();
  }
  function pct(arr, f) {
    if (!arr.length) return 0;
    var s = arr.slice().sort(function (a, b) { return a - b; });
    return s[Math.floor((s.length - 1) * f)];
  }
  function snapshot() {
    var now = performance.now();
    var worst = 0;
    for (var i = 0; i < deltas.length; i++) if (deltas[i] > worst) worst = deltas[i];
    return {
      frames: frames,
      seconds: Math.round((now - since) / 100) / 10,
      frameP50Ms: Math.round(pct(deltas, 0.5) * 10) / 10,
      frameP95Ms: Math.round(pct(deltas, 0.95) * 10) / 10,
      frameWorstMs: Math.round(worst),
      fpsAvg: deltas.length ? Math.round(1000 / (deltas.reduce(function (a, b) { return a + b; }, 0) / deltas.length)) : 0,
      drawCalls: calls, triangles: tris, geometries: geos, textures: texs,
      heapMB: heapMB || null,
      heapStartMB: heapStart || null, heapPeakMB: heapPeak || null,
      longTasks: longCount,
      longTaskTotalMs: Math.round(longTotal),
      longTaskWorstMs: Math.round(longWorst),
      longTaskMsPerSec: Math.round(longTotal / Math.max(1, (now - since) / 1000) * 10) / 10,
      systems: (function () {
        var out = {}, secs = Math.max(1, (now - since) / 1000);
        for (var k in systems) {
          var s = systems[k];
          out[k] = { ticks: s.n, msPerSec: Math.round(s.total / secs * 10) / 10, p95Ms: Math.round(pct(s.arr, 0.95) * 100) / 100, worstMs: Math.round(s.worst * 100) / 100 };
        }
        return out;
      })()
    };
  }
  function reset() { deltas = []; frames = 0; since = performance.now(); longCount = 0; longTotal = 0; longWorst = 0; heapStart = heapMB; heapPeak = heapMB; systems = {}; }
  window.CityPerf = { snapshot: snapshot, reset: reset, system: system };

  function tick(stamp) {
    requestAnimationFrame(tick);
    if (lastStamp) {
      var d = stamp - lastStamp;
      if (d > 0 && d < 1000) { deltas.push(d); if (deltas.length > WIN) deltas.shift(); frames++; }
    }
    lastStamp = stamp;
    var R = A.renderer;
    if (R && R.info && R.info.render) {
      calls = R.info.render.calls; tris = R.info.render.triangles;
      if (R.info.memory) { geos = R.info.memory.geometries; texs = R.info.memory.textures; }
    }
    if (stamp - heapAt > 1000) {
      heapAt = stamp;
      if (performance.memory && performance.memory.usedJSHeapSize) {
        heapMB = Math.round(performance.memory.usedJSHeapSize / 1048576);
        if (!heapStart) heapStart = heapMB;
        if (heapMB > heapPeak) heapPeak = heapMB;
      }
    }
  }

  /* the chip — royal eyes only, in the instruments' family */
  var chip = null, chipTimer = 0;
  function makeChip() {
    if (chip || !document.body) return;
    chip = document.createElement('button');
    chip.type = 'button';
    chip.setAttribute('aria-label', 'Performance meter. Activate to reset the measurement window.');
    chip.id = 'city-perf-chip';
    chip.style.cssText = 'position:fixed;top:54px;left:8px;z-index:9990;background:rgba(35,44,61,.92);border:1px solid rgba(216,168,51,.55);border-radius:8px;color:#cfd8e6;font:10.5px/1.5 ui-monospace,Menlo,monospace;padding:4px 8px;pointer-events:auto;cursor:pointer;max-width:78vw;white-space:pre-line;';
    chip.title = 'Tap to reset the measurement window';
    chip.addEventListener('click', function () { reset(); draw(); });
    document.body.appendChild(chip);
    draw();
    chipTimer = setInterval(draw, 1000);
  }
  function draw() {
    if (!chip) return;
    var s = snapshot();
    var line = '📉 PERF frame p50 ' + s.frameP50Ms + 'ms · p95 ' + s.frameP95Ms + 'ms · worst ' + s.frameWorstMs + 'ms · ' + s.fpsAvg + ' fps · ' + s.drawCalls + ' calls · ' + (s.triangles / 1000).toFixed(0) + 'k tris' + (s.heapMB ? ' · heap ' + s.heapMB + 'MB (peak ' + s.heapPeakMB + ')' : '') + ' · long ' + s.longTasks + ' tasks ' + s.longTaskMsPerSec + 'ms/s worst ' + s.longTaskWorstMs + 'ms · ' + s.seconds + 's';
    for (var k in s.systems) { var y = s.systems[k]; line += '\nsys ' + k + ': ' + y.ticks + ' ticks · ' + y.msPerSec + 'ms/s · p95 ' + y.p95Ms + 'ms · worst ' + y.worstMs + 'ms'; }
    chip.textContent = line;
  }

  var waited = 0;
  var iv = setInterval(function () {
    waited++;
    if (A.isBuilt && A.isBuilt()) {
      clearInterval(iv);
      requestAnimationFrame(tick);
      var royalWait = 0;
      var rv = setInterval(function () {
        royalWait++;
        if (window.CityRoyal) { clearInterval(rv); makeChip(); }
        else if (royalWait > 20) clearInterval(rv);
      }, 500);
    } else if (waited > 120) clearInterval(iv);
  }, 250);
})();


===== FILE: publ