export const PARTICLE_KEYFRAME_BINDINGS = Object.freeze({
  particle_size: { uniform: 'uParticleSize' },
  particle_lifetime: { uniform: 'uLifetime' },
  particle_emission_rate: { uniform: 'uEmissionRate' },
  particle_rotation_speed: { uniform: 'uRotationSpeed' },
  particle_glow: { uniform: 'uGlowIntensity' },
  particle_flare_aspect: { uniform: 'uFlareAspect' },
  particle_flare_sharpness: { uniform: 'uFlareSharpness' },
  particle_offset_x: { position: 'x' },
  particle_offset_y: { position: 'y' },
  particle_offset_z: { position: 'z' },
  particle_gravity_x: { uniform: 'uGravity', component: 'x' },
  particle_gravity_y: { uniform: 'uGravity', component: 'y' },
  particle_gravity_z: { uniform: 'uGravity', component: 'z' },
  particle_drag: { uniform: 'uDrag' },
  particle_floor_y: { uniform: 'uFloorY' },
  particle_bounce: { uniform: 'uBounce' },
  particle_size_start: { uniform: 'uSizeStart' },
  particle_size_mid: { uniform: 'uSizeMid' },
  particle_size_end: { uniform: 'uSizeEnd' },
  particle_opacity_start: { uniform: 'uOpacityStart' },
  particle_opacity_mid: { uniform: 'uOpacityMid' },
  particle_opacity_end: { uniform: 'uOpacityEnd' },
  particle_turb_strength: { uniform: 'uTurbStrength' },
  particle_turb_scale: { uniform: 'uTurbScale' },
  particle_turb_speed: { uniform: 'uTurbSpeed' },
  particle_turb_octaves: { uniform: 'uTurbOctaves' },
  particle_vortex_strength: { uniform: 'uVortexStrength' },
  particle_vortex_inward: { uniform: 'uVortexInward' },
  particle_attractor_strength: { uniform: 'uAttractorStrength' },
  particle_attractor_radius: { uniform: 'uAttractorRadius' },
  particle_attractor_falloff: { uniform: 'uAttractorFalloff' },
  particle_curl_strength: { uniform: 'uCurlStrength' },
  particle_curl_scale: { uniform: 'uCurlScale' },
  particle_curl_speed: { uniform: 'uCurlSpeed' },
  particle_curl_evolution: { uniform: 'uCurlEvolution' },
  particle_cohesion_strength: { uniform: 'uCohesionStrength' },
  particle_cohesion_radius: { uniform: 'uCohesionRadius' },
  particle_surface_tension: { uniform: 'uSurfaceTension' },
  particle_viscosity: { uniform: 'uViscosity' },
  particle_wind_strength: { uniform: 'uWindStrength' },
  particle_wind_gust: { uniform: 'uWindGust' },
  particle_wind_gust_freq: { uniform: 'uWindGustFreq' },
  particle_swarm_separation: { uniform: 'uSwarmSeparation' },
  particle_swarm_alignment: { uniform: 'uSwarmAlignment' },
  particle_swarm_cohesion: { uniform: 'uSwarmCohesion' },
  particle_oscillation_amplitude: { uniform: 'uOscillationAmplitude' },
  particle_oscillation_frequency: { uniform: 'uOscillationFrequency' },
  particle_oscillation_phase: { uniform: 'uOscillationPhaseOffset' },
  particle_buoyancy_temperature: { uniform: 'uBuoyancyTemperature' },
  particle_buoyancy_density: { uniform: 'uBuoyancyDensity' },
  particle_buoyancy_noise: { uniform: 'uBuoyancyThermalNoise' },
  particle_buoyancy_cooling: { uniform: 'uBuoyancyCoolingRate' },
  particle_membrane_stiffness: { uniform: 'uMembraneStiffness' },
  particle_membrane_stretch: { uniform: 'uMembraneMaxStretch' },
  particle_membrane_radius: { uniform: 'uMembraneConnectionRadius' },
  particle_membrane_damping: { uniform: 'uMembraneDamping' },
});

export function applyParticleKeyframeValue(sceneObj, paramId, value) {
  const mesh = sceneObj?.particleSystemMesh;
  const binding = PARTICLE_KEYFRAME_BINDINGS[paramId];
  if (!mesh || !binding) return false;
  if (binding.position) {
    mesh.position[binding.position] = value;
    return true;
  }

  const uniform = mesh.material?.uniforms?.[binding.uniform];
  if (!uniform) return false;
  if (binding.component) {
    if (!uniform.value) return false;
    uniform.value[binding.component] = value;
  } else {
    uniform.value = value;
  }
  return true;
}

function assignDefined(target, key, value) {
  if (value !== undefined) target[key] = value;
}

function assignVector(target, source) {
  if (!source) return;
  assignDefined(target, 'x', source.x);
  assignDefined(target, 'y', source.y);
  assignDefined(target, 'z', source.z);
}

