<!-- SOLID CITY · part 3406 -->
EE.Mesh(capGeometry(PR, MOBILE ? 64 : 76, MOBILE ? 96 : 140, 1.05), new THREE.ShaderMaterial({
  uniforms: { uSun: U.sun, uHaze: U.haze, uHazeDist: U.hazeDist, uT: U.time, uAlt: U.alt01 },
  vertexShader: 'varying vec3 vW; varying vec3 vN; void main(){ vec4 w = modelMatrix*vec4(position,1.); vW = w.xyz; vN = normalize(position); gl_Position = projectionMatrix*viewMatrix*w; }',
  fragmentShader: `uniform vec3 uSun; uniform vec3 uHaze; uniform float uHazeDist; uniform float uT; uniform float uAlt; varying vec3 vW; varying vec3 vN;
  void main(){
    vec3 V = cameraPosition - vW; float dist = length(V); V /= dist;
    vec3 N = normalize(vN);
    vec2 p = vW.xz * .018; float near = clamp(1. - dist / 4000., 0., 1.);
    N = normalize(N + near * .05 * vec3(sin(p.x*3.1+uT*.8)+sin(p.y*2.3-uT*.6), 0., cos(p.x*2.7-uT*.7)+cos(p.y*3.3+uT*.5)));
    float dif = max(dot(N, uSun), 0.);
    vec3 c = vec3(.008,.03,.045) * (.45 + dif * 1.6);
    vec3 H = normalize(uSun + V); float nh = max(dot(N, H), 0.);
    c += vec3(1., .62, .32) * (pow(nh, 260.) * 5. + pow(nh, 22.) * .22);
    float fr = pow(1. - max(dot(N, V), 0.), 5.);
    c += uHaze * fr * .55;
    float term = smoothstep(-.25, .25, dot(N, uSun));
    c *= mix(.25, 1., term);
    float hz = 1. - exp(-dist / uHazeDist);
    c = mix(c, uHaze * mix(1., .55 + .45 * term, uAlt), hz);
    gl_FragColor = vec4(c, 1.);
    ${TONE}
  }`
}));
ocean.position.set(0, SEA - PR, 0); ocean.frustumCulled = false; scene.add(ocean);

/* --- the island: radial terrain around the capital, baked onto the curve --- */
function buildIsland() {
  const rings = MOBILE ? 58 : 66, segs = MOBILE ? 110 : 150, k = 1.115, pos = [], idx = [];
  const cc = new THREE.Color(), snow = new THREE.Color(0.85, 0.85, 0.9);
  const ringR = (i) => (i === 0 ? 0 : 34 * (Math.pow(k, i) - 1) / (k - 1) / 3);
  for (let i = 0; i <= rings; i++) for (let j = 0; j < (i ? segs : 1); j++) {
    const r = ringR(i), ph = j / segs * Math.PI * 2, cx = Math.cos(ph) * r, cz = Math.sin(ph) * r;
    let h = Math.hypot(cx, cz) < 820 ? 22 : cityHeight(cx, cz); if (h < 1) h = Math.min(h, -40);
    const x = cx + CITY.x, z = cz + CITY.z;
    pos.push(x, h + SEA + curveDrop(x * x + z * z) - 0.15, z);
  }
  for (let j = 0; j < segs; j++) idx.push(0, 1 + (j + 1) % segs, 1 + j);
  for (let i = 1; i < rings; i++) { const a = 1 + (i - 1) * segs, b = 1 + i * segs; for (let j = 0; j < segs; j++) { const j2 = (j + 1) % segs; idx.push(a + j, a + j2, b + j, a + j2, b + j2, b + j); } }
  let g = new THREE.BufferGeometry(); g.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3)); g.setIndex(idx);
  g = g.toNonIndexed(); g.computeVertexNormals();
  const P = g.attributes.position, N = g.attributes.normal, C = new Float32Array(P.count * 3);
  for (let v = 0; v < P.count; v++) {
    const x = P.getX(v), z = P.getZ(v), h = P.getY(v) - SEA - curveDrop(x * x + z * z) + 0.15, slope = 1 - N.getY(v);
    const lava = fbm(x * 0.0011 + 7, z * 0.0011, 2);
    if (h < 2) cc.setRGB(0.05, 0.1, 0.12); else if (h < 16) cc.setRGB(0.2, 0.17, 0.14);
    else { const gr = smooth((lava + 0.2) * 2.2) * smooth((2200 - h) / 900); cc.setRGB(lerp(0.075, 0.06, gr), lerp(0.06, 0.17, gr), lerp(0.065, 0.07, gr)); if (slope > 0.35) cc.setRGB(0.11, 0.085, 0.08); if (h > 2700) cc.lerp(snow, smooth((h - 2900) / 400)); }
    if (Math.hypot(x - CITY.x, z - CITY.z) < 815) cc.setRGB(0.12, 0.09, 0.11);
    cc.convertSRGBToLinear(); C[v * 3] = cc.r; C[v * 3 + 1] = cc.g; C[v * 3 + 2] = cc.b;
  }
  g.setAttribute('color', new THREE.BufferAttribute(C, 3));
  const m = new THREE.Mesh(g, new THREE.MeshLambertMaterial({ vertexColors: true, flatShading: true }));
  m.frustumCulled = false; scene.add(m); return m;
}

/* --- the capital around the pad (red / black / gold, lit windows), simplified from /city --- */
const M = {
  black: new THREE.MeshLambertMaterial({ color: 0x0c0a0d }),
  gold: new THREE.MeshPhongMaterial({ color: 0xe8c36a, emissive: 0x3a2808, shininess: 60, specular: 0x886633 }),
  red: new THREE.MeshBasicMaterial({ color: new THREE.Color(2.2, 0.15, 0.25) }),
  glowGold: new THREE.MeshBasicMaterial({ color: new THREE.Color(2.4, 1.6, 0.6) }),
  road: new THREE.MeshLambertMaterial({ color: 0x151216 })
};
function addBox(w, h, d, mat, x, y, z) { const m = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), mat); m.position.set(x, y + h / 2, z); scene.add(m); return m; }
function buildCity() {
  const P = { x: CITY.x, z: CITY.z }, studio = { x: CITY.x + 430, z: CITY.z - 170 }, port = { x: CITY.x - 380, z: CITY.z + 330 }, hangar = { x: CITY.x - 307, z: CITY.z + 365 };
  const base = new THREE.Mesh(new THREE.CircleGeometry(810, 64), new THREE.MeshLambertMaterial({ map: TEX_GRID, emissive: 0xffffff, emissiveMap: TEX_GRIDE })); base.rotation.x = -Math.PI / 2; base.position.set(P.x, 0.02, P.z); scene.add(base);
  const roadMat = new THREE.MeshLambertMaterial({ color: 0x1d161a, emissive: 0x1a0306 });
  [studio, port, { x: CITY.x, z: CITY.z - 700 }, 