<!-- SOLID CITY · part 3409 -->
Color(1, 0.9, 0.8) } },
    vertexShader: 'attribute vec3 aSeed; attribute float aEnd; uniform float uT; uniform float uLen; varying float vA; void main(){ float span = 70.; float z = -span + mod(aSeed.z * span + uT, span); vec3 p = vec3(aSeed.xy * (1. + (-z) * .03), z - aEnd * uLen); vA = smoothstep(-span, -span * .6, z) * smoothstep(0., -8., z) * (1. - aEnd * .9); gl_Position = projectionMatrix * vec4(p, 1.); }',
    fragmentShader: 'uniform float uA; uniform vec3 uCol; varying float vA; void main(){ gl_FragColor = vec4(uCol * vA * uA, 1.); }',
    blending: THREE.AdditiveBlending, depthWrite: false, depthTest: false, transparent: true
  });
  const l = new THREE.LineSegments(g, m); l.frustumCulled = false; l.renderOrder = 20; camera.add(l); return l;
})();

/* ======================= THE SHIP ======================= */
function mergedMesh(parts) {   // [{geo, mat}] -> one mesh per material (few draw calls)
  const byMat = new Map(); parts.forEach((p) => { if (!byMat.has(p.mat)) byMat.set(p.mat, []); byMat.get(p.mat).push(p.geo); });
  const grp = new THREE.Group();
  byMat.forEach((geos, mat) => {
    const gg = mergeGeometries(geos.map((x) => { const y = x.index ? x.toNonIndexed() : x.clone(); for (const k of Object.keys(y.attributes)) if (k !== 'position' && k !== 'normal' && k !== 'uv') y.deleteAttribute(k); if (!y.attributes.uv) y.setAttribute('uv', new THREE.Float32BufferAttribute(new Float32Array(y.attributes.position.count * 2), 2)); return y; }), false);
    gg.computeVertexNormals(); grp.add(new THREE.Mesh(gg, mat));
  });
  return grp;
}
function tr(geo, { p = [0, 0, 0], r = [0, 0, 0], s = [1, 1, 1] } = {}) { geo.applyMatrix4(new THREE.Matrix4().compose(new V3(p[0], p[1], p[2]), new THREE.Quaternion().setFromEuler(new THREE.Euler(r[0], r[1], r[2])), new V3(s[0], s[1], s[2]))); return geo; }
const SM = {
  hull: new THREE.MeshPhongMaterial({ color: 0x1b1418, shininess: 90, specular: 0x6a5a6a }),
  red: new THREE.MeshPhongMaterial({ color: 0xb8102a, shininess: 70, specular: 0x884444, emissive: 0x200004 }),
  gold: new THREE.MeshPhongMaterial({ color: 0xe8c36a, shininess: 100, specular: 0xffeebb, emissive: 0x2a1a04 }),
  glass: new THREE.MeshPhongMaterial({ color: 0x1a0e06, shininess: 160, specular: 0xffd9a0, emissive: 0x2a1404 }),
  glow: new THREE.MeshBasicMaterial({ color: new THREE.Color(3, 1.6, 0.6) })
};
function wingGeo(side) {
  const s = new THREE.Shape(); s.moveTo(0, -1.2); s.lineTo(4.8 * side, 2.2); s.lineTo(4.9 * side, 3.3); s.lineTo(0, 3.4); s.closePath();
  const g = new THREE.ExtrudeGeometry(s, { depth: 0.22, bevelEnabled: false }); g.rotateX(Math.PI / 2); g.translate(0, 0.1, 0); return g;
}
const ship = new THREE.Group();
const shipFx = { flames: [], glows: [], heat: null, shield: null, body: null, flameMat: null };
function buildShip() {
  const parts = [];
  parts.push({ geo: tr(new THREE.CylinderGeometry(0.22, 1.05, 6.6, 10), { p: [0, 0, -2.4], r: [-Math.PI / 2, 0, 0] }), mat: SM.red });
  parts.push({ geo: tr(new THREE.CylinderGeometry(1.05, 1.15, 2.2, 10), { p: [0, 0, 1.9], r: [-Math.PI / 2, 0, 0] }), mat: SM.hull });
  parts.push({ geo: tr(new THREE.BoxGeometry(2.6, 0.9, 3.2), { p: [0, -0.05, 2.4] }), mat: SM.hull });
  parts.push({ geo: tr(new THREE.SphereGeometry(0.72, 14, 10), { p: [0, 0.52, -1.3], s: [0.95, 0.72, 2.3] }), mat: SM.glass });
  parts.push({ geo: tr(wingGeo(1), { p: [0.6, -0.2, 0.3] }), mat: SM.hull }); parts.push({ geo: tr(wingGeo(-1), { p: [-0.6, -0.2, 0.3] }), mat: SM.hull });
  parts.push({ geo: tr(new THREE.BoxGeometry(4.6, 0.12, 0.35), { p: [2.9, -0.06, 1.0], r: [0, -0.62, 0] }), mat: SM.gold });
  parts.push({ geo: tr(new THREE.BoxGeometry(4.6, 0.12, 0.35), { p: [-2.9, -0.06, 1.0], r: [0, 0.62, 0] }), mat: SM.gold });
  for (const sx of [-1, 1]) {
    parts.push({ geo: tr(new THREE.CylinderGeometry(0.55, 0.68, 3.4, 12), { p: [1.15 * sx, -0.05, 2.5], r: [-Math.PI / 2, 0, 0] }), mat: SM.hull });
    parts.push({ geo: tr(new THREE.TorusGeometry(0.62, 0.1, 6, 16), { p: [1.15 * sx, -0.05, 4.2] }), mat: SM.gold });
    parts.push({ geo: tr(new THREE.BoxGeometry(0.16, 1.6, 1.8), { p: [0.75 * sx, 0.9, 3.0], r: [0.35, 0, -0.38 * sx] }), mat: SM.red });
    parts.push({ geo: tr(new THREE.CylinderGeometry(0.09, 0.09, 1.6, 6), { p: [2.4 * sx, -0.12, -0.6], r: [-Math.PI / 2, 0, 0] }), mat: SM.gold });
    parts.push({ geo: tr(new THREE.CircleGeometry(0.5, 14), { p: [1.15 * sx, -0.05, 4.22] }), mat: SM.glow });
  }
  shipFx.body = mergedMesh(parts); ship.add(shipFx.body);
  const flameMat = new THREE.ShaderMaterial({
    uniforms: { uT: U.time, uP: { value: 0.5 }, uB: { value: 0 } },
    vertexShader: 'varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }',
    fragmentShader: 'uniform float uT; uniform float uP; uniform float uB; varying vec2 vUv; void main(){ float y = vUv.y; float f = .8 + .2*sin(uT*60. + y*20.); vec3 hot = mix(vec3(3.,1.6,.6), vec3(1.6,2.2,4.), uB); vec3 c = 