<!-- SOLID CITY · part 3414 -->
.clientY - cy); e.preventDefault(); });
  st.addEventListener('pointermove', (e) => { if (e.pointerId === id) set(e.clientX - cx, e.clientY - cy); });
  const end = (e) => { if (e.pointerId === id) { id = null; set(0, 0); } };
  st.addEventListener('pointerup', end); st.addEventListener('pointercancel', end); st.addEventListener('lostpointercapture', end);
})();
function holdBtn(el, on, off) {
  el.addEventListener('pointerdown', (e) => { e.preventDefault(); try { el.setPointerCapture(e.pointerId); } catch (er) { /* ignore */ } el.classList.add('on'); on(); });
  const up = () => { el.classList.remove('on'); off(); }; ['pointerup', 'pointercancel', 'lostpointercapture'].forEach((ev) => el.addEventListener(ev, up));
}
holdBtn(H['b-fire'], () => { input.fire = true; }, () => { input.fire = false; });
holdBtn(H['b-boost'], () => { input.boost = true; }, () => { input.boost = false; });
addEventListener('keydown', (e) => {
  if (e.target && e.target.tagName === 'INPUT') return;
  input.keys[e.code] = true; if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.code)) e.preventDefault();
  if (e.code === 'KeyM') { input.mouseAim = !input.mouseAim; banner(input.mouseAim ? 'MOUSE AIM ON' : 'MOUSE AIM OFF', 'Press M to toggle', 1.5); }
  if ((e.code === 'Space' || e.code === 'Enter') && S.phase === 'pad') launch();
});
addEventListener('keyup', (e) => { input.keys[e.code] = false; });
addEventListener('blur', () => { input.keys = {}; input.fire = input.boost = input.mouseFire = false; });
canvas.addEventListener('pointermove', (e) => { if (e.pointerType !== 'mouse') return; const [w, h] = vp(); input.mouse = { x: (e.clientX / w) * 2 - 1, y: -((e.clientY / h) * 2 - 1) }; });
canvas.addEventListener('pointerleave', (e) => { if (e.pointerType === 'mouse') input.mouse = null; });
canvas.addEventListener('pointerdown', (e) => { if (e.pointerType === 'mouse' && e.button === 0) input.mouseFire = true; });
addEventListener('pointerup', (e) => { if (e.pointerType === 'mouse') input.mouseFire = false; });
document.addEventListener('touchmove', (e) => { if (!(e.target.closest && e.target.closest('.scroll'))) e.preventDefault(); }, { passive: false });
document.addEventListener('gesturestart', (e) => e.preventDefault());
document.addEventListener('visibilitychange', () => { if (document.hidden) { input.keys = {}; input.fire = input.boost = false; if (AU.ctx) AU.ctx.suspend(); } else if (AU.ctx && S.running) AU.ctx.resume(); });

function readInput(dt) {
  const k = input.keys; let x = input.stickX, y = input.stickY;
  if (k.KeyA || k.ArrowLeft) x -= 1; if (k.KeyD || k.ArrowRight) x += 1; if (k.KeyW || k.ArrowUp) y += 1; if (k.KeyS || k.ArrowDown) y -= 1;
  if (input.mouseAim && input.mouse && !IS_TOUCH && (S.phase === 'battle' || S.phase === 'return' || S.phase === 'climb')) {   // mouse aim: steer toward the cursor
    const dz = 0.07, mx = input.mouse.x, my = input.mouse.y - 0.1;
    x += Math.sign(mx) * clamp((Math.abs(mx) - dz) / 0.55, 0, 1); y += Math.sign(my) * clamp((Math.abs(my) - dz) / 0.5, 0, 1);
  }
  let fire = !!(input.fire || k.Space || input.mouseFire), boost = !!(input.boost || k.ShiftLeft || k.ShiftRight);
  if (bot.on) { const b = botInput(); x = b.x; y = b.y; fire = b.fire; boost = b.boost; }
  x = clamp(x, -1, 1); y = clamp(y, -1, 1);
  input.x = damp(input.x, x, 10, dt); input.y = damp(input.y, y, 10, dt); input.firing = fire; input.boosting = boost;
}

/* ======================= PHASES ======================= */
function setPhase(p) { S.phase = p; S.phaseT = 0; document.body.dataset.phase = p; }
function placeOnPad() {
  S.pos.set(0, SHIP_REST, 0); S.yaw = PAD_HEADING; S.pitch = 0; S.roll = 0; S.speed = 0; S.vUp = 0;
  S.boostMeter = 1; S.shields = 100; S.kills = 0; S.hits = 0; S.wave = 0; S.runT = 0; S.topSpeed = 0; S.heat = 0; S.lock = null; S.invuln = 0; S.slowT = 0;
  S.landing = null; S.homeShown = false; S.result = null; S.saved = null; S.shots = 0;
  ship.visible = true; guide.visible = false; camState.init = false;
}
function reset() {
  aliens.forEach((a) => { scene.remove(a.mesh); a.hull.dispose(); }); aliens.length = 0;
  lasers.forEach((l) => { l.on = false; }); orbs.forEach((o) => { o.on = false; });
  placeOnPad(); setPhase('pad'); $('result').hidden = true; $('hud').hidden = false; H['b-launch'].hidden = false; H['h-mother'].hidden = true; H['h-count'].hidden = true;
  banner('THE HANGAR PAD', IS_TOUCH ? 'Tap LAUNCH to blast off' : 'Click LAUNCH or press Space', 4);
}
let countN = 0;
function launch() { if (S.phase !== 'pad') return; auInit(); H['b-launch'].hidden = true; setPhase('count'); countN = 4; H['h-count'].hidden = false; }
function enterSpace() { setPhase('battle'); S.wave = 0; S.waveT = 1.2; banner('SPACE', 'Alien fleet detected · hit them down', 3.2); sfx('alarm'); }
function spawnWave(w) {
  S.wave = w; const f = forward(new V3(), S.yaw, 0), r = new V3(-f.z, 0, f.x), P = S.pos;
  S.checkpoint = { pos: P.clone(), yaw: S.yaw }