<!-- SOLID CITY · part 3416 -->

    if (alive) S.waveT = 2.2; else { S.waveT -= dt; if (S.waveT <= 0) { if (S.wave < 3) spawnWave(S.wave + 1); else allDown(); } }
  }
  S.topSpeed = Math.max(S.topSpeed, S.speed);
  eul.set(S.pitch, S.yaw, S.roll); shipQ.setFromEuler(eul); ship.quaternion.copy(shipQ); ship.position.copy(S.pos);
  updateShipFx(dt); updateCamera(dt); updateEnv(dt); updateFx(dt); updateHud(dt);
  if (ph === 'parked' || ph === 'result' || ph === 'pad') auEngine(ph === 'pad' ? 0.25 : 0.12, 0.05, 0);
  else if (ph === 'count') auEngine(0.4 + S.phaseT * 0.2, 0.15 + S.phaseT * 0.1, S.phaseT * 0.15);
  else auEngine(ph === 'respawn' ? 0 : 0.85, clamp(S.speed / 520, 0, 1) + (S.boosting ? 0.25 : 0), (S.boosting ? 0.35 : 0) + S.heat * 1.2 + (ph === 'climb' && S.phaseT < 6 ? 0.6 : 0));
}

function updateClimb(dt) {
  const t = S.phaseT;
  if (t < 1.6) {   // vertical lift-off on the engines, smoke on the pad
    S.vUp = Math.min(S.vUp + dt * 26, 34); S.pos.y += S.vUp * dt; S.speed = S.vUp;
    for (let k = 0; k < 3; k++) { tv.set((Math.random() - 0.5) * 60, Math.random() * 5, (Math.random() - 0.5) * 60); tv2.set(S.pos.x + (Math.random() - 0.5) * 4, 0.6, S.pos.z + (Math.random() - 0.5) * 4); emit(sparks, tv2, tv, COL.smoke, 2.2, 9, 0.55); }
    S.shake = Math.max(S.shake, 0.6); return;
  }
  S.pitch = damp(S.pitch, clamp(1.12 + input.y * 0.3, 0.55, 1.4), 1.6, dt);
  S.yaw -= input.x * 0.9 * dt; S.roll = damp(S.roll, -input.x * 0.6, 4, dt);
  const target = CLIMB_V + (S.boosting ? 180 : 0);
  S.speed = Math.min(target, S.speed + dt * (S.boosting ? 120 : 75));
  S.vUp = damp(S.vUp, 0, 1.2, dt);
  forward(tv); S.pos.addScaledVector(tv, S.speed * dt); S.pos.y += S.vUp * dt;
  S.shake = Math.max(S.shake, 0.25 + (S.boosting ? 0.25 : 0));
  if (altOf(S.pos.y) > SPACE_ALT) enterSpace();
}

function updateFlight(dt) {
  const ph = S.phase, alt = altOf(S.pos.y);
  S.yaw -= input.x * 1.45 * dt; S.pitch = clamp(S.pitch + input.y * 1.25 * dt, -1.35, 1.35);
  S.roll = damp(S.roll, -input.x * 0.85, 5, dt);
  if (IS_TOUCH && S.lock && input.firing) {   // touch aim assist: the nose drifts toward the locked alien while firing
    tv.copy(S.lock.pos).sub(S.pos); const dy = Math.atan2(-tv.x, -tv.z), dp = Math.asin(clamp(tv.y / tv.length(), -1, 1));
    S.yaw += clamp(wrapA(dy - S.yaw), -1, 1) * 0.9 * dt; S.pitch += clamp(dp - S.pitch, -1, 1) * 0.9 * dt;
  }
  if (ph === 'battle') {   // stay in space: soft floor / ceiling; level off on arrival
    if (alt < 6200 && S.pitch < 0.4) S.pitch += (6200 - alt) / 1500 * 0.9 * dt;
    if (alt > 16000 && S.pitch > -0.4) S.pitch -= (alt - 16000) / 1500 * 0.9 * dt;
    if (S.phaseT < 2.5) S.pitch = damp(S.pitch, 0, 1.4, dt);
  }
  let target = S.boosting ? BOOST_V : BASE_V;
  if (ph === 'return') { const dive = Math.max(0, -Math.sin(S.pitch)); if (alt > 1500) target += dive * (REENTRY_V - BASE_V) * smooth((alt - 1500) / 1500); }
  if (ph === 'battle' && S.phaseT < 3) target = Math.max(target, lerp(CLIMB_V, BASE_V, S.phaseT / 3));
  if (S.slowT > 0) target *= 0.62;
  const acc = target > S.speed ? (S.boosting ? 160 : 90) : 110;
  S.speed = S.speed < target ? Math.min(target, S.speed + acc * dt) : Math.max(target, S.speed - acc * dt);
  forward(tv); S.pos.addScaledVector(tv, S.speed * dt);
  const vy = tv.y * S.speed;
  const gy = groundY(S.pos.x, S.pos.z), clear = S.pos.y - gy;   // no crashes: pull up over land and sea
  if (clear < 60 && S.pitch < 0.2) S.pitch = Math.min(0.2, S.pitch + (60 - clear) / 60 * 2.5 * dt);
  if (clear < 6) S.pos.y = gy + 6;
  let heat = 0; if (ph === 'return' && alt < 9000 && alt > 1200 && vy < -60) heat = clamp((-vy - 60) / 260, 0, 1) * smooth((alt - 1200) / 900) * smooth((9000 - alt) / 800);
  S.heat = damp(S.heat, heat, 3, dt); if (S.heat > 0.2) S.shake = Math.max(S.shake, S.heat * 0.7);
  if (S.heat > 0.15) for (let k = 0; k < (MOBILE ? 2 : 4); k++) { forward(tv2); tv.copy(tv2).multiplyScalar(-(40 + Math.random() * 60)).add(tv3.set((Math.random() - 0.5) * 30, (Math.random() - 0.5) * 30, (Math.random() - 0.5) * 30)); tv2.multiplyScalar(3).add(S.pos); emit(sparks, tv2, tv, Math.random() < 0.5 ? COL.fire : COL.gold, 0.5 + Math.random() * 0.4, 3.5 * S.heat + 1); }
  if (ph === 'return' && Math.hypot(S.pos.x, S.pos.z) < 750 && alt < 560) startLanding();
  if (S.boosting) S.shake = Math.max(S.shake, 0.22);
}

function updateLanding(dt) {
  const L = S.landing, P = S.pos, hd = Math.hypot(P.x, P.z), toPad = tv3.set(-P.x, 0, -P.z).normalize();
  const steering = Math.abs(input.x) > 0.12 || Math.abs(input.y) > 0.12;
  if (steering) { S.yaw -= input.x * 1.3 * dt; forward(tv2, S.yaw, 0).multiplyScalar(input.y * HOVER_V); }
  else { tv2.copy(toPad).multiplyScalar(Math.min(HOVER_V, hd * 0.55)); if (hd > 12) S.yaw += clamp(wrapA(Math.atan2(P.x, P.z) - S.yaw), -1, 1) * 1.6 * dt; }
  if (hd < 40) tv2.addScaledVector(toPad, (40 - hd) * 0.35);   // magnet onto the circle
  L.hv.x = damp(L.hv.x, tv2.x, 1.25, dt); L.hv.z = damp(L.hv.z, tv2.z, 1.25, dt);
  const gy = ground