<!-- SOLID CITY · part 1526 -->
 = z - V.z, dd = Math.sqrt(dx * dx + dz * dz) / V.r;
    if (dd < 1) {
      var q2 = 1 - dd * dd; h += V.h * q2 * q2 * Math.pow(1 - dd, 0.35) * land;   // broad shield profile
      if (V.crater && dd < 0.12) h -= (0.12 - dd) / 0.12 * 260;
    }
  }
  if (land > 0) {
    h += ridged(x * 0.00045, z * 0.00045, 5) * 520 * land * smooth(c * 2.5);
    h += fbm(x * 0.0035, z * 0.0035, 3) * 18 * land;
  }
  // flatten the capital
  var r = Math.sqrt(x * x + z * z);
  if (r < 1500) h = lerp(CITY_Y, Math.max(h, 4), smooth((r - 820) / 680));
  // Solid City University (campus-map.js): road beds and campus pads flattened to city level
  if (window.CampusMap) { var fw = CampusMap.flatW(x, z); if (fw > 0) h = lerp(h, CITY_Y, fw); }
  return h;
}
function groundAt(x, z) { var r = x * x + z * z; return r < 820 * 820 ? CITY_Y : heightAt(x, z); }

var tmpC = new T.Color();
function colorAt(x, z, h, slope, out) {
  var lava = fbm(x * 0.0011 + 7, z * 0.0011, 3);
  if (h < 0) { out.setRGB(0.05, 0.12, 0.14).convertSRGBToLinear(); return; }
  if (h < 14) { out.setRGB(0.22, 0.19, 0.15).convertSRGBToLinear(); return; }             // black-sand shore
  var g = smooth((lava + 0.2) * 2.2) * smooth((2200 - h) / 900);    // green flanks
  var r0 = lerp(0.075, 0.07, g), g0 = lerp(0.065, 0.17, g), b0 = lerp(0.07, 0.07, g);
  if (slope > 0.55) { r0 = 0.11; g0 = 0.09; b0 = 0.085; }               // basalt cliffs
  if (h > 2600) { var s = smooth((h - 2900) / 400) * (1 - smooth((slope - 0.6) * 4)); r0 = lerp(r0, 0.86, s); g0 = lerp(g0, 0.86, s); b0 = lerp(b0, 0.9, s); }
  out.setRGB(r0, g0, b0).convertSRGBToLinear();
}

/* curved-planet vertex hook: every world material drops by d^2/2R from the camera */
function curve(mat) {
  mat.onBeforeCompile = function (sh) {
    sh.vertexShader = sh.vertexShader.replace('#include <begin_vertex>',
      '#include <begin_vertex>\n' +
      '#ifdef USE_INSTANCING\n vec4 cwp = modelMatrix * instanceMatrix * vec4(transformed,1.0);\n#else\n vec4 cwp = modelMatrix * vec4(transformed,1.0);\n#endif\n' +
      ' vec2 cdd = cwp.xz - cameraPosition.xz; transformed.y -= dot(cdd,cdd) * ' + (0.5 / R_PLANET).toExponential(6) + ';');
  };
  return mat;
}

/* ---------------- renderer / scene ---------------- */
var canvas = $('sc-gl');
var renderer = new T.WebGLRenderer({ canvas: canvas, antialias: !MOBILE, powerPreference: 'high-performance' });
var DPR = Math.min(window.devicePixelRatio || 1, MOBILE ? 1.35 : 1.75);
if (QS.get('dpr')) DPR = +QS.get('dpr');
renderer.setPixelRatio(DPR);
// size the drawing buffer from the canvas box itself (CSS keeps it at 100% of the real viewport); never write px widths
function vpSize() { var el = renderer.domElement, w = el.clientWidth || document.documentElement.clientWidth || innerWidth, h = el.clientHeight || document.documentElement.clientHeight || innerHeight; return [w, h]; }
renderer.setSize(innerWidth, innerHeight, false);
renderer.outputEncoding = T.sRGBEncoding;
var scene = new T.Scene();
var camera = new T.PerspectiveCamera(62, innerWidth / innerHeight, 0.3, 40000);
var world = new T.Group(); scene.add(world);            // floating-origin root
var origin = { x: 0, z: 0 };
scene.fog = new T.Fog(0x2a0d14, 2000, 22000);

var SUN_DIR = new T.Vector3(0.25, 0.85, 0.35).normalize(); // high daytime sun
var hemi = new T.HemisphereLight(0xbfd8ff, 0x4a5a3a, 0.75); scene.add(hemi);
var sun = new T.DirectionalLight(0xfff6e8, 1.4); sun.position.copy(SUN_DIR).multiplyScalar(1000); scene.add(sun);
var amb = new T.AmbientLight(0x8a9aa8, 0.5); scene.add(amb);

/* ---------------- quadtree LOD terrain ---------------- */
var TERRAIN_MAT = curve(new T.MeshLambertMaterial({ vertexColors: true }));
var ROOT = { cx: 8600, cz: 0, size: 131072 };
var SEG = MOBILE ? 24 : 32, MIN_SIZE = MOBILE ? 512 : 256, SPLIT_K = MOBILE ? 1.45 : 1.6;
var chunks = new Map();           // key -> {mesh, last}
var buildQueue = [];
function nodeKey(cx, cz, size) { return size + ':' + cx + ':' + cz; }
function buildChunk(cx, cz, size) {
  var n = SEG, n1 = n + 1, step = size / n, half = size / 2;
  var vcount = n1 * n1 + 4 * n1, pos = new Float32Array(vcount * 3), nor = new Float32Array(vcount * 3), col = new Float32Array(vcount * 3);
  var hs = new Float32Array((n1 + 2) * (n1 + 2));
  var allWater = true;
  for (var z = -1; z <= n1; z++) for (var x = -1; x <= n1; x++) {
    var hv = heightAt(cx - half + x * step, cz - half + z * step);
    hs[(z + 1) * (n1 + 2) + (x + 1)] = hv; if (hv > -8) allWater = false;
  }
  if (allWater && size <= 8192) return null; // deep ocean: the sea disc covers it
  var vi = 0, skirt = Math.max(30, size * 0.02);
  function put(x, z, yOff) {
    var hh = hs[(z + 1) * (n1 + 2) + (x + 1)];
    var hl = hs[(z + 1) * (n1 + 2) + x], hr = hs[(z + 1) * (n1 + 2) + x + 2], hd = hs[z * (n1 + 2) + x + 1], hu = hs[(z + 2) * (n1 + 2) + x + 1];
    var nx = (hl - hr) / (2 * step), nz = (hd - hu) / (2 * step), il = 1 / Math.sqrt(nx * nx + 1 + nz * nz);
    pos[vi * 