<!-- SOLID CITY · part 1588 -->
.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, PAD.y + 12 + Math.max(0, sh.y - PAD.y) * 0.75, PAD.z + 80);
    if (!shipCam) shipCam = want.clone(); else shipCam.lerp(want, Math.min(1, dt * 6));
    // keep camera within a sane tether at high speed
    tv.set(sh.x, sh.y, sh.z); if (shipCam.distanceTo(tv) > back * 2.5) shipCam.copy(want);
    camAbs.copy(shipCam);
    var gy = groundAt(camAbs.x, camAbs.z) + 2; if (camAbs.y < gy) camAbs.y = gy;
    camLook.set(sh.x, sh.y, sh.z).addScaledVector(v.f, S.mode === 'launch' && S.launchT < 5.5 ? 0 : 18);
    camUp.lerp(S.mode === 'fly' || S.mode === 'auto' ? v.u : UP, Math.min(1, dt * 3)).normalize();
  }
  rebase(camAbs.x, camAbs.z);
  camera.position.set(camAbs.x - origin.x, camAbs.y, camAbs.z - origin.z);
  camera.up.copy(camUp);
  camera.lookAt(camLook.x - origin.x, camLook.y, camLook.z - origin.z);
  var alt = Math.max(camAbs.y, 1);
  camera.near = S.mode === 'walk' ? 0.25 : clamp(alt / 2500, 0.5, 40);
  camera.far = clamp(36000 + alt * 9, 36000, 1100000);
  var fov = watchFov > 1 ? watchFov : 62 + (S.mode === 'fly' || S.mode === 'auto' ? clamp(S.ship.speed / 120, 0, 14) : 0);
  camera.fov += (fov - camera.fov) * Math.min(1, dt * 3);
  camera.updateProjectionMatrix();
  // fog + sky by altitude
  var space = smooth((alt - 9000) / 45000);
  scene.fog.near = S.mode === 'walk' ? 900 : 2500 + alt * 2;
  scene.fog.far = (S.mode === 'walk' ? 16000 : 26000 + alt * 6) / Math.max(0.02, 1 - space * 0.98);
  scene.fog.color.setRGB(0.38 * (1 - space) + 0.01, 0.15 * (1 - space) + 0.01, 0.12 * (1 - space) + 0.03);
  skyUni.uAlt.value = alt; skyUni.uDip.value = Math.sqrt(2 * alt / R_PLANET);
  sky.position.copy(camera.position); sky.scale.setScalar(camera.far * 0.6);
  stars.position.copy(camera.position); stars.scale.setScalar(camera.far * 0.55);
  stars.material.opacity = 0.32 + 0.68 * smooth((alt - 2000) / 25000);
  hemi.intensity = 0.55; sun.intensity = 1.25;
  ocean.position.set(camAbs.x - origin.x, 0, camAbs.z - origin.z);
}

/* ---------------- HUD + radar ---------------- */
var radar = $('sc-radar'), rg = radar.getContext('2d'), mapImg = null, MAP = { cx: 8600, cz: 0, span: 76000, n: MOBILE ? 150 : 200 };
function buildMap() {
  var c = document.createElement('canvas'); c.width = c.height = MAP.n; var g = c.getContext('2d'), id = g.createImageData(MAP.n, MAP.n);
  for (var j = 0; j < MAP.n; j++) for (var i = 0; i < MAP.n; i++) {
    var x = MAP.cx - MAP.span / 2 + (i + 0.5) / MAP.n * MAP.span, z = MAP.cz - MAP.span / 2 + (j + 0.5) / MAP.n * MAP.span, h = heightAt(x, z), o = (j * MAP.n + i) * 4;
    if (h < 0) { id.data[o] = 10; id.data[o + 1] = 14 + Math.max(0, 30 + h / 20); id.data[o + 2] = 28 + Math.max(0, 30 + h / 20); }
    else { var t = Math.min(1, h / 3500); id.data[o] = 40 + t * 180; id.data[o + 1] = 26 + t * 130; id.data[o + 2] = 22 + t * 60; if (h > 2900) { id.data[o] = id.data[o + 1] = id.data[o + 2] = 225; } }
    id.data[o + 3] = 255;
  }
  g.putImageData(id, 0, 0); mapImg = c;
}
function drawRadar(ax, az, heading) {
  var W = radar.width, H = radar.height, flying = S.mode !== 'walk';
  var rad = !flying ? 900 : S.ship.y > 20000 ? 40000 : S.ship.y > 4000 ? 18000 : 7000;   // metres from centre to edge
  rg.save(); rg.clearRect(0, 0, W, H); rg.beginPath(); rg.arc(W / 2, H / 2, W / 2 - 2, 0, Math.PI * 2); rg.clip();
  rg.fillStyle = '#04070c'; rg.fillRect(0, 0, W, H);
  if (mapImg) {
    var px = mapImg.width / MAP.span, sxp = (ax - (MAP.cx - MAP.span / 2)) * px, szp = (az - (MAP.cz - MAP.span / 2)) * px, sr = rad * px;
    rg.imageSmoothingEnabled = true; rg.drawImage(mapImg, sxp - sr, szp - sr, sr * 2, sr * 2, 0, 0, W, H);
  }
  var sc = (W / 2) / rad;
  function blip(x, z, col, r, txt) { var bx = W / 2 + (x - ax) * sc, by = H / 2 + (z - az) * sc; var d = Math.hypot(bx - W / 2, by - H / 2); if (d > W / 2 - 8) { bx = W / 2 + (bx - W / 2) / d * (W / 2 - 8); by = H / 2 + (by - H / 2) / d * (W / 2 - 8); } rg.fillStyle = col; rg.beginPath(); rg.arc(bx, by, r, 0, Math.PI * 2); rg.fill(); if (txt) { rg.font = 'bold 15px system-ui'; rg.fillText(txt, bx + r + 2, by + 5); } }
  rg.fillStyle = 'rgba(30,20,24,.6)'; var cr = 800 * sc; rg.beginPath(); rg.arc(W / 2 + (0 - ax) * sc, H / 2 + (0 - az) * sc, Math.max(cr, 3), 0, Math.PI * 2); rg.fill();
  blip(D.plaza.x, D.plaza.z, '#e8c36a', 6, flying ? '' : 'F');
  blip(D.studio.x, D.studio.z, '#e8c36a', 6, flying ? '' : 'M');
  blip(D.port.x, D.port.z, '#ff3048', 7, flying ? 'PORT' : 'S');
  blip(D.soul.x, D.soul.z, '#49a6ff', 5, flying ? '' : 'SP');
  if (!flying) blip(SABLE_POS.x, SABLE_POS.z, '#ff8fa0', 4, '');
  if (window.CityHyper) try { CityHyper.radar(rg, blip, flying, sc, ax, az); } catch (e) {}
  if (window.CityWatch) try { CityWatch.radar(rg, blip, flying, sc, ax, az); } 