<!-- SOLID CITY · part 888 -->
tTarget = { kind: 'citizen', id: 'provolta', label: 'TALK TO PROVOLTA' };   // the sanctuary's talk point
  if (!actTarget && window.CityPodium && CityPodium.actHere(P.x, P.z)) actTarget = { kind: 'podium', label: 'READ' };   // ALL THAT WE CAN DO! podium
  if (!actTarget && window.CityBook && CityBook.actHere(P.x, P.z)) actTarget = { kind: 'book', label: 'READ THE BOOK' };   // the King's Book of Law (Forum Plaza)
  if (!actTarget && window.CityCampus) { var cpt = CityCampus.actHere(P.x, P.z); if (cpt) actTarget = { kind: 'campus', id: cpt, label: 'OPEN COURSE CATALOG' }; }   // a campus hall's forecourt
  if (!actTarget && window.CityCitizens) { var cz = CityCitizens.near(P.x, P.z); if (cz) actTarget = { kind: 'citizen', id: cz.id, label: 'TALK TO ' + cz.name.toUpperCase() }; }
  if (!actTarget) for (var i = 0; i < portals.length; i++) {
    var po = portals[i], dp = Math.hypot(P.x - po.x, P.z - po.z);
    if (dp < 9) actTarget = { kind: 'portal', d: po.d, label: po.d.go ? po.d.go + ' →' : 'ENTER ' + po.d.label.toUpperCase() + ' →' };
    if (dp < 2.2 && S.portalCool <= 0) { openPortal(po.d); S.portalCool = 4; }
  }
  if (!actTarget && Math.hypot(P.x - PAD.x, P.z - PAD.z) < 34) actTarget = { kind: 'board', label: 'BOARD SHIP' };
  if (!actTarget && window.CityHyperTrain) { var trl = CityHyperTrain.actHere(P.x, P.z); if (trl) actTarget = { kind: 'train', label: trl }; }
  if (!actTarget && window.CityFirstParking) { var crt = CityFirstParking.actHere(P.x, P.z); if (crt) actTarget = crt; }
  if (!actTarget && window.CityFleet) { var flt = CityFleet.actHere(P.x, P.z); if (flt) actTarget = flt; }
  if (!actTarget && window.CityHyperCity) { var elv = CityHyperCity.actHere(P.x, P.z); if (elv) actTarget = { kind: 'elevator', label: elv }; }
  if (!actTarget && window.CityAnnex) { var aelv = CityAnnex.actHere(P.x, P.z); if (aelv) actTarget = { kind: 'elevator', label: aelv }; }
  if (!actTarget && window.CityQuadrantCrown) { var qelv = CityQuadrantCrown.actHere(P.x, P.z); if (qelv) actTarget = { kind: 'elevator', label: qelv }; }
  S.portalCool -= dt;
}

/* ---------------- flight ---------------- */
function shipVectors() { var f = new V3(0, 0, 1).applyQuaternion(S.ship.q), u = new V3(0, 1, 0).applyQuaternion(S.ship.q), r = new V3(-1, 0, 0).applyQuaternion(S.ship.q); return { f: f, u: u, r: r }; }
function rotLocal(axis, ang) { tq.setFromAxisAngle(axis, ang); S.ship.q.multiply(tq); }
function rotWorld(axis, ang) { tq.setFromAxisAngle(axis, ang); S.ship.q.premultiply(tq); }
function maxSpeed(alt) { return 320 + smooth((alt - 1500) / 22000) * 2900; }
function updateFly(dt) {
  var k = input.keys, sh = S.ship;
  var pitch = -input.sy - input.dragY, roll = input.sx + input.dragX, yaw = 0;
  if (k.KeyW || k.ArrowUp) pitch -= 1; if (k.KeyS || k.ArrowDown) pitch += 1; if (k.KeyA || k.ArrowLeft) roll -= 1; if (k.KeyD || k.ArrowRight) roll += 1;
  if (k.KeyQ) yaw += 1; if (k.KeyE) yaw -= 1;
  if (k.KeyR) sh.throttle = clamp(sh.throttle + dt * 0.5, 0, 1); if (k.KeyF) sh.throttle = clamp(sh.throttle - dt * 0.5, 0, 1);
  var boost = sh.boost || k.ShiftLeft || k.ShiftRight || k.Space;
  pitch = clamp(pitch, -1, 1); roll = clamp(roll, -1, 1);
  var v = shipVectors();
  rotLocal(AX, -pitch * 1.25 * dt);
  rotLocal(AZ, roll * 2.3 * dt);
  rotWorld(UP, (v.r.y * 1.1 + yaw * 0.6) * dt);   // bank-to-turn
  if (S.autoLevel) {
    if (Math.abs(roll) < 0.05) rotLocal(AZ, clamp(v.r.y * 2.2, -1.5, 1.5) * dt);
    if (Math.abs(pitch) < 0.05 && Math.abs(v.f.y) < 0.9) rotLocal(AX, v.f.y * 0.55 * dt);
  }
  S.ship.q.normalize();
  var alt = sh.y, target = sh.throttle * maxSpeed(alt) * (boost ? 2.6 : 1);
  sh.speed += (target - sh.speed) * Math.min(1, dt * (boost ? 1.2 : 0.7));
  moveShip(dt);
}
var warnMsg = '';
function moveShip(dt) {
  var sh = S.ship, f = new V3(0, 0, 1).applyQuaternion(sh.q);
  sh.x += f.x * sh.speed * dt; sh.y += f.y * sh.speed * dt; sh.z += f.z * sh.speed * dt;
  if (sh.y > 95000) sh.y = 95000;
  var g = groundAt(sh.x, sh.z), floor = Math.max(g, 0) + 4;
  warnMsg = '';
  if (sh.y < floor + 140 && f.y < -0.15 && S.mode === 'fly') warnMsg = 'PULL UP';
  if (sh.y < floor) { sh.y = floor; if (f.y < 0) rotLocal(AX, f.y * 2.5 * dt * 3); sh.speed *= 0.985; warnMsg = 'TERRAIN'; }
  var hit = insideSolid(sh.x, sh.y, sh.z, 6);
  if (hit) { sh.y = hit.y1 + 7; sh.speed *= 0.9; warnMsg = 'COLLISION — SOLID'; }
}
function updateAuto(dt) {
  var sh = S.ship, A = S.auto;
  if (Math.abs(input.sx) + Math.abs(input.sy) > 0.3 || input.keys.KeyW || input.keys.KeyS || input.keys.KeyA || input.keys.KeyD) { S.mode = 'fly'; S.auto = null; help('Manual control.', 2); return; }
  var tgt = A.tx != null, tx = tgt ? A.tx : PAD.x, tz = tgt ? A.tz : PAD.z;
  var dx = tx - sh.x, dz = tz - sh.z, dh = Math.hypot(dx, dz);
  if (A.phase === 'cruise') {
    var cruise = tgt ? (dh > 6000 ? Math.max(1800, A.alt, Math.min(sh.y, 12000)) : A.alt + dh * 0.05) : dh > 6000 ? Math.max(1800, Math.min(sh.y, 12000)) : PAD.y + 260 + 