<!-- SOLID CITY · part 2888 -->
 Avatar Forge —
 * and so the seven citizens' bodies — was written for a
 * younger three that owns CapsuleGeometry; r128 does not,
 * and every Forge build threw on its neck piece. Proved on
 * the bench: THREE.CapsuleGeometry is not a constructor.
 *
 * The bridge is the lathe craft: a capsule is a surface of
 * revolution — a semicircle, a straight run, a semicircle
 * — so where the geometry is missing, we GROW it from a
 * spun profile and hand it to the library under its true
 * name. Installed once, before any builder wakes; a newer
 * three that already owns the shape is left untouched.
 * ============================================================ */
(function () {
  'use strict';
  var T = window.THREE;
  if (!T) return;
  if (typeof T.CapsuleGeometry !== 'function' && typeof T.LatheGeometry === 'function') {
    /* r128's geometries are true classes, so the bridge is
     * a true subclass: the capsule IS a lathe whose profile
     * runs semicircle — straight — semicircle */
    class CapsuleGeometry extends T.LatheGeometry {
      constructor(radius, length, capSegments, radialSegments) {
        radius = radius || 1; length = length || 1;
        capSegments = Math.max(2, capSegments || 4); radialSegments = Math.max(6, radialSegments || 12);
        var pts = [], half = length / 2, i, a;
        for (i = 0; i <= capSegments; i++) { a = -Math.PI / 2 + (i / capSegments) * (Math.PI / 2); pts.push(new T.Vector2(Math.max(0.0001, radius * Math.cos(a)), -half + radius * Math.sin(a))); }
        pts.push(new T.Vector2(radius, half));
        for (i = 1; i <= capSegments; i++) { a = (i / capSegments) * (Math.PI / 2); pts.push(new T.Vector2(Math.max(0.0001, radius * Math.cos(a)), half + radius * Math.sin(a))); }
        super(pts, radialSegments);
      }
    }
    T.CapsuleGeometry = CapsuleGeometry;
    window.__r128shims = { capsule: true };
  }
})();


===== FILE: public/city/radar-find.js =====
/* Solid City — RADAR FINDER (window.CityRadarFind)
   Makes the bots easy to find on the compass/radar:
   - featured citizens (King Playa, Playa 2.0, Solid, Provolta, Soul Print, SABLE) get a distinct
     colour and a short label pill; the 3 nearest others get initials; everyone is labelled in the
     big map. Overlapping labels try other sides, then are skipped (no clutter).
   - tap the radar: picker of nearby citizens with Go / Follow (Watch) / Talk (48px targets),
     plus a bigger-map toggle; in the big map, tapping a dot picks that citizen.
   Wraps CityCitizens.radar (stock blips are still drawn first), so existing markers are unchanged. */
(function () {
'use strict';
if (!window.SC || !SC.api || !window.CityCitizens) return;
var A = SC.api, S = A.S, CZ = window.CityCitizens;
var FEAT = {
  kingplaya: { col: '#ffd700', lab: 'KING' }, playa2: { col: '#ff8a00', lab: 'P2' }, solid: { col: '#ff2d55', lab: 'SOLID' },
  provolta: { col: '#00e5ff', lab: 'PROV' }, soulprint: { col: '#a58bff', lab: 'SOUL' }, sable: { col: '#ff6fd8', lab: 'SABLE' }
};
var radarEl = document.getElementById('sc-radar'); if (!radarEl) return;
var big = false, dots = [], picker = null;
function $(id) { return document.getElementById(id); }
function esc(s) { return String(s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
function hex(c) { return '#' + ('000000' + c.toString(16)).slice(-6); }
function initials(n) { var w = String(n).split(/\s+/); return (w.length > 1 ? w[0][0] + w[1][0] : n.slice(0, 2)).toUpperCase(); }
function me() { return S.mode === 'walk' ? S.p : S.ship; }
function fmtD(d) { return d < 1000 ? Math.round(d) + ' m' : (d / 1000).toFixed(1) + ' km'; }
function colOf(c) { return FEAT[c.id] ? FEAT[c.id].col : hex(c.accent); }

/* ---- radar overlay ---- */
var stock = CZ.radar;
CZ.radar = function (g, blip, fl, sc, ax, az) {
  try { stock.apply(CZ, arguments); } catch (e) {}
  var W = g.canvas.width, H = g.canvas.height, R = W / 2 - 8, p = me(), list = [];
  dots = [];
  CZ.roster.forEach(function (c) {
    if (c.x == null || (fl && c.st !== 'fly' && !big)) return;
    var bx = W / 2 + (c.x - ax) * sc, by = H / 2 + (c.z - az) * sc, d = Math.hypot(bx - W / 2, by - H / 2);
    if (d > R) { bx = W / 2 + (bx - W / 2) / d * R; by = H / 2 + (by - H / 2) / d * R; }
    var it = { c: c, bx: bx, by: by, dist: Math.hypot(c.x - p.x, c.z - p.z) };
    dots.push(it); list.push(it);
  });
  list.sort(function (a, b) { return a.dist - b.dist; });
  var k = big ? 1.1 : W / 240, boxes = [[W / 2 - 9 * k, H / 2 - 12 * k, W / 2 + 9 * k, H / 2 + 9 * k]], fol = CZ.following && CZ.following();
  function free(b) { for (var q = 0; q < boxes.length; q++) { var o = boxes[q]; if (b[0] < o[2] && b[2] > o[0] && b[1] < o[3] && b[3] > o[1]) return false; } return b[0] >= 2 && b[2] <= W - 2 && b[1] >= 2 && b[3] <= H - 2; }
  function label(i, txt, col, pill) {
    var tw = g.measureText(txt).width, fh = parseInt(g.font.split(' ')[1], 