<!-- SOLID CITY · part 3408 -->
new THREE.Mesh(new THREE.CylinderGeometry(9, 14, 2400, 24, 1, true), new THREE.MeshBasicMaterial({ map: TEX_BEAM, transparent: true, opacity: 0.22, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide, fog: false }));
  pad.beam.position.y = 1200; pad.beam.frustumCulled = false; g.add(pad.beam);
  const lights = new THREE.InstancedMesh(new THREE.SphereGeometry(0.45, 8, 6), new THREE.MeshBasicMaterial({ color: new THREE.Color(3, 0.3, 0.3) }), 16);
  const mt = new THREE.Matrix4(); for (let k = 0; k < 16; k++) { const a = k / 16 * Math.PI * 2; mt.makeTranslation(Math.cos(a) * 16.4, 0.5, Math.sin(a) * 16.4); lights.setMatrixAt(k, mt); }
  g.add(lights);
}

/* --- clouds: soft billboards in one draw call, lit by the sunset --- */
const clouds = (() => {
  const n = MOBILE ? 150 : 240, base = new THREE.PlaneGeometry(1, 1), g = new THREE.InstancedBufferGeometry();
  g.index = base.index; g.setAttribute('position', base.attributes.position); g.setAttribute('uv', base.attributes.uv);
  const off = new Float32Array(n * 3), size = new Float32Array(n), seed = new Float32Array(n), pr = mulberry(5);
  for (let i = 0; i < n; i++) {
    const a = pr() * Math.PI * 2, r = 700 + Math.pow(pr(), 0.7) * 15000, x = Math.cos(a) * r - 1200, z = Math.sin(a) * r - 1500;
    off[i * 3] = x; off[i * 3 + 1] = SEA + CLOUD_LO + pr() * (CLOUD_HI - CLOUD_LO) + curveDrop(x * x + z * z); off[i * 3 + 2] = z; size[i] = 260 + pr() * 520; seed[i] = pr();
  }
  g.setAttribute('iOff', new THREE.InstancedBufferAttribute(off, 3)); g.setAttribute('iSize', new THREE.InstancedBufferAttribute(size, 1)); g.setAttribute('iSeed', new THREE.InstancedBufferAttribute(seed, 1));
  g.instanceCount = n;
  const m = new THREE.ShaderMaterial({
    uniforms: { map: { value: TEX_PUFF }, uHaze: U.haze, uHazeDist: U.hazeDist },
    vertexShader: `attribute vec3 iOff; attribute float iSize; attribute float iSeed; varying vec2 vUv; varying float vF; varying float vY; varying float vD;
      void main(){ vUv = uv; vec4 mv = viewMatrix * vec4(iOff, 1.); float c = cos(iSeed*6.28), s = sin(iSeed*6.28); vec2 q = mat2(c,-s,s,c) * position.xy; mv.xy += q * iSize; vY = position.y + .5;
        float d = -mv.z; vD = d; vF = smoothstep(15., 160., d); gl_Position = projectionMatrix * mv; }`,
    fragmentShader: `uniform sampler2D map; uniform vec3 uHaze; uniform float uHazeDist; varying vec2 vUv; varying float vF; varying float vY; varying float vD;
      void main(){ float a = texture2D(map, vUv).a * vF; if (a < .01) discard;
        vec3 top = pow(vec3(1., .78, .6), vec3(2.2)) * 1.25, bot = pow(vec3(.42, .26, .36), vec3(2.2));
        vec3 c = mix(bot, top, smoothstep(.15, .95, vY));
        c = mix(c, uHaze, (1. - exp(-vD / (uHazeDist * 1.6))) * .8);
        gl_FragColor = vec4(c, a * .92);
        ${TONE} }`,
    transparent: true, depthWrite: false
  });
  const mesh = new THREE.Mesh(g, m); mesh.frustumCulled = false; mesh.renderOrder = 2; scene.add(mesh); return { mesh, n };
})();

/* --- space dust: wraps around the camera, gives speed parallax everywhere --- */
const dust = (() => {
  const n = MOBILE ? 260 : 420, pos = new Float32Array(n * 3), pr = mulberry(9); for (let i = 0; i < n * 3; i++) pos[i] = (pr() - 0.5) * 500;
  const g = new THREE.BufferGeometry(); g.setAttribute('position', new THREE.BufferAttribute(pos, 3));
  const m = new THREE.ShaderMaterial({
    uniforms: { uCam: { value: new V3() }, uA: { value: 0.5 }, uPx: { value: 1 } },
    vertexShader: 'uniform vec3 uCam; uniform float uPx; varying float vA; void main(){ vec3 p = mod(position - uCam + 250., 500.) - 250.; float d = length(p); vA = smoothstep(250., 120., d) * smoothstep(3., 20., d); vec4 mv = viewMatrix * vec4(p + uCam, 1.); gl_PointSize = clamp(uPx * 260. / -mv.z, 1., 6.); gl_Position = projectionMatrix * mv; }',
    fragmentShader: 'uniform float uA; varying float vA; void main(){ gl_FragColor = vec4(vec3(1., .9, .8) * vA * uA, 1.); }',
    blending: THREE.AdditiveBlending, depthWrite: false, transparent: true
  });
  const p = new THREE.Points(g, m); p.frustumCulled = false; scene.add(p); return p;
})();

/* --- speed streaks in camera space (boost / climb / re-entry) --- */
const streaks = (() => {
  const n = MOBILE ? 46 : 70, seed = new Float32Array(n * 6), end = new Float32Array(n * 2), pos = new Float32Array(n * 6), pr = mulberry(13);
  for (let i = 0; i < n; i++) { const a = pr() * Math.PI * 2, r = 2.2 + pr() * 7, z = pr(); for (let e = 0; e < 2; e++) { const o = (i * 2 + e) * 3; seed[o] = Math.cos(a) * r; seed[o + 1] = Math.sin(a) * r * 0.8; seed[o + 2] = z; end[i * 2 + e] = e; } }
  const g = new THREE.BufferGeometry(); g.setAttribute('position', new THREE.BufferAttribute(pos, 3)); g.setAttribute('aSeed', new THREE.BufferAttribute(seed, 3)); g.setAttribute('aEnd', new THREE.BufferAttribute(end, 1));
  const m = new THREE.ShaderMaterial({
    uniforms: { uT: { value: 0 }, uLen: { value: 6 }, uA: { value: 0 }, uCol: { value: new THREE.