<!-- SOLID CITY · part 3412 -->
e; f.s.visible = true; f.s.material.color.copy(col || (ring ? COL.ringC : COL.flashC));
}
const COL = { fire: new THREE.Color(1, 0.45, 0.12), gold: new THREE.Color(1, 0.8, 0.35), green: new THREE.Color(0.25, 1, 0.5), white: new THREE.Color(1, 1, 1), smoke: new THREE.Color(0.25, 0.22, 0.24), red: new THREE.Color(1, 0.15, 0.2), magenta: new THREE.Color(0.9, 0.3, 1), ringC: new THREE.Color(1.6, 1.4, 1), flashC: new THREE.Color(3, 2, 1), muzzle: new THREE.Color(3, 0.6, 0.5) };
const tv = new V3(), tv2 = new V3(), tv3 = new V3(), tv4 = new V3();
function explode(pos, scale, tint) {
  const n = Math.round((MOBILE ? 34 : 54) * Math.min(scale, 2.4));
  for (let k = 0; k < n; k++) { tv4.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize().multiplyScalar((40 + Math.random() * 90) * scale); emit(sparks, pos, tv4, k % 3 === 0 ? (tint || COL.green) : k % 3 === 1 ? COL.fire : COL.gold, 0.5 + Math.random() * 0.8, (2.2 + Math.random() * 3) * scale); }
  for (let k = 0; k < 8; k++) { tv4.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).multiplyScalar(26 * scale); emit(sparks, pos, tv4, COL.smoke, 1.4 + Math.random(), 7 * scale, 0.5); }
  flash(pos, 46 * scale, false); flash(pos, 30 * scale, true);
}
function hitSpark(pos, tint) { for (let k = 0; k < 7; k++) { tv4.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize().multiplyScalar(30 + Math.random() * 50); emit(sparks, pos, tv4, k % 2 ? COL.gold : (tint || COL.white), 0.3 + Math.random() * 0.25, 1.6); } }

/* --- guide arrow (RETURN HOME) --- */
const guide = (() => {
  const s = new THREE.Shape(); s.moveTo(0, 3.2); s.lineTo(2.2, 0.6); s.lineTo(0.8, 0.6); s.lineTo(0.8, -2.6); s.lineTo(-0.8, -2.6); s.lineTo(-0.8, 0.6); s.lineTo(-2.2, 0.6); s.closePath();
  const g = new THREE.ExtrudeGeometry(s, { depth: 0.6, bevelEnabled: true, bevelSize: 0.15, bevelThickness: 0.15, bevelSegments: 1 }); g.translate(0, 0, -0.3); g.rotateX(Math.PI / 2);   // tip -> +Z
  const grp = new THREE.Group();
  grp.add(new THREE.Mesh(g, new THREE.MeshBasicMaterial({ color: new THREE.Color(3, 2.1, 0.7) })));
  const halo = new THREE.Mesh(g, new THREE.MeshBasicMaterial({ color: new THREE.Color(1.6, 0.5, 0.1), transparent: true, opacity: 0.5, blending: THREE.AdditiveBlending, depthWrite: false })); halo.scale.setScalar(1.35); grp.add(halo);
  grp.visible = false; grp.renderOrder = 8; scene.add(grp); return grp;
})();

/* ======================= AUDIO (WebAudio synth; no sound files) ======================= */
const AU = { ctx: null, muted: (() => { try { return localStorage.getItem('space-muted') === '1'; } catch (e) { return false; } })() };
function auInit() {
  if (AU.ctx) { if (AU.ctx.state !== 'running') AU.ctx.resume(); return; }
  try {
    const C = new (window.AudioContext || window.webkitAudioContext)(); AU.ctx = C;
    AU.master = C.createGain(); AU.master.gain.value = AU.muted ? 0 : 0.55; AU.master.connect(C.destination);
    const nb = C.createBuffer(1, C.sampleRate * 2, C.sampleRate), d = nb.getChannelData(0); for (let i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1; AU.noise = nb;
    const o1 = C.createOscillator(), o2 = C.createOscillator(); o1.type = 'sawtooth'; o2.type = 'sawtooth'; o1.frequency.value = 48; o2.frequency.value = 48.7;
    const lp = C.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 300; const eg = C.createGain(); eg.gain.value = 0;
    o1.connect(lp); o2.connect(lp); lp.connect(eg); eg.connect(AU.master); o1.start(); o2.start();
    const ns = C.createBufferSource(); ns.buffer = nb; ns.loop = true; const bp = C.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.value = 700; bp.Q.value = 0.7; const ng = C.createGain(); ng.gain.value = 0;
    ns.connect(bp); bp.connect(ng); ng.connect(AU.master); ns.start();
    AU.eng = { o1, o2, lp, eg, bp, ng };
    if (C.state !== 'running') C.resume();
  } catch (e) { AU.ctx = null; }
}
function auEngine(level, pitch, rumble) {
  if (!AU.ctx || !AU.eng) return; const t = AU.ctx.currentTime, E = AU.eng;
  E.o1.frequency.setTargetAtTime(40 + pitch * 70, t, 0.1); E.o2.frequency.setTargetAtTime(40.8 + pitch * 71, t, 0.1);
  E.lp.frequency.setTargetAtTime(220 + pitch * 1500, t, 0.1); E.eg.gain.setTargetAtTime(level * 0.16, t, 0.1);
  E.bp.frequency.setTargetAtTime(500 + pitch * 1800, t, 0.1); E.ng.gain.setTargetAtTime(level * 0.08 + rumble * 0.32, t, 0.12);
}
function sfx(kind) {
  if (!AU.ctx || AU.muted || AU.ctx.state !== 'running') return; const C = AU.ctx, t = C.currentTime;
  const env = (node, a, d, peak) => { const g = C.createGain(); g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(peak, t + a); g.gain.exponentialRampToValueAtTime(0.0001, t + a + d); node.connect(g); g.connect(AU.master); return g; };
  const noise = (dur, type, f, q) => { const s = C.createBufferSource(); s.buffer = AU.noise; const fl = C.createBiquadFilter(); fl.type = type; fl.frequency.value = f; fl.Q.value = q