export function syncParticleConfigFromRuntime(config, mesh) {
  if (!config || !mesh) return config;
  const u = mesh.material?.uniforms;
  if (!u) return config;

  assignDefined(config, 'particleSize', u.uParticleSize?.value);
  assignDefined(config, 'lifetime', u.uLifetime?.value);
  assignDefined(config, 'offsetX', mesh.position?.x);
  assignDefined(config, 'offsetY', mesh.position?.y);
  assignDefined(config, 'offsetZ', mesh.position?.z);

  config.emitter ||= {};
  assignDefined(config.emitter, 'rate', u.uEmissionRate?.value);

  config.physics ||= {};
  config.physics.gravity ||= {};
  assignVector(config.physics.gravity, u.uGravity?.value);
  assignDefined(config.physics, 'drag', u.uDrag?.value);
  assignDefined(config.physics, 'bounce', u.uBounce?.value);
  assignDefined(config.physics, 'floorY', u.uFloorY?.value);
  if (u.uFloorEnabled) config.physics.floorEnabled = u.uFloorEnabled.value > 0.5;

  config.turbulence ||= {};
  if (u.uTurbEnabled) config.turbulence.enabled = u.uTurbEnabled.value > 0.5;
  assignDefined(config.turbulence, 'strength', u.uTurbStrength?.value);
  assignDefined(config.turbulence, 'scale', u.uTurbScale?.value);
  assignDefined(config.turbulence, 'speed', u.uTurbSpeed?.value);
  assignDefined(config.turbulence, 'octaves', u.uTurbOctaves?.value);

  config.vortex ||= {};
  if (u.uVortexEnabled) config.vortex.enabled = u.uVortexEnabled.value > 0.5;
  assignDefined(config.vortex, 'strength', u.uVortexStrength?.value);
  assignDefined(config.vortex, 'inwardForce', u.uVortexInward?.value);
  config.vortex.axis ||= {};
  assignVector(config.vortex.axis, u.uVortexAxis?.value);
  config.vortex.center ||= {};
  assignVector(config.vortex.center, u.uVortexCenter?.value);

  config.attractor ||= {};
  if (u.uAttractorEnabled) config.attractor.enabled = u.uAttractorEnabled.value > 0.5;
  assignDefined(config.attractor, 'strength', u.uAttractorStrength?.value);
  assignDefined(config.attractor, 'radius', u.uAttractorRadius?.value);
  assignDefined(config.attractor, 'falloff', u.uAttractorFalloff?.value);
  assignDefined(config.attractor, 'killZone', u.uAttractorKillZone?.value);
  config.attractor.position ||= {};
  assignVector(config.attractor.position, u.uAttractorPos?.value);

  config.curl ||= {};
  if (u.uCurlEnabled) config.curl.enabled = u.uCurlEnabled.value > 0.5;
  assignDefined(config.curl, 'strength', u.uCurlStrength?.value);
  assignDefined(config.curl, 'scale', u.uCurlScale?.value);
  assignDefined(config.curl, 'speed', u.uCurlSpeed?.value);
  assignDefined(config.curl, 'evolution', u.uCurlEvolution?.value);

  config.cohesion ||= {};
  if (u.uCohesionEnabled) config.cohesion.enabled = u.uCohesionEnabled.value > 0.5;
  assignDefined(config.cohesion, 'strength', u.uCohesionStrength?.value);
  assignDefined(config.cohesion, 'radius', u.uCohesionRadius?.value);
  assignDefined(config.cohesion, 'surfaceTension', u.uSurfaceTension?.value);
  assignDefined(config.cohesion, 'viscosity', u.uViscosity?.value);

  config.wind ||= {};
  if (u.uWindEnabled) config.wind.enabled = u.uWindEnabled.value > 0.5;
  assignDefined(config.wind, 'strength', u.uWindStrength?.value);
  assignDefined(config.wind, 'gust', u.uWindGust?.value);
  assignDefined(config.wind, 'gustFrequency', u.uWindGustFreq?.value);
  config.wind.direction ||= {};
  assignVector(config.wind.direction, u.uWindDirection?.value);

  config.swarm ||= {};
  if (u.uSwarmEnabled) config.swarm.enabled = u.uSwarmEnabled.value > 0.5;
  assignDefined(config.swarm, 'separation', u.uSwarmSeparation?.value);
  assignDefined(config.swarm, 'alignment', u.uSwarmAlignment?.value);
  assignDefined(config.swarm, 'cohesion', u.uSwarmCohesion?.value);
  assignDefined(config.swarm, 'perceptionRadius', u.uSwarmPerceptionRadius?.value);
  assignDefined(config.swarm, 'maxSpeed', u.uSwarmMaxSpeed?.value);
  assignDefined(config.swarm, 'maxForce', u.uSwarmMaxForce?.value);

  config.oscillation ||= {};
  if (u.uOscillationEnabled) config.oscillation.enabled = u.uOscillationEnabled.value > 0.5;
  assignDefined(config.oscillation, 'amplitude', u.uOscillationAmplitude?.value);
  assignDefined(config.oscillation, 'frequency', u.uOscillationFrequency?.value);
  assignDefined(config.oscillation, 'phaseOffset', u.uOscillationPhaseOffset?.value);
  if (u.uOscillationRadial) config.oscillation.radial = u.uOscillationRadial.value > 0.5;
  config.oscillation.axis ||= {};
  assignVector(config.oscillation.axis, u.uOscillationAxis?.value);

  config.buoyancy ||= {};
  if (u.uBuoyancyEnabled) config.buoyancy.enabled = u.uBuoyancyEnabled.value > 0.5;
  assignDefined(config.buoyancy, 'temperature', u.uBuoyancyTemperature?.value);
  assignDefined(config.buoyancy, 'density', u.uBuoyancyDensity?.value);
  assignDefined(config.buoyancy, 'thermalNoise', u.uBuoyancyThermalNoise?.value);
  assignDefined(config.buoyancy, 'coolingRate', u.uBuoyancyCoolingRate?.value);

  config.membrane ||= {};
  if (u.uMembraneEnabled) config.membrane.enabled = u.uMembraneEnabled.value > 0.5;
  assignDefined(config.membrane, 'stiffness', u.uMembraneStiffness?.value);
  assignDefined(config.membrane, 'maxStretch', u.uMembraneMaxStretch?.value);
  assignDefined(config.membrane, 'connectionRadius', u.uMembraneConnectionRadius?.value);
  assignDefined(config.membrane, 'damping', u.uMembraneDamping?.value);

  config.sizeOverLife ||= {};
  assignDefined(config.sizeOverLife, 'start', u.uSizeStart?.value);
  assignDefined(config.sizeOverLife, 'mid', u.uSizeMid?.value);
  assignDefined(config.sizeOverLife, 'end', u.uSizeEnd?.value);
  assignDefined(config.sizeOverLife, 'midPoint', u.uSizeMidPoint?.value);

  config.opacityOverLife ||= {};
  assignDefined(config.opacityOverLife, 'start', u.uOpacityStart?.value);
  assignDefined(config.opacityOverLife, 'mid', u.uOpacityMid?.value);
  assignDefined(config.opacityOverLife, 'end', u.uOpacityEnd?.value);
  assignDefined(config.opacityOverLife, 'midPoint', u.uOpacityMidPoint?.value);

  config.rotation ||= {};
  assignDefined(config.rotation, 'speed', u.uRotationSpeed?.value);
  assignDefined(config.rotation, 'variation', u.uRotationVariation?.value);

  config.colors ||= {};
  if (u.uColorStart?.value?.getHexString) config.colors.start = `#${u.uColorStart.value.getHexString()}`;
  if (u.uColorMid?.value?.getHexString) config.colors.mid = `#${u.uColorMid.value.getHexString()}`;
  if (u.uColorEnd?.value?.getHexString) config.colors.end = `#${u.uColorEnd.value.getHexString()}`;
  assignDefined(config.colors, 'midPoint', u.uColorMidPoint?.value);
  if (u.uColorByVelocity) config.colors.byVelocity = u.uColorByVelocity.value > 0.5;
  assignDefined(config.colors, 'velocityScale', u.uColorVelScale?.value);

  config.rendering ||= {};
  assignDefined(config.rendering, 'glow', u.uGlowIntensity?.value);
  if (u.uShape) {
    config.rendering.shape =
      ['orb', 'star', 'streak', 'ring', 'diamond'][Math.round(u.uShape.value)] || 'orb';
  }
  assignDefined(config.rendering, 'aspect', u.uFlareAspect?.value);
  assignDefined(config.rendering, 'sharpness', u.uFlareSharpness?.value);
  if (u.uAlignToMotion) config.rendering.alignToMotion = u.uAlignToMotion.value > 0.5;
  return config;
}

