<!-- SOLID CITY · part 1563 -->
ain.actHere(P.x, P.z); if (trl) actTarget = { kind: 'train', label: trl }; }
  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 + dh * 0.05;
    if (dh > 1) for (var la = 400; la <= 3200; la *= 2) { var lx = sh.x + dx / dh * Math.min(la, dh), lz = sh.z + dz / dh * Math.min(la, dh); cruise = Math.max(cruise, groundAt(lx, lz) + 220); }   // terrain look-ahead
    var dir = tv.set(dx, (cruise - sh.y) * 1.2, dz); if (dir.length() < 1) dir.set(0, 0, 1); dir.normalize();
    var spd = clamp(dh * 0.45, 90, 2600); if (sh.y > 20000) spd = 3000;
    tm.lookAt(new V3(), tv2.copy(dir).negate(), UP); tq.setFromRotationMatrix(tm);
    sh.q.slerp(tq, Math.min(1, dt * 1.6));
    sh.speed += (spd - sh.speed) * Math.min(1, dt * 0.9);
    moveShip(dt);
    if (tgt && dh < (A.arrive || 600)) { S.mode = 'fly'; S.auto = null; sh.throttle = clamp(sh.speed / maxSpeed(sh.y), 0.2, 1); if (A.onArrive) A.onArrive(); return; }
    if (!tgt && dh < 140 && sh.y < PAD.y + 600) A.phase = 'descend';
  } else {
    var head = Math.atan2(-dx, -dz) || 0;
    sh.speed = 0;
    sh.x += dx * Math.min(1, dt * 1.5); sh.z += dz * Math.min(1, dt * 1.5);
    var ty = PAD.y + 2.4, vy = Math.min(70, (sh.y - ty) * 0.9 + 4);
    sh.y = Math.max(ty, sh.y - vy * dt);
    tq.setFromAxisAngle(UP, Math.PI); sh.q.slerp(tq, Math.min(1, dt * 2));
    if (sh.y <= ty + 0.05 && dh < 1) { S.mode = 'boarded'; S.auto = null; sh.speed = 0; setFlightUI(false); help('Landed at the Orbital Spaceport. LAUNCH again or EXIT SHIP to walk.'); }
  }
}
function updateLaunch(dt) {
  var sh = S.ship; S.launchT += dt; var t = S.launchT;
  if (t < 5) {
    var up = smooth(t / 1.4);
    tq.setFromAxisAngle(UP, Math.PI); sh.q