<!-- SOLID CITY · part 994 -->
adar(rg, blip, flying, sc, ax, az); } catch (e) {}
  if (window.CityWatch) try { CityWatch.radar(rg, blip, flying, sc, ax, az); } catch (e) {}
  if (window.CityCitizens) try { CityCitizens.radar(rg, blip, flying, sc, ax, az); } catch (e) {}
  if (window.CityMonuments) try { CityMonuments.radar(rg, blip, flying, sc, ax, az); } catch (e) {}
  if (window.CitySanctuary) try { CitySanctuary.radar(rg, blip, flying, sc, ax, az); } catch (e) {}
  if (window.CityCampus) try { CityCampus.radar(rg, blip, flying, sc, ax, az); } catch (e) {}
  if (window.CityBook) try { CityBook.radar(rg, blip, flying, sc, ax, az); } catch (e) {}
  if (window.CityPodium) try { CityPodium.radar(rg, blip, flying, sc, ax, az); } catch (e) {}
  // you
  rg.translate(W / 2, H / 2); rg.rotate(-heading); rg.fillStyle = '#fff'; rg.beginPath(); rg.moveTo(0, -11); rg.lineTo(7, 8); rg.lineTo(0, 4); rg.lineTo(-7, 8); rg.closePath(); rg.fill();
  rg.restore();
  rg.strokeStyle = 'rgba(232,195,106,.5)'; rg.lineWidth = 2; rg.beginPath(); rg.arc(W / 2, H / 2, W / 4, 0, Math.PI * 2); rg.stroke();
  rg.fillStyle = '#e8c36a'; rg.font = 'bold 16px system-ui'; rg.fillText('N', W / 2 - 6, 20);
  rg.font = '13px system-ui'; rg.fillText((rad / 1000).toFixed(rad < 2000 ? 1 : 0) + ' km', W / 2 - 18, H - 10);
}
var hudT = 0;
function whereName(x, z) {
  var best = null, bd = 1e9; [D.plaza, D.studio, D.port, D.soul].forEach(function (d) { var dd = Math.hypot(x - d.x, z - d.z); if (dd < bd) { bd = dd; best = d; } });
  if (bd < 160) return best.name; if (window.CampusMap) { var cn = CampusMap.nameAt(x, z); if (cn) return cn; } if (Math.hypot(x, z) < 820) return 'SOLID CITY';
  for (var k = 0; k < VOLCANOES.length; k++) { var V = VOLCANOES[k]; if (V.name && Math.hypot(x - V.x, z - V.z) < V.r * 0.45) return V.name; }
  return landMask(x, z) > 0 ? 'THE ISLANDS' : 'OPEN OCEAN';
}
function updateHUD(dt) {
  hudT -= dt; if (hudT > 0) return; hudT = 0.12;
  var flying = S.mode !== 'walk', x = flying ? S.ship.x : S.p.x, z = flying ? S.ship.z : S.p.z, y = flying ? S.ship.y : S.p.y;
  var spd = flying ? S.ship.speed : (Math.abs(input.sx) + Math.abs(input.sy) > 0.08 || input.keys.KeyW || input.keys.KeyS || input.keys.KeyA || input.keys.KeyD ? 8 : 0);
  var alt = flying ? y - Math.max(0, groundAt(x, z)) : y;
  $('h-mode').textContent = { walk: 'ON FOOT', boarded: 'SHIP · ON PAD', countdown: 'COUNTDOWN', launch: 'LAUNCH', fly: y > 25000 ? 'ORBIT' : y > 12000 ? 'STRATOSPHERE' : 'FLIGHT', auto: 'AUTOPILOT', watch: 'WATCH' }[S.mode];
  $('h-where').textContent = y > 25000 ? 'ABOVE THE ATMOSPHERE' : whereName(x, z);
  $('h-spd').textContent = Math.round(spd * 3.6).toLocaleString();
  $('h-alt').textContent = Math.round(flying ? y : y).toLocaleString();
  $('h-dist').textContent = (Math.hypot(x, z) / 1000).toFixed(1);
  $('sc-thr-fill').style.height = Math.round(S.ship.throttle * 100) + '%';
  var w = $('h-warn'); if (warnMsg && (S.mode === 'fly')) { w.hidden = false; w.textContent = warnMsg; } else w.hidden = true;
  var heading = flying ? Math.atan2(-new V3(0, 0, 1).applyQuaternion(S.ship.q).x, -new V3(0, 0, 1).applyQuaternion(S.ship.q).z) : Math.atan2(-Math.sin(S.camYaw), -Math.cos(S.camYaw));
  drawRadar(x, z, -heading + Math.PI);
  // action buttons
  var act = $('sc-act');
  if (S.mode === 'walk' && actTarget) { act.hidden = false; act.textContent = actTarget.label; }
  else if (S.mode === 'boarded') { act.hidden = false; act.textContent = 'EXIT SHIP'; actTarget = { kind: 'exit' }; }
  else act.hidden = true;
  $('sc-launch').hidden = S.mode !== 'boarded';
  $('sc-stick').style.display = (S.mode === 'walk' || S.mode === 'fly' || S.mode === 'auto' || S.mode === 'watch') && (IS_TOUCH || QS.has('touch')) ? '' : 'none';
  $('b-land').hidden = !((S.mode === 'fly') && Math.hypot(x - PAD.x, z - PAD.z) < 2500 && y < 3000);
  $('b-rtc').classList.toggle('is-on', S.mode === 'auto' && !(S.auto && S.auto.tx != null));
  if (window.CityPlayer) {
    CityPlayer.setCockpit(S.mode !== 'walk');
    var bd = 1e9; for (var mi = 0; mi < RADIO_MASTS.length; mi++) bd = Math.min(bd, Math.hypot(x - RADIO_MASTS[mi].x, z - RADIO_MASTS[mi].z, Math.max(0, y - CITY_Y - 90)));
    CityPlayer.setBoost(1 + 0.3 * smooth((260 - bd) / 200));
  }
  if (helpTimer > 0) { helpTimer -= 0.12; if (helpTimer <= 0) $('sc-help').style.opacity = 0; }
  if (PERF) { var ts = terrainStats(); $('sc-perf').hidden = false; $('sc-perf').textContent = fpsVal.toFixed(0) + ' fps  ' + renderer.info.render.calls + ' calls\n' + Math.round(renderer.info.render.triangles / 1000) + 'k tris  ' + ts.chunks + ' chunks  q' + ts.queue; }
}

/* ---------------- SABLE talk (login-gated, rate-limited server-side) ---------------- */
var me = null, talkHist = [], talkBusy = false;
var CITY_SEED = { role: 'sable', text: 'Welcome to Solid City. I stand in the Forum Plaza, the heart of the capital, on the west coast of the main volcanic island. The Music Studio Tower rises to the east, and its gate opens Solid Sound 