<!-- SOLID CITY · part 3420 -->
f.t / f.life); f.s.scale.setScalar(f.size * (f.ring ? 0.3 + k * 1.6 : 1 - k * 0.5)); f.s.material.opacity = 1 - k; }
  if ((S.phase === 'return' || S.phase === 'landing') && S.homeShown) {   // guide arrow ahead of the nose, pointing home
    guide.visible = true; guide.position.copy(S.pos).addScaledVector(forward(tv3), 16); guide.position.y += 4.2;
    guide.lookAt(homeV); guide.scale.setScalar((S.phase === 'landing' ? 0.55 : 0.8) * (1 + 0.08 * Math.sin(S.t * 6)));
  } else guide.visible = false;
}

/* --- HUD --- */
const projV = new V3(), scr = {};
function toScreen(p, out) { projV.copy(p).project(camera); const [w, h] = vp(); out.x = (projV.x * 0.5 + 0.5) * w; out.y = (-projV.y * 0.5 + 0.5) * h; out.behind = projV.z > 1; return out; }
function placeMarker(m, p, cls) {
  const [w, h] = vp(); toScreen(p, scr); let x = scr.x, y = scr.y; const off = scr.behind || x < 0 || x > w || y < 0 || y > h;
  if (scr.behind) { x = w - x; y = h - y; }
  if (off) { const cx = w / 2, cy = h / 2, dx = x - cx, dy = y - cy, s = Math.min((w / 2 - 30) / Math.max(1e-3, Math.abs(dx)), (h / 2 - 100) / Math.max(1e-3, Math.abs(dy))); x = cx + dx * s; y = cy + dy * s; m.className = 'mk off ' + cls; m.style.transform = `translate(${x.toFixed(0)}px,${y.toFixed(0)}px) rotate(${Math.atan2(dy, dx).toFixed(2)}rad)`; }
  else { m.className = 'mk ' + cls; m.style.transform = `translate(${x.toFixed(0)}px,${y.toFixed(0)}px)`; }
  m.hidden = false;
}
function updateHud(dt) {
  const ph = S.phase, alt = altOf(S.pos.y);
  setText(H['h-spd'], 'spd', String(Math.round(S.speed * KMH)));
  setText(H['h-alt'], 'alt', 'ALT ' + (alt > 1000 ? (alt / 1000).toFixed(1) + ' km' : Math.round(alt) + ' m'));
  setText(H['h-kills'], 'kills', S.kills + '/' + TOTAL_ALIENS);
  setText(H['h-time'], 'time', fmtT(S.runT));
  const shp = Math.round(S.shields); if (lastHud.sh !== shp) { lastHud.sh = shp; H['h-shield'].style.width = shp + '%'; H['h-shield'].parentNode.classList.toggle('low', shp < 30); }
  const bo = Math.round(S.boostMeter * 100); if (lastHud.bo !== bo) { lastHud.bo = bo; H['h-boost'].style.width = bo + '%'; H['b-boost'].style.setProperty('--m', bo + '%'); }
  H['b-boost'].classList.toggle('hot', S.boosting); H['h-speedo'].classList.toggle('boost', S.boosting);
  setText(H['h-arc'], 'arc', ''); H['h-arc'].style.strokeDashoffset = String(Math.round(100 - clamp(S.speed * KMH / 2400, 0, 1) * 100));
  let obj = '';
  if (ph === 'pad') obj = 'ON THE HANGAR PAD · READY';
  else if (ph === 'count') obj = 'COUNTDOWN';
  else if (ph === 'climb') obj = 'CLIMB TO SPACE · ' + (alt / 1000).toFixed(1) + ' / ' + SPACE_ALT / 1000 + ' km';
  else if (ph === 'battle') obj = S.wave ? 'HIT THEM DOWN · WAVE ' + S.wave + '/3' : 'ALIEN FLEET INBOUND';
  else if (ph === 'respawn') obj = 'RESPAWNING…';
  else if (ph === 'return') obj = (S.heat > 0.3 ? 'RE-ENTRY · ' : 'RETURN HOME · ') + (Math.hypot(Math.hypot(S.pos.x, S.pos.z), alt) / 1000).toFixed(1) + ' km';
  else if (ph === 'landing') obj = 'PARK ON THE GLOWING CIRCLE · ' + Math.round(Math.hypot(S.pos.x, S.pos.z)) + ' m';
  else if (ph === 'parked' || ph === 'result') obj = 'PARKED · MISSION COMPLETE';
  setText(H['h-obj'], 'obj', obj);
  if (bannerT > 0) { bannerT -= dt; if (bannerT <= 0) H['h-banner'].hidden = true; }
  S.hurt = Math.max(0, S.hurt - dt * 1.8); H['h-hurt'].style.opacity = (S.hurt * 0.9 + (S.shields < 30 && ph === 'battle' ? 0.25 + 0.15 * Math.sin(S.t * 8) : 0)).toFixed(3);
  H['h-heat'].style.opacity = (S.heat * 0.85).toFixed(3);
  const flying = ph === 'battle' || ph === 'return' || ph === 'climb';
  if (flying && ship.visible) { toScreen(tv.copy(S.pos).addScaledVector(forward(tv3), 700), scr); H['h-reticle'].hidden = scr.behind; H['h-reticle'].style.transform = `translate(${scr.x.toFixed(1)}px,${scr.y.toFixed(1)}px)`; } else H['h-reticle'].hidden = true;
  if (S.lock && flying) { toScreen(S.lock.pos, scr); const sz = clamp(S.lock.r * 2 * 900 / Math.max(1, S.lock.pos.distanceTo(camera.position)), 30, 120); H['h-lock'].hidden = scr.behind; H['h-lock'].style.transform = `translate(${scr.x.toFixed(1)}px,${scr.y.toFixed(1)}px)`; H['h-lock'].style.width = H['h-lock'].style.height = sz.toFixed(0) + 'px'; } else H['h-lock'].hidden = true;
  let mi = 0;
  if (ph === 'battle') { const live = aliens.filter((a) => a.alive && a !== S.lock).sort((a, b) => a.pos.distanceToSquared(S.pos) - b.pos.distanceToSquared(S.pos)); for (const a of live) { if (mi >= markers.length - 1) break; placeMarker(markers[mi++], a.pos, a.type === 'mother' ? 'boss' : ''); } }
  if (ph === 'return' || ph === 'landing') placeMarker(markers[mi++], homeV, 'home');
  for (; mi < markers.length; mi++) markers[mi].hidden = true;
  const mom = aliens.find((a) => a.type === 'mother'); if (mom && mom.alive && ph === 'battle') { H['h-mother'].hidden = false; H['h-mbar'].style.width = (mom.hp / mom.maxHp * 100).toFixed(0) + '%'; } else H['h-mother'].hidden = true;
  const ctl = !(ph === 'battle' || ph === 'return' || p