<!-- SOLID CITY · part 2186 -->
ur.wait = 1.2;
}
function tourNext() {
  var t = H.tour; t.i++;
  if (t.i >= TOUR.length) { H.tour = null; setMode('WALK'); narrate('Tour complete. You\u2019ve seen ' + TOUR.length + ' places. Board a ship at the spaceport to see the islands from the air.', 9); return; }
  var p = P(TOUR[t.i]), tour = t;
  if (!startWalk(p, 'AI DRIVE', 'Stop ' + (t.i + 1) + ' of ' + TOUR.length + ' on the tour.')) { H.tour = null; return; }
  H.tour = tour;
}
function tourArrived(p) {
  setMode('AI DRIVE'); H.focus = p; beacon(p, 4);
  arrived(p.name, esc(p.text || ''));
  H.tour.wait = 6;
}

/* ---------------- FOCUS / SCAN ---------------- */
function focusNearest() {
  var fl = flying(), me = fl ? S.ship : S.p, fx, fz;
  if (fl) { var f = A.shipVectors().f; fx = f.x; fz = f.z; } else { fx = -Math.sin(S.camYaw); fz = -Math.cos(S.camYaw); }
  var fl2 = Math.hypot(fx, fz) || 1; fx /= fl2; fz /= fl2;
  var list = fl ? FLY : POIS, best = null, bs = Infinity, near = null, nd = Infinity;
  list.forEach(function (p) {
    var dx = p.x - me.x, dz = p.z - me.z, d = Math.hypot(dx, dz);
    if (d < nd && d > (fl ? 300 : 3)) { nd = d; near = p; }
    if (d < (fl ? 300 : 3)) return;
    var ang = Math.acos(clamp((dx * fx + dz * fz) / d, -1, 1)), sc = ang * 10 + d * (fl ? 0.0005 : 0.02);
    if (ang < (fl ? 0.5 : 0.6) && sc < bs) { bs = sc; best = p; }
  });
  var p = best || near; if (!p) return null;
  H.focus = p; beacon(p, 3); poiCard(p); return p;
}
function scan() {
  clearScan();
  var fl = flying(), me = fl ? S.ship : S.p, R = fl ? 45000 : 600, list = fl ? FLY.filter(function (p) { return !p.home; }).concat([{ name: 'Orbital Spaceport', short: 'SPACEPORT', x: PT.x, z: PT.z }]) : POIS, marks = [];
  list.forEach(function (p) {
    var d = Math.hypot(p.x - me.x, p.z - me.z); if (d > R) return;
    var gy = fl ? Math.max(0, A.heightAt(p.x, p.z)) : Y, h = fl ? 2600 : 14, r = fl ? 22 : 0.45;
    var bm = new T.Mesh(beamGeo, matRed); bm.scale.set(r, h, r); bm.position.set(p.x, gy, p.z); scanG.add(bm);
    var lb = label((p.short || p.name).toUpperCase(), fmtDist(d), fl ? 0.24 : 0.2); lb.position.set(p.x, gy + h + (fl ? 120 : 2.5), p.z); lb.userData.top = gy + h + (fl ? 120 : 2.5); scanG.add(lb);
    marks.push({ x: p.x, z: p.z, t: p.short || p.name });
  });
  if (!fl) { scanRing.position.set(S.p.x, Y + 0.3, S.p.z); scanRing.visible = true; scanRing.scale.setScalar(2); matRing.opacity = 0.8; }
  H.scan = { marks: marks, t: fl ? 20 : 12, age: 0, fly: fl };
  H.prevMode = H.mode === 'SCAN' ? H.prevMode : H.mode; setMode('SCAN');
  H.scanT = 3;
  narrate('Scan: ' + marks.length + ' point' + (marks.length === 1 ? '' : 's') + ' of interest within ' + fmtDist(R) + (marks.length ? ' (' + esc(marks.slice(0, 5).map(function (m) { return m.t; }).join(', ')) + (marks.length > 5 ? '\u2026' : '') + ')' : '') + '. Marked on the radar.', 5);
}

/* ---------------- 👋 guide ---------------- */
function guide() {
  if (flying()) { narrate('SABLE is in the Forum Plaza. Tap 🤖 then "Home" to land, or say "fly me to Solid City".'); return; }
  if (S.mode !== 'walk') return;
  if (H.nav && H.nav.p.id === 'sable') { stopAI(); return; }
  var p = P('sable'), d = Math.hypot(S.p.x - p.x, S.p.z - p.z);
  hideCard();
  if (d < 8) { narrate('This is <b>SABLE</b>, the city\u2019s guide. Ask her anything about Solid City.', 5); A.openTalk(); return; }
  narrate('Taking you to <b>SABLE</b>, the city\u2019s guide.', 4);
  startWalk(p, 'AI DRIVE', 'She can answer anything about Solid City.', function () { narrate('This is <b>SABLE</b>. Ask her anything about Solid City.', 5); A.openTalk(); });
}

/* ---------------- commands ---------------- */
function matchTarget(q) {
  q = fold(q).replace(/^(the|a)\s+/, '').trim(); if (!q) return null;
  if (/\b(campus|campuses|university|college|faculty)\b/.test(q)) {   // Solid City University: "drive me to the arts campus"
    var cq = q.replace(/\b(campus|campuses|university|college|faculty|of|the|school)\b/g, ' ').replace(/\s+/g, ' ').trim(), cps = POIS.filter(function (o) { return o.campus; });
    for (var ci = 0; ci < cps.length && cq; ci++) if (fold(cps[ci].name).indexOf(cq) >= 0) return cps[ci];
    for (ci = 0; ci < cps.length && cq; ci++) { var ca = cps[ci].alias.split(' '); for (var ck = 0; ck < ca.length; ck++) if (ca[ck] && cq.split(' ').indexOf(ca[ck]) >= 0) return cps[ci]; }
    var uni = POIS.filter(function (o) { return o.id === 'university'; })[0]; if (uni) return uni;
  }
  var lists = flying() ? [FLY, POIS] : [POIS, FLY];
  for (var L = 0; L < 2; L++) for (var i = 0; i < lists[L].length; i++) { var p = lists[L][i]; if (fold(p.name).indexOf(q) >= 0) return p; }
  for (L = 0; L < 2; L++) for (i = 0; i < lists[L].length; i++) { p = lists[L][i]; var al = (p.alias || '').split(' '); for (var k = 0; k < al.length; k++) if (al[k] && (al[k] === q || q.split(/\s+/).indexOf(al[k]) >= 0)) return p; }
  return null;
}
function handleCommand(cmd) {
  var c = fold(cmd).trim().replace(/[.!?