<!-- SOLID CITY · part 2192 -->
icMaterial({ 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.1, s.z1 + 1.1); });
  mark(SB.x - 1.8, SB.z - 1.8, SB.x + 1.8, SB.z + 1.8);
  A.portals.forEach(function (p) { mark(p.x - 3.2, p.z - 3.2, p.x + 3.2, p.z + 3.2); });   // never stroll through a gate by accident
  grid = g;
}
function blocked(i, j) { return i < 0 || j < 0 || i >= N || j >= N || grid[j * N + i] === 1; }
function freeNear(i, j) { if (!blocked(i, j)) return [i, j]; for (var r = 1; r < 20; r++) for (var dj = -r; dj <= r; dj++) for (var di = -r; di <= r; di++) { if (Math.max(Math.abs(di), Math.abs(dj)) !== r) continue; if (!blocked(i + di, j + dj)) return [i + di, j + dj]; } return null; }
function los(a, b) { var dx = b.x - a.x, dz = b.z - a.z, l = Math.hypot(dx, dz), n = Math.ceil(l / 1); for (var k = 1; k < n; k++) { var t = k / n; if (blocked(ci(a.x + dx * t), ci(a.z + dz * t))) return false; } return true; }
function findPath(from, to) {
  if (!grid) buildGrid();
  var s = freeNear(ci(from.x), ci(from.z)), e = freeNear(ci(to.x), ci(to.z)); if (!s || !e) return null;
  var NN = N * N, gS = new Float32Array(NN).fill(Infinity), came = new Int32Array(NN).fill(-1), closed = 