<!-- SOLID CITY · part 1167 -->
+) if (visibleNow[i].mesh) visibleNow[i].mesh.visible = false;
  for (i = 0; i < sel.length; i++) { if (sel[i].mesh) sel[i].mesh.visible = true; sel[i].last = frameNo; }
  visibleNow = sel;
  // build nearest-first within a time budget
  if (buildQueue.length) {
    buildQueue.sort(function (a, b) { return (a.size - b.size) * 0 + (Math.hypot(a.cx - cam.x, a.cz - cam.z) / a.size) - (Math.hypot(b.cx - cam.x, b.cz - cam.z) / b.size); });
    var t0 = performance.now(), built = 0;
    while (buildQueue.length && (built < 1 || performance.now() - t0 < budgetMs)) {
      var nd = buildQueue.shift(), c = chunks.get(nodeKey(nd.cx, nd.cz, nd.size));
      if (!c || c.built) continue;
      c.mesh = buildChunk(nd.cx, nd.cz, nd.size); c.built = true; built++;
    }
  }
  // evict stale chunks
  if (frameNo % 120 === 0 && chunks.size > 420) {
    chunks.forEach(function (c, k) {
      if (c.built && frameNo - c.last > 600 && chunks.size > 300) { if (c.mesh) { world.remove(c.mesh); c.mesh.geometry.dispose(); } chunks.delete(k); }
    });
    buildQueue = buildQueue.filter(function (n) { return chunks.has(nodeKey(n.cx, n.cz, n.size)); });
  }
}
function terrainStats() { var t = 0, v = 0; for (var i = 0; i < visibleNow.length; i++) if (visibleNow[i].mesh) { v++; t += visibleNow[i].mesh.geometry.index.count / 3; } return { chunks: v, tris: t, cached: chunks.size, queue: buildQueue.length }; }

/* ---------------- ocean disc (follows the camera, curved) ---------------- */
var ocean = (function () {
  var rings = [0], rr = 40; while (rr < 900000) { rings.push(rr); rr *= 1.22; }
  var segs = 96, pos = [], idx = [];
  for (var r = 0; r < rings.length; r++) for (var s = 0; s <= segs; s++) { var a = s / segs * Math.PI * 2; pos.push(Math.cos(a) * rings[r], 0, Math.sin(a) * rings[r]); }
  for (r = 0; r < rings.length - 1; r++) for (s = 0; s < segs; s++) { var a0 = r * (segs + 1) + s, b0 = a0 + 1, c0 = a0 + segs + 1, d0 = c0 + 1; idx.push(a0, b0, c0, b0, d0, c0); }
  var g = new T.BufferGeometry(); g.setAttribute('position', new T.Float32BufferAttribute(pos, 3)); g.setIndex(idx); g.computeVertexNormals();
  var m = new T.Mesh(g, curve(new T.MeshPhongMaterial({ color: 0x06121c, specular: 0x1c120a, shininess: 320 })));
  m.frustumCulled = false; m.renderOrder = -1; scene.add(m);
  return m;
})();

/* ---------------- sky, sun, stars, galaxy ---------------- */
var skyUni = { uAlt: { value: 0 }, uDip: { value: 0 }, uSun: { value: SUN_DIR } };
var sky = new T.Mesh(new T.SphereGeometry(1, 32, 20), new T.ShaderMaterial({
  uniforms: skyUni, side: T.BackSide, depthWrite: false, fog: false,
  vertexShader: 'varying vec3 vD; void main(){ vD = normalize(position); gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); gl_Position.z = gl_Position.w*0.99999; }',
  fragmentShader: [
    'uniform float uAlt; uniform float uDip; uniform vec3 uSun; varying vec3 vD;',
    'void main(){',
    ' float e = vD.y + uDip;',             // elevation relative to the true horizon
    ' float space = smoothstep(9000.0, 60000.0, uAlt);',
    ' vec3 zen = vec3(0.20,0.50,0.92); vec3 mid = vec3(0.50,0.74,0.96); vec3 hor = vec3(0.86,0.92,0.98);',
    ' vec3 c = mix(hor, mid, smoothstep(0.0, 0.18, e)); c = mix(c, zen, smoothstep(0.12, 0.6, e));',
    ' c = mix(c, vec3(0.05,0.03,0.04), smoothstep(0.0,-0.25,e));',
    ' float s = max(dot(normalize(vD), normalize(uSun)), 0.0);',
    ' c += vec3(1.0,0.96,0.86) * pow(s, 9.0) * 0.45 + vec3(1.0,0.98,0.92) * smoothstep(0.9993, 0.9997, s);',
    ' vec3 spc = vec3(0.0) + vec3(1.0,0.85,0.55)*smoothstep(0.9993,0.9997,s);',
    ' float limb = exp(-abs(e)*55.0);',     // thin blue-gold limb seen from orbit
    ' spc += mix(vec3(0.2,0.45,1.0), vec3(1.0,0.5,0.2), 0.35) * limb * 0.9;',
    ' gl_FragColor = vec4(mix(c, spc, space), 1.0);',
    '}'].join('\n')
}));
sky.frustumCulled = false; sky.renderOrder = -10; scene.add(sky);
var stars = (function () {
  var N = MOBILE ? 2600 : 4200, G = MOBILE ? 4200 : 7000, pos = new Float32Array((N + G) * 3), col = new Float32Array((N + G) * 3), r2 = mulberry(77);
  var v = new T.Vector3(), c = new T.Color();
  for (var k = 0; k < N; k++) {
    v.set(r2() * 2 - 1, r2() * 2 - 1, r2() * 2 - 1).normalize(); pos.set([v.x, v.y, v.z], k * 3);
    var b = 0.45 + r2() * 0.55; c.setRGB(b, b * (0.85 + r2() * 0.15), b * (0.8 + r2() * 0.25)); col.set([c.r, c.g, c.b], k * 3);
  }
  // galaxy band: a tilted great circle with gaussian spread, red/gold/violet
  var ax = new T.Vector3(0.3, 0.85, 0.42).normalize(), u = new T.Vector3(1, 0, 0).cross(ax).normalize(), w = ax.clone().cross(u);
  for (k = 0; k < G; k++) {
    var a = r2() * Math.PI * 2, sp = (r2() + r2() + r2() - 1.5) * 0.16 * (1 + 0.6 * Math.sin(a * 3));
    v.copy(u).multiplyScalar(Math.cos(a)).addScaledVector(w, Math.sin(a)).addScaledVector(ax, sp).normalize();
    pos.set([v.x, v.y, v.z], (N + k) * 3);
    var h = r2(); c.setRGB(h < .4 ? 1 : h < .75 ? 0.95 : 0.7, h < .4 ? 0.55 : h < .75 ? 0.82 : 