export function createParticleRandom(seed) {
  let state = Number(seed) >>> 0;
  return () => {
    state = (state + 0x6D2B79F5) >>> 0;
    let value = state;
    value = Math.imul(value ^ (value >>> 15), value | 1);
    value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
    return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
  };
}

export function createParticleSystem(THREE, config, scene) {
  if (config.seed === undefined || config.seed === null) {
    config.seed = Math.floor(Math.random() * 0x100000000) >>> 0;
  }
  const random = createParticleRandom(config.seed);

  // Use ?? (nullish coalescing) to preserve 0 values - || would treat 0 as falsy
  const count = config.count ?? 50000;
  const particleSize = config.particleSize ?? 0.05;
  const sizeVariation = config.sizeVariation ?? 0.5;
  const lifetime = config.lifetime ?? 5.0;
  const lifetimeVariation = config.lifetimeVariation ?? 0.5;
  
  // Emitter settings
  const emitterType = config.emitter?.type || 'sphere';
  const emitterRadius = config.emitter?.radius ?? 5.0;
  const emitterWidth = config.emitter?.width ?? 5.0;
  const emitterHeight = config.emitter?.height ?? 5.0;
  const emitterDepth = config.emitter?.depth ?? 5.0;
  const emitterAngle = config.emitter?.angle ?? 0.5;
  const emissionRate = config.emitter?.rate ?? 1.0;
  const burstCount = config.emitter?.burstCount ?? 0;
  const burstInterval = config.emitter?.burstInterval ?? 1.0;
  
  // Initial velocity
  const initVelX = config.velocity?.x ?? 0;
  const initVelY = config.velocity?.y ?? 0;
  const initVelZ = config.velocity?.z ?? 0;
  const velSpread = config.velocity?.spread ?? 1.0;
  const velRadial = config.velocity?.radial ?? 0;
  
  // Physics
  const gravityX = config.physics?.gravity?.x ?? 0;
  const gravityY = config.physics?.gravity?.y ?? 0;
  const gravityZ = config.physics?.gravity?.z ?? 0;
  const drag = config.physics?.drag ?? 0.01;
  const bounce = config.physics?.bounce ?? 0.5;
  const floorY = config.physics?.floorY ?? -10;
  const floorEnabled = config.physics?.floorEnabled ?? false;
  
  // Turbulence effector
  const turbEnabled = config.turbulence?.enabled ?? false;
  const turbStrength = config.turbulence?.strength ?? 1.0;
  const turbScale = config.turbulence?.scale ?? 1.0;
  const turbSpeed = config.turbulence?.speed ?? 0.5;
  const turbOctaves = config.turbulence?.octaves ?? 3;
  
  // Vortex effector
  const vortexEnabled = config.vortex?.enabled ?? false;
  const vortexStrength = config.vortex?.strength ?? 1.0;
  const vortexAxisX = config.vortex?.axis?.x ?? 0;
  const vortexAxisY = config.vortex?.axis?.y ?? 1;
  const vortexAxisZ = config.vortex?.axis?.z ?? 0;
  const vortexCenterX = config.vortex?.center?.x ?? 0;
  const vortexCenterY = config.vortex?.center?.y ?? 0;
  const vortexCenterZ = config.vortex?.center?.z ?? 0;
  const vortexInwardForce = config.vortex?.inwardForce ?? 0;
  
  // Attractor effector
  const attractorEnabled = config.attractor?.enabled ?? false;
  const attractorStrength = config.attractor?.strength ?? 1.0;
  const attractorX = config.attractor?.position?.x ?? 0;
  const attractorY = config.attractor?.position?.y ?? 0;
  const attractorZ = config.attractor?.position?.z ?? 0;
  const attractorRadius = config.attractor?.radius ?? 5.0;
  const attractorFalloff = config.attractor?.falloff ?? 2.0;
  const attractorKillZone = config.attractor?.killZone ?? 0;
  
  // Curl noise effector (for fluid-like motion)
  const curlEnabled = config.curl?.enabled ?? false;
  const curlStrength = config.curl?.strength ?? 1.0;
  const curlScale = config.curl?.scale ?? 0.5;
  const curlSpeed = config.curl?.speed ?? 0.3;
  const curlEvolution = config.curl?.evolution ?? 0.1;
  
  // Liquid/Cohesion effector
  const cohesionEnabled = config.cohesion?.enabled ?? false;
  const cohesionStrength = config.cohesion?.strength ?? 0.5;
  const cohesionRadius = config.cohesion?.radius ?? 1.0;
  const surfaceTension = config.cohesion?.surfaceTension ?? 0.3;
  const viscosity = config.cohesion?.viscosity ?? 0.1;
  
  // Wind effector
  const windEnabled = config.wind?.enabled ?? false;
  const windX = config.wind?.direction?.x ?? 1;
  const windY = config.wind?.direction?.y ?? 0;
  const windZ = config.wind?.direction?.z ?? 0;
  const windStrength = config.wind?.strength ?? 1.0;
  const windGust = config.wind?.gust ?? 0.3;
  const windGustFreq = config.wind?.gustFrequency ?? 0.5;
  
  // Swarm/Flocking effector
  const swarmEnabled = config.swarm?.enabled ?? false;
  const swarmSeparation = config.swarm?.separation ?? 1.0;
  const swarmAlignment = config.swarm?.alignment ?? 0.5;
  const swarmCohesion = config.swarm?.cohesion ?? 0.3;
  const swarmPerceptionRadius = config.swarm?.perceptionRadius ?? 2.0;
  const swarmMaxSpeed = config.swarm?.maxSpeed ?? 2.0;
  const swarmMaxForce = config.swarm?.maxForce ?? 0.1;
  
  // Oscillation effector (wave-like undulation)
  const oscillationEnabled = config.oscillation?.enabled ?? false;
  const oscillationAmplitude = config.oscillation?.amplitude ?? 0.5;
  const oscillationFrequency = config.oscillation?.frequency ?? 1.0;
  const oscillationPhaseOffset = config.oscillation?.phaseOffset ?? 0;
  const oscillationAxisX = config.oscillation?.axis?.x ?? 0;
  const oscillationAxisY = config.oscillation?.axis?.y ?? 1;
  const oscillationAxisZ = config.oscillation?.axis?.z ?? 0;
  const oscillationRadial = config.oscillation?.radial ?? false;
  
  // Buoyancy effector (temperature-based rise/fall)
  const buoyancyEnabled = config.buoyancy?.enabled ?? false;
  const buoyancyTemperature = config.buoyancy?.temperature ?? 0.5;
  const buoyancyDensity = config.buoyancy?.density ?? 1.0;
  const buoyancyThermalNoise = config.buoyancy?.thermalNoise ?? 0.2;
  const buoyancyCoolingRate = config.buoyancy?.coolingRate ?? 0.1;
  
  // Membrane effector (stretchy connections)
  const membraneEnabled = config.membrane?.enabled ?? false;
  const membraneStiffness = config.membrane?.stiffness ?? 0.5;
  const membraneMaxStretch = config.membrane?.maxStretch ?? 2.0;
  const membraneConnectionRadius = config.membrane?.connectionRadius ?? 1.0;
  const membraneDamping = config.membrane?.damping ?? 0.1;
  
  // Size over lifetime
  const sizeStart = config.sizeOverLife?.start ?? 1.0;
  const sizeMid = config.sizeOverLife?.mid ?? 1.0;
  const sizeEnd = config.sizeOverLife?.end ?? 0.0;
  const sizeMidPoint = config.sizeOverLife?.midPoint ?? 0.5;
  
  // Opacity over lifetime
  const opacityStart = config.opacityOverLife?.start ?? 0.0;
  const opacityMid = config.opacityOverLife?.mid ?? 1.0;
  const opacityEnd = config.opacityOverLife?.end ?? 0.0;
  const opacityMidPoint = config.opacityOverLife?.midPoint ?? 0.3;
  
  // Rotation
  const rotationSpeed = config.rotation?.speed ?? 0;
  const rotationVariation = config.rotation?.variation ?? 1.0;
  
  // Colors
  const colorStart = new THREE.Color(config.colors?.start || '#ffffff');
  const colorMid = new THREE.Color(config.colors?.mid || '#ffaa00');
  const colorEnd = new THREE.Color(config.colors?.end || '#ff0000');
  const colorMidPoint = config.colors?.midPoint ?? 0.5;
  const colorByVelocity = config.colors?.byVelocity ?? false;
  const colorVelScale = config.colors?.velocityScale ?? 1.0;
  
  // Rendering
  const blendMode = config.rendering?.blendMode || 'additive';
  const depthWrite = config.rendering?.depthWrite ?? false;
  const sortParticles = config.rendering?.sort ?? false;
  const glowIntensity = config.rendering?.glow ?? 0.5;
  const trailEnabled = config.rendering?.trails ?? false;
  const trailLength = config.rendering?.trailLength ?? 5;
  const shapeType = {
    orb: 0,
    star: 1,
    streak: 2,
    ring: 3,
    diamond: 4,
  }[config.rendering?.shape || 'orb'] ?? 0;
  const flareAspect = config.rendering?.aspect ?? 1.0;
  const flareSharpness = config.rendering?.sharpness ?? 2.0;
  const alignToMotion = config.rendering?.alignToMotion ?? false;
  
  // Create geometry with particle attributes
  const geometry = new THREE.BufferGeometry();
  
  // Position (current)
  const positions = new Float32Array(count * 3);
  // Velocity
  const velocities = new Float32Array(count * 3);
  // Initial position (for respawn)
  const initPositions = new Float32Array(count * 3);
  // Life data: x=age, y=lifetime, z=random seed, w=size
  const lifeData = new Float32Array(count * 4);
  // Random values for variation
  const randoms = new Float32Array(count * 4);
  
  // Initialize particles based on emitter type
  for (let i = 0; i < count; i++) {
    let x = 0, y = 0, z = 0;
    let vx = initVelX, vy = initVelY, vz = initVelZ;
    
    // Random seed for this particle
    const seed = random();
    const seed2 = random();
    const seed3 = random();
    const seed4 = random();
    
    // Emitter shape
    if (emitterType === 'point') {
      x = 0; y = 0; z = 0;
    } else if (emitterType === 'sphere') {
      const theta = random() * Math.PI * 2;
      const phi = Math.acos(2 * random() - 1);
      const r = Math.pow(random(), 1/3) * emitterRadius; // Volume distribution
      x = r * Math.sin(phi) * Math.cos(theta);
      y = r * Math.sin(phi) * Math.sin(theta);
      z = r * Math.cos(phi);
      // Radial velocity
      if (velRadial !== 0) {
        const len = Math.sqrt(x*x + y*y + z*z) || 1;
        vx += (x/len) * velRadial;
        vy += (y/len) * velRadial;
        vz += (z/len) * velRadial;
      }
    } else if (emitterType === 'sphereSurface') {
      const theta = random() * Math.PI * 2;
      const phi = Math.acos(2 * random() - 1);
      x = emitterRadius * Math.sin(phi) * Math.cos(theta);
      y = emitterRadius * Math.sin(phi) * Math.sin(theta);
      z = emitterRadius * Math.cos(phi);
      if (velRadial !== 0) {
        const len = Math.sqrt(x*x + y*y + z*z) || 1;
        vx += (x/len) * velRadial;
        vy += (y/len) * velRadial;
        vz += (z/len) * velRadial;
      }
    } else if (emitterType === 'box') {
      x = (random() - 0.5) * emitterWidth;
      y = (random() - 0.5) * emitterHeight;
      z = (random() - 0.5) * emitterDepth;
    } else if (emitterType === 'cone') {
      const t = random();
      const angle = random() * Math.PI * 2;
      const r = t * Math.tan(emitterAngle) * emitterHeight;
      x = Math.cos(angle) * r;
      y = t * emitterHeight;
      z = Math.sin(angle) * r;
      // Velocity follows cone direction
      const coneDir = Math.sqrt(x*x + z*z) || 1;
      vx += (x/coneDir) * velSpread * random();
      vy += velSpread * (0.5 + random() * 0.5);
      vz += (z/coneDir) * velSpread * random();
    } else if (emitterType === 'ring') {
      const angle = random() * Math.PI * 2;
      x = Math.cos(angle) * emitterRadius;
      y = 0;
      z = Math.sin(angle) * emitterRadius;
    } else if (emitterType === 'disc') {
      const angle = random() * Math.PI * 2;
      const r = Math.sqrt(random()) * emitterRadius;
      x = Math.cos(angle) * r;
      y = 0;
      z = Math.sin(angle) * r;
    } else if (emitterType === 'line') {
      x = 0;
      y = (random() - 0.5) * emitterHeight;
      z = 0;
    }
    
    // Add velocity spread
    vx += (random() - 0.5) * velSpread;
    vy += (random() - 0.5) * velSpread;
    vz += (random() - 0.5) * velSpread;
    
    // Store initial position
    initPositions[i * 3] = x;
    initPositions[i * 3 + 1] = y;
    initPositions[i * 3 + 2] = z;
    
    // Current position (staggered start)
    positions[i * 3] = x;
    positions[i * 3 + 1] = y;
    positions[i * 3 + 2] = z;
    
    // Velocity
    velocities[i * 3] = vx;
    velocities[i * 3 + 1] = vy;
    velocities[i * 3 + 2] = vz;
    
    // Life data: stagger particle births (store variation factor, multiply by uLifetime in shader)
    const lifetimeVar = 1 + (random() - 0.5) * lifetimeVariation;
    const startAge = random() * lifetime * lifetimeVar; // Stagger births
    lifeData[i * 4] = startAge; // age
    lifeData[i * 4 + 1] = lifetimeVar; // lifetime variation factor (multiplied by uLifetime in shader)
    lifeData[i * 4 + 2] = seed; // random seed
    lifeData[i * 4 + 3] = 1 + (random() - 0.5) * sizeVariation; // size variation factor (multiplied by uParticleSize in shader)
    
    // Random values
    randoms[i * 4] = seed;
    randoms[i * 4 + 1] = seed2;
    randoms[i * 4 + 2] = seed3;
    randoms[i * 4 + 3] = seed4;
  }
  
  geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 3));
  geometry.setAttribute('initPosition', new THREE.BufferAttribute(initPositions, 3));
  geometry.setAttribute('lifeData', new THREE.BufferAttribute(lifeData, 4));
  geometry.setAttribute('random', new THREE.BufferAttribute(randoms, 4));
  
  // Shader material
  const material = new THREE.ShaderMaterial({
    uniforms: {
      uTime: { value: 0 },
      uDeltaTime: { value: 0.016 },
      uParticleSize: { value: particleSize },
      uLifetime: { value: lifetime },
      uEmissionRate: { value: emissionRate },
      
      // Physics
      uGravity: { value: new THREE.Vector3(gravityX, gravityY, gravityZ) },
      uDrag: { value: drag },
      uBounce: { value: bounce },
      uFloorY: { value: floorY },
      uFloorEnabled: { value: floorEnabled ? 1.0 : 0.0 },
      
      // Turbulence
      uTurbEnabled: { value: turbEnabled ? 1.0 : 0.0 },
      uTurbStrength: { value: turbStrength },
      uTurbScale: { value: turbScale },
      uTurbSpeed: { value: turbSpeed },
      uTurbOctaves: { value: turbOctaves },
      
      // Vortex
      uVortexEnabled: { value: vortexEnabled ? 1.0 : 0.0 },
      uVortexStrength: { value: vortexStrength },
      uVortexAxis: { value: new THREE.Vector3(vortexAxisX, vortexAxisY, vortexAxisZ).normalize() },
      uVortexCenter: { value: new THREE.Vector3(vortexCenterX, vortexCenterY, vortexCenterZ) },
      uVortexInward: { value: vortexInwardForce },
      
      // Attractor
      uAttractorEnabled: { value: attractorEnabled ? 1.0 : 0.0 },
      uAttractorStrength: { value: attractorStrength },
      uAttractorPos: { value: new THREE.Vector3(attractorX, attractorY, attractorZ) },
      uAttractorRadius: { value: attractorRadius },
      uAttractorFalloff: { value: attractorFalloff },
      uAttractorKillZone: { value: attractorKillZone },
      
      // Curl noise
      uCurlEnabled: { value: curlEnabled ? 1.0 : 0.0 },
      uCurlStrength: { value: curlStrength },
      uCurlScale: { value: curlScale },
      uCurlSpeed: { value: curlSpeed },
      uCurlEvolution: { value: curlEvolution },
      
      // Cohesion/Liquid
      uCohesionEnabled: { value: cohesionEnabled ? 1.0 : 0.0 },
      uCohesionStrength: { value: cohesionStrength },
      uCohesionRadius: { value: cohesionRadius },
      uSurfaceTension: { value: surfaceTension },
      uViscosity: { value: viscosity },
      
      // Wind
      uWindEnabled: { value: windEnabled ? 1.0 : 0.0 },
      uWindDirection: { value: new THREE.Vector3(windX, windY, windZ).normalize() },
      uWindStrength: { value: windStrength },
      uWindGust: { value: windGust },
      uWindGustFreq: { value: windGustFreq },
      
      // Swarm
      uSwarmEnabled: { value: swarmEnabled ? 1.0 : 0.0 },
      uSwarmSeparation: { value: swarmSeparation },
      uSwarmAlignment: { value: swarmAlignment },
      uSwarmCohesion: { value: swarmCohesion },
      uSwarmPerceptionRadius: { value: swarmPerceptionRadius },
      uSwarmMaxSpeed: { value: swarmMaxSpeed },
      uSwarmMaxForce: { value: swarmMaxForce },
      
      // Oscillation
      uOscillationEnabled: { value: oscillationEnabled ? 1.0 : 0.0 },
      uOscillationAmplitude: { value: oscillationAmplitude },
      uOscillationFrequency: { value: oscillationFrequency },
      uOscillationPhaseOffset: { value: oscillationPhaseOffset },
      uOscillationAxis: { value: new THREE.Vector3(oscillationAxisX, oscillationAxisY, oscillationAxisZ) },
      uOscillationRadial: { value: oscillationRadial ? 1.0 : 0.0 },
      
      // Buoyancy
      uBuoyancyEnabled: { value: buoyancyEnabled ? 1.0 : 0.0 },
      uBuoyancyTemperature: { value: buoyancyTemperature },
      uBuoyancyDensity: { value: buoyancyDensity },
      uBuoyancyThermalNoise: { value: buoyancyThermalNoise },
      uBuoyancyCoolingRate: { value: buoyancyCoolingRate },
      
      // Membrane
      uMembraneEnabled: { value: membraneEnabled ? 1.0 : 0.0 },
      uMembraneStiffness: { value: membraneStiffness },
      uMembraneMaxStretch: { value: membraneMaxStretch },
      uMembraneConnectionRadius: { value: membraneConnectionRadius },
      uMembraneDamping: { value: membraneDamping },
      
      // Size over lifetime
      uSizeStart: { value: sizeStart },
      uSizeMid: { value: sizeMid },
      uSizeEnd: { value: sizeEnd },
      uSizeMidPoint: { value: sizeMidPoint },
      
      // Opacity over lifetime
      uOpacityStart: { value: opacityStart },
      uOpacityMid: { value: opacityMid },
      uOpacityEnd: { value: opacityEnd },
      uOpacityMidPoint: { value: opacityMidPoint },
      
      // Rotation
      uRotationSpeed: { value: rotationSpeed },
      uRotationVariation: { value: rotationVariation },
      
      // Colors
      uColorStart: { value: colorStart },
      uColorMid: { value: colorMid },
      uColorEnd: { value: colorEnd },
      uColorMidPoint: { value: colorMidPoint },
      uColorByVelocity: { value: colorByVelocity ? 1.0 : 0.0 },
      uColorVelScale: { value: colorVelScale },
      
      // Rendering
      uGlowIntensity: { value: glowIntensity },
      uShape: { value: shapeType },
      uFlareAspect: { value: flareAspect },
      uFlareSharpness: { value: flareSharpness },
      uAlignToMotion: { value: alignToMotion ? 1.0 : 0.0 },
      
      // Viewport scaling (for consistent particle size across different render target sizes)
      uViewportHeight: { value: 878.0 }, // Default reference height
    },
    vertexShader: `
      attribute vec3 velocity;
      attribute vec3 initPosition;
      attribute vec4 lifeData; // age, lifetime, seed, size
      attribute vec4 random;
      
      uniform float uTime;
      uniform float uDeltaTime;
      uniform float uParticleSize;
      uniform float uLifetime;
      uniform float uEmissionRate;
      
      // Physics
      uniform vec3 uGravity;
      uniform float uDrag;
      uniform float uBounce;
      uniform float uFloorY;
      uniform float uFloorEnabled;
      
      // Turbulence
      uniform float uTurbEnabled;
      uniform float uTurbStrength;
      uniform float uTurbScale;
      uniform float uTurbSpeed;
      uniform float uTurbOctaves;
      
      // Vortex
      uniform float uVortexEnabled;
      uniform float uVortexStrength;
      uniform vec3 uVortexAxis;
      uniform vec3 uVortexCenter;
      uniform float uVortexInward;
      
      // Attractor
      uniform float uAttractorEnabled;
      uniform float uAttractorStrength;
      uniform vec3 uAttractorPos;
      uniform float uAttractorRadius;
      uniform float uAttractorFalloff;
      uniform float uAttractorKillZone;
      
      // Curl noise
      uniform float uCurlEnabled;
      uniform float uCurlStrength;
      uniform float uCurlScale;
      uniform float uCurlSpeed;
      uniform float uCurlEvolution;
      
      // Cohesion/Liquid
      uniform float uCohesionEnabled;
      uniform float uCohesionStrength;
      uniform float uCohesionRadius;
      uniform float uSurfaceTension;
      uniform float uViscosity;
      
      // Wind
      uniform float uWindEnabled;
      uniform vec3 uWindDirection;
      uniform float uWindStrength;
      uniform float uWindGust;
      uniform float uWindGustFreq;
      
      // Oscillation
      uniform float uOscillationEnabled;
      uniform float uOscillationAmplitude;
      uniform float uOscillationFrequency;
      uniform float uOscillationPhaseOffset;
      uniform vec3 uOscillationAxis;
      uniform float uOscillationRadial;
      
      // Buoyancy
      uniform float uBuoyancyEnabled;
      uniform float uBuoyancyTemperature;
      uniform float uBuoyancyDensity;
      uniform float uBuoyancyThermalNoise;
      uniform float uBuoyancyCoolingRate;
      
      // Membrane
      uniform float uMembraneEnabled;
      uniform float uMembraneStiffness;
      uniform float uMembraneMaxStretch;
      uniform float uMembraneConnectionRadius;
      uniform float uMembraneDamping;
      
      // Size over lifetime
      uniform float uSizeStart;
      uniform float uSizeMid;
      uniform float uSizeEnd;
      uniform float uSizeMidPoint;
      
      // Opacity over lifetime
      uniform float uOpacityStart;
      uniform float uOpacityMid;
      uniform float uOpacityEnd;
      uniform float uOpacityMidPoint;
      
      // Rotation
      uniform float uRotationSpeed;
      uniform float uRotationVariation;
      
      // Colors
      uniform vec3 uColorStart;
      uniform vec3 uColorMid;
      uniform vec3 uColorEnd;
      uniform float uColorMidPoint;
      uniform float uColorByVelocity;
      uniform float uColorVelScale;
      uniform float uAlignToMotion;
      
      // Viewport scaling
      uniform float uViewportHeight;
      
      varying float vLifeRatio;
      varying vec3 vColor;
      varying float vOpacity;
      varying float vRotation;
      varying vec3 vVelocity;
      
      // Noise functions
      vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
      vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
      vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); }
      vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
      
      float snoise(vec3 v) {
        const vec2 C = vec2(1.0/6.0, 1.0/3.0);
        const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
        vec3 i = floor(v + dot(v, C.yyy));
        vec3 x0 = v - i + dot(i, C.xxx);
        vec3 g = step(x0.yzx, x0.xyz);
        vec3 l = 1.0 - g;
        vec3 i1 = min(g.xyz, l.zxy);
        vec3 i2 = max(g.xyz, l.zxy);
        vec3 x1 = x0 - i1 + C.xxx;
        vec3 x2 = x0 - i2 + C.yyy;
        vec3 x3 = x0 - D.yyy;
        i = mod289(i);
        vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0))
          + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));
        float n_ = 0.142857142857;
        vec3 ns = n_ * D.wyz - D.xzx;
        vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
        vec4 x_ = floor(j * ns.z);
        vec4 y_ = floor(j - 7.0 * x_);
        vec4 x = x_ * ns.x + ns.yyyy;
        vec4 y = y_ * ns.x + ns.yyyy;
        vec4 h = 1.0 - abs(x) - abs(y);
        vec4 b0 = vec4(x.xy, y.xy);
        vec4 b1 = vec4(x.zw, y.zw);
        vec4 s0 = floor(b0) * 2.0 + 1.0;
        vec4 s1 = floor(b1) * 2.0 + 1.0;
        vec4 sh = -step(h, vec4(0.0));
        vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
        vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
        vec3 p0 = vec3(a0.xy, h.x);
        vec3 p1 = vec3(a0.zw, h.y);
        vec3 p2 = vec3(a1.xy, h.z);
        vec3 p3 = vec3(a1.zw, h.w);
        vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
        p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
        vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
        m = m * m;
        return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
      }
      
      // Curl noise for fluid-like motion
      vec3 curlNoise(vec3 p) {
        const float e = 0.1;
        vec3 dx = vec3(e, 0.0, 0.0);
        vec3 dy = vec3(0.0, e, 0.0);
        vec3 dz = vec3(0.0, 0.0, e);
        
        float n1 = snoise(p + dy) - snoise(p - dy);
        float n2 = snoise(p + dz) - snoise(p - dz);
        float n3 = snoise(p + dx) - snoise(p - dx);
        float n4 = snoise(p + dz) - snoise(p - dz);
        float n5 = snoise(p + dx) - snoise(p - dx);
        float n6 = snoise(p + dy) - snoise(p - dy);
        
        return normalize(vec3(n1 - n2, n3 - n4, n5 - n6));
      }
      
      // FBM turbulence
      vec3 turbulence(vec3 p, int octaves) {
        vec3 result = vec3(0.0);
        float amplitude = 1.0;
        float frequency = 1.0;
        for (int i = 0; i < 6; i++) {
          if (i >= octaves) break;
          result += vec3(
            snoise(p * frequency),
            snoise(p * frequency + vec3(100.0)),
            snoise(p * frequency + vec3(200.0))
          ) * amplitude;
          frequency *= 2.0;
          amplitude *= 0.5;
        }
        return result;
      }
      
      // Smooth interpolation for lifetime curves
      float smoothCurve(float t, float start, float mid, float end, float midPoint) {
        if (t < midPoint) {
          return mix(start, mid, smoothstep(0.0, midPoint, t));
        } else {
          return mix(mid, end, smoothstep(midPoint, 1.0, t));
        }
      }
      
      void main() {
        float age = lifeData.x;
        float particleLifetime = lifeData.y * uLifetime;
        float seed = lifeData.z;
        float baseSize = lifeData.w * uParticleSize;
        
        // Calculate life ratio (0 = born, 1 = dead)
        float simulatedAge = mod(age + uTime * uEmissionRate, particleLifetime);
        float lifeRatio = simulatedAge / particleLifetime;
        vLifeRatio = lifeRatio;
        
        // Reset position when particle dies
        vec3 pos = position;
        vec3 vel = velocity;
        
        // Simulate from birth
        float simTime = simulatedAge;
        pos = initPosition;
        
        // Apply forces over simulated time
        vec3 acceleration = uGravity;
        
        // Turbulence
        if (uTurbEnabled > 0.5) {
          vec3 turbPos = pos * uTurbScale + uTime * uTurbSpeed;
          vec3 turb = turbulence(turbPos, int(uTurbOctaves));
          acceleration += turb * uTurbStrength;
        }
        
        // Curl noise (fluid-like)
        if (uCurlEnabled > 0.5) {
          vec3 curlPos = pos * uCurlScale + uTime * uCurlSpeed;
          curlPos.y += uTime * uCurlEvolution;
          vec3 curl = curlNoise(curlPos);
          acceleration += curl * uCurlStrength;
        }
        
        // Vortex
        if (uVortexEnabled > 0.5) {
          vec3 toCenter = uVortexCenter - pos;
          vec3 inPlane = toCenter - dot(toCenter, uVortexAxis) * uVortexAxis;
          float dist = length(inPlane);
          if (dist > 0.01) {
            vec3 tangent = normalize(cross(uVortexAxis, inPlane));
            float vortexForce = uVortexStrength / (1.0 + dist * 0.5);
            acceleration += tangent * vortexForce;
            // Inward force
            acceleration += normalize(inPlane) * uVortexInward / (1.0 + dist);
          }
        }
        
        // Attractor
        if (uAttractorEnabled > 0.5) {
          vec3 toAttractor = uAttractorPos - pos;
          float dist = length(toAttractor);
          if (dist > uAttractorKillZone && dist < uAttractorRadius) {
            float falloff = pow(1.0 - dist / uAttractorRadius, uAttractorFalloff);
            acceleration += normalize(toAttractor) * uAttractorStrength * falloff;
          }
        }
        
        // Wind
        if (uWindEnabled > 0.5) {
          float gust = 1.0 + sin(uTime * uWindGustFreq + seed * 10.0) * uWindGust;
          acceleration += uWindDirection * uWindStrength * gust;
        }
        
        // Oscillation (wave-like undulation)
        if (uOscillationEnabled > 0.5) {
          float phase = uTime * uOscillationFrequency + seed * uOscillationPhaseOffset * 6.28;
          float wave = sin(phase) * uOscillationAmplitude;
          
          if (uOscillationRadial > 0.5) {
            // Radial oscillation - expand/contract from center
            vec3 dir = normalize(pos + vec3(0.001));
            acceleration += dir * wave * 2.0;
          } else {
            // Axis-based oscillation
            acceleration += uOscillationAxis * wave * 2.0;
          }
        }
        
        // Buoyancy (temperature-based rise/fall)
        if (uBuoyancyEnabled > 0.5) {
          // Temperature decreases with lifetime (cooling)
          float temp = uBuoyancyTemperature * (1.0 - lifeRatio * uBuoyancyCoolingRate);
          
          // Add thermal noise for organic feel
          float thermalNoise = sin(pos.x * 3.0 + uTime) * cos(pos.z * 3.0 + uTime * 0.7) * uBuoyancyThermalNoise;
          temp += thermalNoise;
          
          // Buoyancy force: hot rises, cold sinks
          float buoyancyForce = (temp - 0.5) * 2.0 / uBuoyancyDensity;
          acceleration.y += buoyancyForce;
        }
        
        // Membrane (stretchy connections - simulated with center attraction)
        if (uMembraneEnabled > 0.5) {
          // Pull towards a local center point (simulates membrane tension)
          vec3 localCenter = initPosition;
          vec3 toCenter = localCenter - pos;
          float stretch = length(toCenter);
          
          if (stretch > 0.01 && stretch < uMembraneMaxStretch * uMembraneConnectionRadius) {
            // Spring force with damping
            float springForce = uMembraneStiffness * stretch;
            float dampForce = uMembraneDamping * dot(vel, normalize(toCenter));
            acceleration += normalize(toCenter) * (springForce - dampForce);
          }
        }
        
        // Apply drag
        vel = vel * pow(1.0 - uDrag, simTime * 60.0);
        
        // Integrate
        vel += acceleration * simTime;
        pos += velocity * simTime + 0.5 * acceleration * simTime * simTime;
        
        // Floor collision
        if (uFloorEnabled > 0.5 && pos.y < uFloorY) {
          pos.y = uFloorY;
          vel.y = abs(vel.y) * uBounce;
        }
        
        // Size over lifetime
        float size = baseSize * smoothCurve(lifeRatio, uSizeStart, uSizeMid, uSizeEnd, uSizeMidPoint);
        
        // Opacity over lifetime
        vOpacity = smoothCurve(lifeRatio, uOpacityStart, uOpacityMid, uOpacityEnd, uOpacityMidPoint);
        
        // Color over lifetime
        vec3 lifeColor;
        if (lifeRatio < uColorMidPoint) {
          lifeColor = mix(uColorStart, uColorMid, lifeRatio / uColorMidPoint);
        } else {
          lifeColor = mix(uColorMid, uColorEnd, (lifeRatio - uColorMidPoint) / (1.0 - uColorMidPoint));
        }
        
        // Color by velocity
        if (uColorByVelocity > 0.5) {
          float speed = length(vel) * uColorVelScale;
          lifeColor = mix(uColorStart, uColorEnd, clamp(speed, 0.0, 1.0));
        }
        
        vColor = lifeColor;
        vVelocity = vel;
        
        // Rotation
        vRotation = uTime * uRotationSpeed * (1.0 + random.x * uRotationVariation) + random.y * 6.28;
        if (uAlignToMotion > 0.5) {
          vRotation = atan(pos.y, pos.x);
        }
        
        // Transform
        vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
        gl_Position = projectionMatrix * mvPosition;
        // Scale particle size based on viewport height for consistent appearance
        // Use a reference height of 878 (typical editor viewport height)
        float scaleFactor = uViewportHeight / 878.0;
        gl_PointSize = size * scaleFactor * (300.0 / -mvPosition.z);
      }
    `,
    fragmentShader: `
      uniform float uTime;
      uniform float uGlowIntensity;
      uniform float uShape;
      uniform float uFlareAspect;
      uniform float uFlareSharpness;
      
      varying float vLifeRatio;
      varying vec3 vColor;
      varying float vOpacity;
      varying float vRotation;
      varying vec3 vVelocity;
      
      void main() {
        // Circular particle with soft edge
        vec2 center = gl_PointCoord - 0.5;
        
        // Apply rotation
        float c = cos(vRotation);
        float s = sin(vRotation);
        center = mat2(c, -s, s, c) * center;
        
        float dist = length(center);
        float sharpness = max(0.25, uFlareSharpness);
        float alpha;

        if (uShape < 0.5) {
          // Soft photographic bokeh orb.
          alpha = 1.0 - smoothstep(0.28, 0.5, dist);
        } else if (uShape < 1.5) {
          // Multi-axis diffraction star with a hot central core.
          float core = 1.0 - smoothstep(0.06, 0.24, dist);
          float horizontal = exp(-abs(center.y) * sharpness * 18.0)
            * (1.0 - smoothstep(0.1, 0.5, abs(center.x)));
          float vertical = exp(-abs(center.x) * sharpness * 18.0)
            * (1.0 - smoothstep(0.1, 0.5, abs(center.y)));
          vec2 diagonalCoord = mat2(0.7071, -0.7071, 0.7071, 0.7071) * center;
          float diagonal = exp(-abs(diagonalCoord.y) * sharpness * 24.0)
            * (1.0 - smoothstep(0.08, 0.42, abs(diagonalCoord.x)));
          alpha = max(core, max(horizontal, vertical) * 0.9);
          alpha = max(alpha, diagonal * 0.55);
        } else if (uShape < 2.5) {
          // Anamorphic streak, optionally aligned radially for forward flight.
          vec2 streakCoord = center;
          streakCoord.y *= max(0.25, uFlareAspect);
          float body = exp(-abs(streakCoord.y) * sharpness * 7.0)
            * (1.0 - smoothstep(0.18, 0.5, abs(streakCoord.x)));
          float core = 1.0 - smoothstep(0.012, 0.055, length(streakCoord));
          alpha = max(body, core);
        } else if (uShape < 3.5) {
          // Optical ring / halo flare.
          float band = abs(dist - 0.31);
          alpha = 1.0 - smoothstep(0.008, 0.012 + 0.055 / sharpness, band);
          alpha += (1.0 - smoothstep(0.0, 0.1, dist)) * 0.35;
        } else {
          // Faceted diamond spark.
          float diamondDistance = abs(center.x) + abs(center.y);
          float body = 1.0 - smoothstep(0.22, 0.5, diamondDistance);
          float horizontal = exp(-abs(center.y) * sharpness * 22.0)
            * (1.0 - smoothstep(0.08, 0.5, abs(center.x)));
          alpha = max(body, horizontal * 0.75);
        }

        // Shared luminous halo keeps every shape usable as a flare layer.
        float glow = exp(-dist * (2.5 + sharpness * 0.45)) * uGlowIntensity;
        
        vec3 finalColor = vColor + vColor * glow;
        float finalAlpha = (alpha + glow * 0.5) * vOpacity;
        float spriteMask = 1.0 - smoothstep(
          0.42,
          0.5,
          max(abs(center.x), abs(center.y))
        );
        finalAlpha *= spriteMask;
        
        if (finalAlpha < 0.01) discard;
        
        gl_FragColor = vec4(finalColor, finalAlpha);
      }
    `,
    transparent: true,
    depthWrite: depthWrite,
    blending: blendMode === 'additive' ? THREE.AdditiveBlending : 
              blendMode === 'multiply' ? THREE.MultiplyBlending : THREE.NormalBlending,
  });
  
  const particles = new THREE.Points(geometry, material);
  particles.frustumCulled = false;
  particles.position.set(
    config.offsetX ?? 0,
    config.offsetY ?? 0,
    config.offsetZ ?? 0,
  );
  scene.add(particles);
  
  console.log(`Particle system: ${count} particles, emitter: ${emitterType}`);
  
  return particles;
}
