<!-- SOLID CITY · part 2853 -->
  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,
      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:132px;left:8px;z-index:9990;background:rgba(35,44,61,.78);border:1px solid rgba(216,168,51,.38);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;';
    chip.title = 'Tap to reset the measurement window';
    chip.addEventListener('click', function () { reset(); draw(); });
  