<!-- SOLID CITY · part 2857 -->
monospace;padding:5px 9px;pointer-events:auto;cursor:pointer;white-space:nowrap;';
  var FULL_CSS = 'position:fixed;bottom:148px;left:10px;z-index:9990;background:rgba(35,44,61,.88);border:1px solid rgba(216,168,51,.5);border-radius:8px;color:#eef3fa;font:10.5px/1.5 ui-monospace,Menlo,monospace;padding:6px 9px;pointer-events:auto;cursor:pointer;max-width:78vw;white-space:pre-line;text-align:left;';
  function makeChip() {
    if (chip || !document.body) return;
    chip = document.createElement('button');
    chip.type = 'button';
    chip.setAttribute('aria-label', 'Performance meter. Activate to open the full table; activate again to dock it.');
    chip.id = 'city-perf-chip';
    chip.style.cssText = DOCK_CSS;
    chip.title = 'Tap for the full table';
    chip.addEventListener('click', function () { expanded = !expanded; chip.style.cssText = expanded ? FULL_CSS : DOCK_CSS; if (expanded) reset(); draw(); });
    document.body.appendChild(chip);
    draw();
    chipTimer = setInterval(draw, 1000);
  }
  function draw() {
    if (!chip) return;
    var s = snapshot();
    if (!expanded) { chip.textContent = '📉 ' + s.fpsAvg + ' fps · p50 ' + s.frameP50Ms + 'ms · p95 ' + s.frameP95Ms + 'ms · ' + s.drawCalls + ' calls · ' + (s.triangles / 1000).toFixed(0) + 'k tris'; return; }
    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: public/city/perf-meter.js.bak-chip5-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 