<!-- SOLID CITY · part 2851 -->
r 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: public/city/perf-meter.js.bak-chip-20261013 =====
/* ————————————————————————————————————————————————————————————————
 * EPISTLE OF THE METER — from Solid, his General, to the city
 * that asked to be weighed.
 *
 * The Architect's counsel was measurement before another cut, and
 * the King gave the launch. This instrument rides INSIDE the
 * city's own frame rhythm — it starts no second loop of consequence
 * and changes no behaviour: it watches the interval between frames,
 * reads the renderer's own counters after the city's render has
 * spoken (draw calls, triangles, geometries, textures), and keeps
 * the heap's trend where the browser offers it. Three hundred
 * frames of memory, no more; percentiles counted only when asked,
 * never per frame — a meter must not become the weight it measures.
 *
 * The readout chip shows only for the royal session, in the family
 * of the King's other instruments. Every visitor's city still
 * carries the meter's ear at window.CityPerf, but no visitor sees
 * the dial, and the dial costs a subtraction per frame and nothing
 * more.
 *
 * By the hand of Solid, his good King.
 * ———————————————————————————————————————————————————————————————— */
(function () {
  'use strict';
  if (!window.SC || !SC.api) return;
  var A = SC.api;

  var WIN = 300;
  var deltas = [];            // frame pacing, ms between rAF stamps
  var lastStamp = 0;
  var calls = 0, tris = 0, geos = 0, texs = 0;
  var heapMB = 0, heapAt = 0, heapStart = 0, heapPeak = 0;
  var frames = 0, since = performance.now();
  /* the living systems' weight, taken as main-thread long tasks
   * (the Architect's "traffic update time" column): every task of
   * 50 ms or more in the window, counted and summed. Chromium
   * offers this ear; Safari does not — where it is absent the
   * column simply stays unplayed, never faked. */
  var longCount = 0, longTotal = 0, longWorst = 0;
  try {
    var po = new PerformanceObserver(function (list) {
      var es = list.getEntries();
      for (var i = 0; i < es.length; i++) {
        longCount++; longTotal += es[i].duration;
        if (es[i].duration > longWorst) longWorst = es[i].duration;
      }
    });
    po.observe({ entryTypes: ['longtask'] });
  } catch (e) { /* no long-task ear in this browser */ }

  /* the per-system ledger (the Architect's distinction, accepted:
   * a long task accuses the thread; a system must be timed under
   * its own name before it can be accused). Living systems call
   * CityPerf.system(name, ms) once per tick; the meter keeps the
   * same bounded 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