<!-- SOLID CITY · part 2173 -->
tion (p) {
    var me = flying() ? S.ship : S.p, b = document.createElement('button'); b.type = 'button'; b.className = 'hc-chip';
    b.innerHTML = esc(p.name) + ' <small>' + fmtDist(Math.hypot(p.x - me.x, p.z - me.z)) + '</small>';
    b.addEventListener('click', function () { hideCard(); H.focus = p; driveTo(p, 'AI DRIVE'); });
    box.appendChild(b);
  });
  if (!flying()) {
    var fx = document.createElement('p'); fx.className = 'hc-meta'; fx.textContent = 'Volcanoes and islands are flight destinations: board a ship at the spaceport, LAUNCH, then tap 🤖 again.'; card.querySelector('.hc-card-b').appendChild(fx);
  }
}
function glossary() {
  showCard('Shared language', 'SOLID CITY', '<dl class="hc-gl">' + GLOSS.map(function (g) { return '<dt>' + esc(g[0]) + '</dt><dd>' + esc(g[1]) + '</dd>'; }).join('') + '</dl>', []);
}

/* ---------------- 3D markers (beacons, path line, scan ring, labels) ---------------- */
var beamGeo = new T.CylinderGeometry(1, 1, 1, 10, 1, true); beamGeo.translate(0, 0.5, 0);
var ringGeo = new T.RingGeometry(0.94, 1, 96);
var matGold = A.curve(new T.MeshBasicMaterial({ color: 0xffd27a, transparent: true, opacity: 0.55, depthWrite: false, side: T.DoubleSide }));
var matRed = A.curve(new T.MeshBasicMaterial({ color: 0xff2a40, transparent: true, opacity: 0.6, depthWrite: false, side: T.DoubleSide }));
var matRing = A.curve(new T.MeshBasicMaterial({ color: 0xffd27a, transparent: true, opacity: 0.8, depthWrite: false, side: T.DoubleSide }));
var matPath = A.curve(new T.MeshBasicMaterial({ color: 0xffd27a, transparent: true, opacity: 0.8, depthWrite: false, side: T.DoubleSide }));
var texCache = {};
function label(name, sub, sx) {
  var k = name + '|' + sub, tx = texCache[k] || (texCache[k] = A.labelTex(name, sub || ''));
  var sp = new T.Sprite(new T.SpriteMaterial({ map: tx, sizeAttenuation: false, depthTest: false, depthWrite: false, transparent: true, fog: false }));
  sp.scale.set(sx || 0.2, (sx || 0.2) / 4, 1); sp.renderOrder = 999; return sp;
}
var focusG = new T.Group(); focusG.visible = false; A.world.add(focusG);
var fBeam = new T.Mesh(beamGeo, matGold), fRing = new T.Mesh(ringGeo, matRed); fRing.rotation.x = -Math.PI / 2; focusG.add(fBeam); focusG.add(fRing);
function beacon(p, sec) {
  var big = flying(), gy = big ? Math.max(0, A.heightAt(p.x, p.z)) : Y;
  focusG.position.set(p.x, gy + 0.3, p.z);
  fBeam.scale.set(big ? 30 : 0.9, big ? 3000 : 70, big ? 30 : 0.9); fRing.scale.setScalar(big ? 400 : 6);
  focusG.visible = true; H.beaconT = sec || 3;
}
var pathG = null;
function clearPath() { if (pathG) { A.world.remove(pathG); pathG.children.forEach(function (m) { m.geometry.dispose(); }); pathG = null; } }
function showPath(pts) {
  clearPath(); pathG = new T.Group();
  var pos = [], w = 0.7, yy = Y + 0.24;
  for (var i = 1; i < pts.length; i++) {
    var a = pts[i - 1], b = pts[i], dx = b.x - a.x, dz = b.z - a.z, l = Math.hypot(dx, dz) || 1, nx = -dz / l * w, nz = dx / l * w;
    pos.push(a.x + nx, yy, a.z + nz, a.x - nx, yy, a.z - nz, b.x + nx, yy, b.z + nz, a.x - nx, yy, a.z - nz, b.x - nx, yy, b.z - nz, b.x + nx, yy, b.z + nz);
  }
  var g = new T.BufferGeometry(); g.setAttribute('position', new T.Float32BufferAttribute(pos, 3));
  var ribbon = new T.Mesh(g, matPath); ribbon.renderOrder = 2; pathG.add(ribbon);
  var end = pts[pts.length - 1], er = new T.Mesh(ringGeo.clone(), matRed); er.rotation.x = -Math.PI / 2; er.scale.setScalar(2.4); er.position.set(end.x, yy + 0.02, end.z); pathG.add(er);
  A.world.add(pathG);
}
var scanG = new T.Group(); A.world.add(scanG);
var scanRing = new T.Mesh(ringGeo, matRing); scanRing.rotation.x = -Math.PI / 2; scanRing.visible = false; A.world.add(scanRing);
function clearScan() { while (scanG.children.length) { var c = scanG.children.pop(); if (c.material && c.material.isSpriteMaterial) c.material.dispose(); c.children && c.children.forEach(function (k) { if (k.material && k.material.isSpriteMaterial) k.material.dispose(); }); } H.scan = null; }

/* ---------------- walking path-finding (grid A* over the solids) ---------------- */
// HALF 1290: the grid also covers Solid City University (campus-map.js): roads + campus pads are walkable outside the city disc
var CELL = 3, HALF = window.CampusMap ? 1290 : 810, N = Math.ceil(HALF * 2 / CELL), grid = null;
function ci(v) { return Math.floor((v + HALF) / CELL); }
function cc(i) { return -HALF + (i + 0.5) * CELL; }
function buildGrid() {
  var g = new Uint8Array(N * N), i, j;
  var CM = window.CampusMap;
  for (j = 0; j < N; j++) for (i = 0; i < N; i++) { var x = cc(i), z = cc(j); if (x * x + z * z > 785 * 785 && !(CM && CM.walk(x, z))) g[j * N + i] = 1; }
  function mark(x0, z0, x1, z1) { for (var jj = Math.max(0, ci(z0)); jj <= Math.min(N - 1, ci(z1)); jj++) for (var ii = Math.max(0, ci(x0)); ii <= Math.min(N - 1, ci(x1)); ii++) g[jj * N + ii] = 1; }
  A.solids.forEach(function (s) { if (s.y0 < Y + 2.2 && s.y1 > Y + 0.3) mark(s.x0 - 1.1, s.z0 - 1.1, s.x1 + 1