<!-- SOLID CITY · part 3410 -->
mix(vec3(2.,.25,.1), hot, smoothstep(.2, 1., y)); float a = smoothstep(0., .9, y) * f * (.35 + uP * .65); gl_FragColor = vec4(c * a, 1.); }',
    blending: THREE.AdditiveBlending, depthWrite: false, transparent: true, side: THREE.DoubleSide
  });
  for (const sx of [-1, 1]) {
    const cg = new THREE.ConeGeometry(0.5, 1, 14, 1, true); cg.translate(0, -0.5, 0);
    const cone = new THREE.Mesh(cg, flameMat); cone.rotation.x = -Math.PI / 2; cone.position.set(1.15 * sx, -0.05, 4.25); ship.add(cone); shipFx.flames.push(cone);
    const gl = new THREE.Sprite(new THREE.SpriteMaterial({ map: TEX_GLOW, color: new THREE.Color(2.5, 1.2, 0.5), blending: THREE.AdditiveBlending, depthWrite: false, transparent: true })); gl.position.set(1.15 * sx, -0.05, 4.6); gl.scale.setScalar(3); ship.add(gl); shipFx.glows.push(gl);
  }
  shipFx.flameMat = flameMat;
  const heatMat = new THREE.ShaderMaterial({   // re-entry plasma sheath around the nose
    uniforms: { uT: U.time, uA: { value: 0 } },
    vertexShader: 'varying vec3 vP; varying vec3 vN; varying vec3 vV; void main(){ vP = position; vN = normalize(normalMatrix*normal); vec4 mv = modelViewMatrix*vec4(position,1.); vV = normalize(-mv.xyz); gl_Position = projectionMatrix*mv; }',
    fragmentShader: 'uniform float uT; uniform float uA; varying vec3 vP; varying vec3 vN; varying vec3 vV; void main(){ float fr = pow(1. - abs(dot(vN, vV)), 1.5); float n = .55 + .45*sin(vP.z*3. + uT*40. + sin(vP.x*5. + uT*23.)*2.); float tail = smoothstep(9., -5., vP.z); vec3 c = mix(vec3(2.2,.35,.05), vec3(2.4,1.2,.3), n); gl_FragColor = vec4(c * fr * n * tail * uA, 1.); }',
    blending: THREE.AdditiveBlending, depthWrite: false, transparent: true, side: THREE.DoubleSide
  });
  shipFx.heat = new THREE.Mesh(tr(new THREE.SphereGeometry(1, 20, 14), { p: [0, 0, 0.6], s: [4.2, 2.4, 8.4] }), heatMat); shipFx.heat.visible = false; ship.add(shipFx.heat);
  shipFx.shield = new THREE.Mesh(tr(new THREE.SphereGeometry(1, 20, 14), { s: [6.2, 3.2, 7.4] }), new THREE.MeshBasicMaterial({ color: new THREE.Color(0.6, 1.8, 3), transparent: true, opacity: 0, blending: THREE.AdditiveBlending, depthWrite: false }));
  ship.add(shipFx.shield);
  scene.add(ship);
}

/* ======================= ALIENS ======================= */
const AM = {
  hull: () => new THREE.MeshPhongMaterial({ color: 0x1a2a24, emissive: 0x000000, shininess: 80, specular: 0x55aa88 }),
  glowG: new THREE.MeshBasicMaterial({ color: new THREE.Color(0.4, 3, 1.3) }),
  glowM: new THREE.MeshBasicMaterial({ color: new THREE.Color(2.6, 0.4, 3) }),
  glowR: new THREE.MeshBasicMaterial({ color: new THREE.Color(3, 0.5, 0.3) })
};
const ALIEN_GEO = {};
function alienGeos() {
  ALIEN_GEO.scout = [
    { geo: tr(new THREE.SphereGeometry(4.2, 18, 10), { s: [1, 0.32, 1] }), mat: 'hull' },
    { geo: tr(new THREE.TorusGeometry(4.3, 0.35, 6, 28), { r: [Math.PI / 2, 0, 0] }), mat: 'glowG' },
    { geo: tr(new THREE.SphereGeometry(1.5, 12, 8), { p: [0, 0.9, 0] }), mat: 'glowG' },
    { geo: tr(new THREE.ConeGeometry(0.8, 3.4, 6), { p: [0, -0.2, 4.6], r: [Math.PI / 2, 0, 0] }), mat: 'hull' }];
  ALIEN_GEO.circler = [
    { geo: tr(new THREE.TorusGeometry(4.6, 1.0, 8, 24), { r: [Math.PI / 2, 0, 0] }), mat: 'hull' },
    { geo: tr(new THREE.IcosahedronGeometry(2.1, 1)), mat: 'glowM' },
    ...[0, 1, 2].map((k) => ({ geo: tr(new THREE.ConeGeometry(0.7, 3.6, 6), { p: [Math.cos(k * 2.09) * 6.4, 0, Math.sin(k * 2.09) * 6.4], r: [0, -k * 2.09, -Math.PI / 2] }), mat: 'hull' }))];
  ALIEN_GEO.diver = [
    { geo: tr(new THREE.OctahedronGeometry(3.2, 0), { s: [0.9, 0.5, 2.0] }), mat: 'hull' },
    { geo: tr(new THREE.BoxGeometry(9, 0.3, 1.6), { p: [0, 0, -1.6] }), mat: 'hull' },
    { geo: tr(new THREE.SphereGeometry(0.9, 10, 8), { p: [0, 0.6, 2.4] }), mat: 'glowR' },
    { geo: tr(new THREE.BoxGeometry(9.2, 0.2, 0.3), { p: [0, 0.05, -2.4] }), mat: 'glowR' }];
  ALIEN_GEO.mother = [
    { geo: tr(new THREE.SphereGeometry(30, 32, 14), { s: [1, 0.22, 1] }), mat: 'hull' },
    { geo: tr(new THREE.SphereGeometry(11, 20, 12, 0, Math.PI * 2, 0, Math.PI / 2), { p: [0, 4, 0] }), mat: 'hull' },
    { geo: tr(new THREE.TorusGeometry(30.5, 1.1, 6, 48), { r: [Math.PI / 2, 0, 0] }), mat: 'glowG' },
    { geo: tr(new THREE.TorusGeometry(18, 0.6, 6, 40), { p: [0, -5.5, 0], r: [Math.PI / 2, 0, 0] }), mat: 'glowM' },
    ...[0, 1, 2, 3, 4, 5].map((k) => ({ geo: tr(new THREE.BoxGeometry(2.4, 3, 8), { p: [Math.cos(k * 1.047) * 22, 2.6, Math.sin(k * 1.047) * 22], r: [0, -k * 1.047, 0] }), mat: 'hull' })),
    { geo: tr(new THREE.SphereGeometry(5, 14, 10), { p: [0, -6, 0] }), mat: 'glowR' }];
}
const ALIEN_DEF = { scout: { hp: 2, r: 9, v: 135 }, circler: { hp: 3, r: 9.5, v: 150 }, diver: { hp: 3, r: 8.5, v: 230 }, mother: { hp: 34, r: 32, v: 60 } };
const aliens = [];
function makeAlien(type) {
  const hull = AM.hull(), parts = ALIEN_GEO[type].map((p) => ({ geo: p.geo, mat: p.mat === 'hull' ? hull : AM[p.mat] }));
  const mesh = mergedMesh(parts); scene.add(mesh); c