<!-- SOLID CITY · part 1236 -->
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.copy(tq); rotLocal(AX, -Math.PI / 2 * up);
    sh.speed = Math.min(170, 8 + t * t * 14);
    sh.y += sh.speed * dt * up + 1.5 * dt;
  } else {
    var lv = smooth((t - 5) / 2.2);
    tq.setFromAxisAngle(UP, Math.PI); var tgt = tq.clone(); tm.makeRotationAxis(AX, -Math.PI / 2 * (1 - lv) - 0.12 * lv); tgt.multiply(new T.Quaternion().setFromRotationMatrix(tm));
    sh.q.copy(tgt); sh.speed = 170 + lv * 80; moveShip(dt);
    if (t > 7.2) { S.mode = 'fly'; sh.throttle = 0.6; setFlightUI(true); help(IS_TOUCH ? 'Stick: pitch & roll (bank to turn). Slider: throttle. Hold BOOST. Climb past 25 km for orbit.' : 'W/S pitch · A/D roll · Q/E yaw · R/F throttle · Shift boost · L auto-level · H return to city. Climb past 25 km for orbit.', 9); }
  }
}

/* ---------------- camera + floating origin ---------------- */
var camAbs = new V3(), camLook = new V3(), camUp = new V3(0, 1, 0), shipCam = null, watchFov = 0;
function rebase(ax, az) {
  if (Math.abs(ax - origin.x) > 3000 || Math.abs(az - origin.z) > 3000) {
    origin.x = Math.round(ax / 1000) * 1000; origin.z = Math.round(az / 1000) * 1000;
    world.position.set(-origin.x, 0, -origin.z); world.updateMatrixWorld(true);
  }
}
function placeCamera(dt) {
  if (S.mode === 'walk') {
    var P = S.p, d = S.camDist, cp = Math.cos(S.camPitch);
    camAbs.set(P.x + Math.sin(S.camYaw) * d * cp, P.y + 1.6 + Math.sin(S.camPitch) * d, P.z + Math.cos(S.camYaw) * d * cp);
    var g = groundAt(camAbs.x, camAbs.z) + 0.6; if (camAbs.y < g) camAbs.y = g;
    camLook.set(P.x, P.y + 1.5, P.z); camUp.set(0, 1, 0); shipCam = null; watchFov = 0;
    if (window.CityCitizens) CityCitizens.cam(dt, camAbs, camLook);   // 👥 WATCH-follow a citizen (walk mode only)
  } else if (window.CityWatch && (watchFov = CityWatch.cam(dt, camAbs, camLook, camUp))) {
    var gw = groundAt(camAbs.x, camAbs.z) + 3; if (camAbs.y < gw) camAbs.y = gw; shipCam = null;
  } else {
    watchFov = 0;
    var sh = S.ship, v = shipVectors(), back = S.mode === 'boarded' || S.mode === 'countdown' ? 30 : 24 + Math.min(sh.speed, 3000) * 0.004;
    var want = new V3(sh.x, sh.y, sh.z).addScaledVector(v.f, -back).addScaledVector(S.mode === 'launch' ? UP : v.u, 7);
    if (S.mode === 'boarded' || S.mode === 'countdown') want.set(sh.x + 18, sh.y + 9, sh.z + 26);
    if (S.mode === 'launch' && S.launchT < 5.5) want.set(PAD.x + 60