<!-- SOLID CITY · part 3411 -->
onst d = ALIEN_DEF[type];
  return { type, mesh, hull, hp: d.hp, maxHp: d.hp, r: d.r, vmax: d.v, pos: new V3(), vel: new V3(), alive: true, t: Math.random() * 10, fireT: 2 + Math.random() * 2, flash: 0, mode: 'attack', modeT: 0, orbit: Math.random() * 6.28, side: Math.random() < 0.5 ? -1 : 1, aim: new V3() };
}

/* ======================= FX POOLS ======================= */
const LASER_N = 56;
const laserMesh = new THREE.InstancedMesh(new THREE.BoxGeometry(0.34, 0.34, 1), new THREE.MeshBasicMaterial({ color: new THREE.Color(4, 0.5, 0.6), transparent: true, blending: THREE.AdditiveBlending, depthWrite: false }), LASER_N);
const laserCore = new THREE.InstancedMesh(new THREE.BoxGeometry(0.12, 0.12, 1), new THREE.MeshBasicMaterial({ color: new THREE.Color(4, 3.4, 2.4) }), LASER_N);
laserMesh.frustumCulled = laserCore.frustumCulled = false; laserMesh.count = laserCore.count = 0; scene.add(laserMesh, laserCore);
const lasers = []; for (let i = 0; i < LASER_N; i++) lasers.push({ on: false, pos: new V3(), prev: new V3(), dir: new V3(), v: 0, life: 0 });
function makePointPool(n) {   // GPU-animated particles: position + velocity (with drag) + colour + birth/life/size
  const g = new THREE.BufferGeometry(); const A = (k) => new THREE.BufferAttribute(new Float32Array(n * k), k).setUsage(THREE.DynamicDrawUsage);
  g.setAttribute('position', A(3)); g.setAttribute('aV', A(3)); g.setAttribute('aC', A(4)); g.setAttribute('aInfo', A(3));
  for (let i = 0; i < n; i++) g.attributes.aInfo.setXYZ(i, -999, 0.001, 0);
  const m = new THREE.ShaderMaterial({
    uniforms: { uT: U.time, uScale: { value: 400 } },
    vertexShader: `attribute vec3 aV; attribute vec4 aC; attribute vec3 aInfo; uniform float uT; uniform float uScale; varying vec4 vC; varying float vK;
      void main(){ float t = uT - aInfo.x; float life = aInfo.y; vK = t / life; vC = aC;
        if (t < 0. || t > life) { gl_Position = vec4(2., 2., 2., 1.); gl_PointSize = 0.; return; }
        vec3 p = position + aV * (1. - exp(-2.2 * t)) / 2.2;
        vec4 mv = modelViewMatrix * vec4(p, 1.); float sz = aInfo.z * (1. + vK * 1.6);
        gl_PointSize = clamp(sz * uScale / -mv.z, 0., 220.); gl_Position = projectionMatrix * mv; }`,
    fragmentShader: 'varying vec4 vC; varying float vK; void main(){ vec2 p = gl_PointCoord - .5; float r = length(p); float a = smoothstep(.5, .0, r) * (1. - vK); vec3 c = mix(vC.rgb * 2.6, vC.rgb * .5, vK); gl_FragColor = vec4(c * a * vC.a, 1.); }',
    blending: THREE.AdditiveBlending, depthWrite: false, transparent: true
  });
  const p = new THREE.Points(g, m); p.frustumCulled = false; p.renderOrder = 5; scene.add(p);
  return { p, g, n, i: 0, dirty: false };
}
const sparks = makePointPool(MOBILE ? 520 : 900);
function emit(pool, pos, vel, col, life, size, a = 1) {
  const i = pool.i, g = pool.g; pool.i = (pool.i + 1) % pool.n;
  g.attributes.position.setXYZ(i, pos.x, pos.y, pos.z); g.attributes.aV.setXYZ(i, vel.x, vel.y, vel.z);
  g.attributes.aC.setXYZW(i, col.r, col.g, col.b, a); g.attributes.aInfo.setXYZ(i, U.time.value, life, size); pool.dirty = true;
}
function flushPool(pool) { if (!pool.dirty) return; for (const k of ['position', 'aV', 'aC', 'aInfo']) pool.g.attributes[k].needsUpdate = true; pool.dirty = false; }
const ORB_N = 70, orbs = [], orbGeo = new THREE.BufferGeometry(), orbPos = new Float32Array(ORB_N * 3);
orbGeo.setAttribute('position', new THREE.BufferAttribute(orbPos, 3).setUsage(THREE.DynamicDrawUsage));
const orbPts = new THREE.Points(orbGeo, new THREE.ShaderMaterial({   // alien plasma orbs
  uniforms: { uScale: { value: 400 }, uT: U.time },
  vertexShader: 'uniform float uScale; uniform float uT; void main(){ vec4 mv = modelViewMatrix * vec4(position, 1.); gl_PointSize = clamp((7. + sin(uT*30.)) * uScale / -mv.z, 0., 160.); gl_Position = projectionMatrix * mv; }',
  fragmentShader: 'void main(){ vec2 p = gl_PointCoord - .5; float r = length(p); float a = smoothstep(.5, .1, r); float core = smoothstep(.22, .0, r); gl_FragColor = vec4(vec3(.4, 3., 1.2) * a + vec3(2.5) * core, 1.); }',
  blending: THREE.AdditiveBlending, depthWrite: false, transparent: true
}));
orbPts.frustumCulled = false; orbPts.renderOrder = 6; scene.add(orbPts);
for (let i = 0; i < ORB_N; i++) { orbs.push({ on: false, pos: new V3(), prev: new V3(), vel: new V3(), life: 0 }); orbPos[i * 3 + 1] = -1e6; }
const flashes = [];
for (let i = 0; i < 12; i++) { const ring = i % 2 === 1, s = new THREE.Sprite(new THREE.SpriteMaterial({ map: ring ? TEX_RING : TEX_GLOW, color: new THREE.Color(3, 2, 1), blending: THREE.AdditiveBlending, depthWrite: false, transparent: true })); s.visible = false; scene.add(s); flashes.push({ s, t: 1, life: 1, size: 0, ring }); }
function flash(pos, size, ring, col) {
  const list = flashes.filter((x) => x.ring === ring), f = list.find((x) => x.t >= x.life) || list.reduce((a, b) => (a.t / a.life > b.t / b.life ? a : b));
  f.s.position.copy(pos); f.t = 0; f.life = ring ? 0.7 : 0.35; f.size = siz