<!-- SOLID CITY · part 1595 -->
ition.set(S.ship.x - origin.x, S.ship.y, S.ship.z - origin.z); ship.quaternion.copy(S.ship.q);
  var fl = S.mode === 'fly' || S.mode === 'auto' || S.mode === 'launch' || S.mode === 'watch' ? 0.6 + Math.min(S.ship.speed, 3000) / 1200 + (S.ship.boost ? 0.8 : 0) : S.mode === 'countdown' ? null : 0.01;
  if (fl !== null) shipFlames.forEach(function (f) { f.scale.set(1, Math.min(fl, 2.2) * (0.9 + Math.random() * 0.2), 1); });
  avatar.position.set(S.p.x - origin.x, S.p.y, S.p.z - origin.z);
  sable.position.set(SABLE_POS.x - origin.x, SABLE_POS.y + 0.1 + Math.sin(S.t * 1.3) * 0.06, SABLE_POS.z - origin.z);
  if (S.mode === 'walk') sable.rotation.y = Math.atan2(S.p.x - SABLE_POS.x, S.p.z - SABLE_POS.z);
  sable.userData.halo.rotation.z = S.t;
  for (var i = 0; i < animated.length; i++) animated[i](S.t);
  placeCamera(dt);
  updateTerrain({ x: camAbs.x, y: camAbs.y, z: camAbs.z }, MOBILE ? 4 : 6);
  updateHUD(dt);
  renderer.render(scene, camera);
  if (PERF) { renderer.getContext().finish(); workTimes.push(performance.now() - now0); if (workTimes.length > 600) workTimes.shift(); }
}

/* ---------------- boot ---------------- */
var cityBuilt = false;
function boot() {
  $('sc-load').textContent = 'Raising the capital…';
  setTimeout(function () {
    buildCity(); cityBuilt = true; try { dispatchEvent(new Event('sc-city-built')); } catch (e) {}
    $('sc-load').textContent = 'Generating the island chain…';
    setTimeout(function () {
      camAbs.set(0, 30, 60);
      for (var n = 0; n < 6; n++) updateTerrain({ x: 0, y: 30, z: 60 }, 120);
      buildMap();
      if (window.CityPlayer) { CityPlayer.mount($('sc-hud')); CityPlayer.onChange(function (s) { NP.playing = s.playing; if (NP.draw && (s.title !== NP.t || s.playing !== NP.p)) { NP.t = s.title; NP.p = s.playing; NP.draw(s.title, s.kind, s.playing); } }); setTimeout(function () { CityPlayer.preload(); }, 200); }
      $('sc-load').textContent = 'Ready.'; $('sc-enter').disabled = false;
      if (QS.get('back') === 'workshop') { S.p.x = D.workshop.x; S.p.z = D.workshop.z + 62; S.p.face = Math.atan2(-D.workshop.x, -D.workshop.z); } else if (QS.get('back') === 'strike') { S.p.x = D.strike.x; S.p.z = D.strike.z + 58; S.p.face = Math.atan2(-D.strike.x, -D.strike.z); }
      if (QS.has('auto')) enter();
    }, 30);
  }, 30);
}
function enter() {
  if (window.CityPlayer) CityPlayer.gesture();   // the ENTER tap unlocks audio on iOS
  if (QS.get('fs') === '1') { var fe = document.documentElement, rf = fe.requestFullscreen || fe.webkitRequestFullscreen; if (rf) try { var fp = rf.call(fe); if (fp && fp.catch) fp.catch(function () {}); } catch (e) {} }   // from /press-start
  $('sc-boot').hidden = true; $('sc-hud').hidden = false; if (window.AudioDrive) AudioDrive.attach(); running = true; last = performance.now(); requestAnimationFrame(frame);
  help(IS_TOUCH ? 'Left stick to walk, drag to look. Tap SABLE to talk. The spaceport is to the south-west.' : 'WASD to walk, drag to look, E to interact. Tap SABLE to talk. The spaceport is south-west — board a ship and fly.', 8);
}
$('sc-enter').disabled = true; $('sc-enter').addEventListener('click', enter);
S.camYaw = 0;
boot();

/* test / screenshot hooks (read-only debugging; no server effect) */
window.SC = {
  state: S, origin: origin,
  stats: function () { var ft = frameTimes.slice().sort(function (a, b) { return a - b; }); var avg = ft.reduce(function (a, b) { return a + b; }, 0) / (ft.length || 1); var wt = workTimes.slice().sort(function (a, b) { return a - b; }); return { workAvgMs: wt.reduce(function (a, b) { return a + b; }, 0) / (wt.length || 1), workP95Ms: wt[Math.floor(wt.length * 0.95)] || 0, fps: fpsVal, avgMs: avg, p95Ms: ft[Math.floor(ft.length * 0.95)] || 0, calls: renderer.info.render.calls, tris: renderer.info.render.triangles, terrain: terrainStats(), solids: solids.length, mem: renderer.info.memory }; },
  resetFrames: function () { frameTimes.length = 0; workTimes.length = 0; },
  fly: function (x, y, z, heading, pitch) { S.mode = 'fly'; avatar.visible = false; S.ship.x = x; S.ship.y = y; S.ship.z = z; S.ship.q.setFromAxisAngle(UP, heading || 0); rotLocal(AX, -(pitch || 0)); S.ship.speed = 300; S.ship.throttle = 0.6; setFlightUI(true); shipCam = null; },
  walk: function (x, z, yaw) { S.mode = 'walk'; avatar.visible = true; S.p.x = x; S.p.z = z; S.p.y = groundAt(x, z); if (yaw != null) S.camYaw = yaw; setFlightUI(false); },
  board: board, launch: startCountdown, talk: openTalk, soul: openSoul, rtc: returnToCity, heightAt: heightAt
};
window.SC.api = {
  T: T, S: S, D: D, PAD: PAD, SABLE_POS: SABLE_POS, VOLCANOES: VOLCANOES, ISLANDS: ISLANDS, CITY_Y: CITY_Y, IS_TOUCH: IS_TOUCH, MOBILE: MOBILE,
  world: world, scene: scene, camera: camera, renderer: renderer, origin: origin, curve: curve, labelTex: labelTex, groundAt: groundAt, heightAt: heightAt, whereName: whereName,
  solids: solids, portals: portals, RADIO_MASTS: RADIO_MASTS, input: input, help: help, openPortal: op