<!-- SOLID CITY · part 1985 -->
r rx = Math.cos(c.yaw), rz = -Math.sin(c.yaw); // the craft's right hand
  var sides = [[-rx, -rz], [rx, rz]];              // left first — the city's habit
  for (var i = 0; i < 2; i++) {
    var tx = c.pos.x + sides[i][0] * off, tz = c.pos.z + sides[i][1] * off;
    var gy = A.standingAt(tx, tz, c.pos.y);
    if (!isFinite(gy) || gy < c.pos.y - e.clr - 1.2 || gy > c.pos.y + 1.6) continue;
    var blocked = false;
    for (var k = 0; k < A.solids.length; k++) {
      var s = A.solids[k];
      if (gy + 1 < s.y0 - 0.5 || gy + 1 > s.y1) continue;
      if (tx > s.x0 - 0.45 && tx < s.x1 + 0.45 && tz > s.z0 - 0.45 && tz < s.z1 + 0.45) { blocked = true; break; }
    }
    if (blocked) continue;
    S.p.x = tx; S.p.z = tz; S.p.y = gy;
    e.park.x = c.pos.x; e.park.z = c.pos.z; e.park.yaw = c.yaw;
    M.active = null;
    addSolid(e);
    if (M.hopBtn) M.hopBtn.style.display = 'none';
    if (M.thrUp) { M.thrUp.style.display = 'none'; M.thrDn.style.display = 'none'; M.thrDir = 0; }
    A.help('You step off. The ' + e.name.toLowerCase() + ' waits where you left her.', 5);
    return;
  }
  A.help('No room to step off here — ride somewhere clearer.', 5);
}
function active() { return !!M.active; }
function stepDrive(dt) {
  var e = M.active;
  if (!e) return;
  if (e.key !== 'ship' && !e.clock) return;
  var inp = A.input || {}, k = inp.keys || {};
  var sx = inp.sx || 0, sy = inp.sy || 0;
  if (k.KeyW || k.ArrowUp) sy -= 1; if (k.KeyS || k.ArrowDown) sy += 1;
  if (k.KeyA || k.ArrowLeft) sx -= 1; if (k.KeyD || k.ArrowRight) sx += 1;
  sx = Math.max(-1, Math.min(1, sx)); sy = Math.max(-1, Math.min(1, sy));
  var spaceEdge = !!k.Space && !M.spacePrev; M.spacePrev = !!k.Space;
  if (e.key === 'ship') {
    var action = M.jumpQ || spaceEdge; M.jumpQ = false;
    var thrDelta = M.thrDir + (k.KeyR ? 1 : 0) - (k.KeyF ? 1 : 0);
    var res = e.machine.tick(dt, { x: -sx, y: -sy, action: action, thrDelta: thrDelta });
    narrateShip(res.events);
  } else {
    var jump = M.jumpQ || !!k.Space; M.jumpQ = false;
    e.clock(dt, { x: -sx, y: -sy, jump: jump });
  }
  var c = e.craft;
  S.p.x = c.pos.x; S.p.z = c.pos.z; S.p.face = c.yaw;
}
function update(dt) {
  if (M.state === 'idle') { if (A.isBuilt && !A.isBuilt()) return; M.wait -= dt; if (M.wait <= 0) load(); return; }
  if (M.state !== 'ready') return;
  M.t += dt;
  M.crafts.forEach(function (e) {
    if (e.rack && e.rack.userData.parts && e.rack.userData.parts.jewel) e.rack.userData.parts.jewel.rotation.y += dt * 1.4;
    if (e.key === 'ship' && e.mesh && e.mesh.userData.parts && e.mesh.userData.parts.engine) e.mesh.userData.parts.engine.rotation.y += dt * 2.2;
    if (M.active === e) {
      var c = e.craft;
      S.p.y = c.pos.y + e.footLift; // re-asserted after the walk tail's ease: the rider stands ON the craft
      e.mesh.position.set(c.pos.x, c.pos.y - e.meshDrop, c.pos.z);
      if (e.key === 'ship') e.mesh.rotation.set(-(c.pitch || 0), c.yaw, -(c.bank || 0));
      else if (e.key === 'rider') e.mesh.rotation.set(0, c.yaw, -(c.lean || 0));
      else e.mesh.rotation.set(0, c.yaw, c.roll || 0);
      if (M.hopBtn) M.hopBtn.style.display = 'block';
    } else if (e.mesh) {
      var atHome = Math.abs(e.park.x - e.home.x) < 0.01 && Math.abs(e.park.z - e.home.z) < 0.01;
      var py = supportHere(e.park.x, e.park.z) + (atHome ? e.homeLift : e.parkLift);
      if (e.bob) py += Math.sin(M.t * 1.7) * 0.05;
      e.mesh.position.set(e.park.x, py, e.park.z);
      e.mesh.rotation.set(0, e.park.yaw, e.bob ? Math.sin(M.t * 0.9) * 0.03 : 0);
      if (M.hopBtn && !M.active) M.hopBtn.style.display = 'none';
    }
  });
  if (M.hopBtn && M.active) {
    var a = M.active;
    if (a.key === 'ship') {
      var md = a.machine ? a.machine.mode : 'PARKED';
      M.hopBtn.textContent = md === 'PARKED' ? 'LAUNCH \u{1F680}' : md === 'FREE' ? 'ASSIST \u2726' : (md === 'ASSIST' || md === 'APPROACH') ? 'FLY FREE \u2726' : '\u00B7 \u00B7 \u00B7';
    } else M.hopBtn.textContent = 'HOP \u2B06';
  }
  var showThr = !!(M.active && M.active.key === 'ship');
  if (M.thrUp) { M.thrUp.style.display = showThr ? 'block' : 'none'; M.thrDn.style.display = showThr ? 'block' : 'none'; }
  var dx = S.p.x - MID.x, dz = S.p.z - MID.z;
  var bx = S.p.x - SHIP_HOME.x, bz = S.p.z - SHIP_HOME.z;
  var nearFleet = (dx * dx + dz * dz < 26 * 26) || (bx * bx + bz * bz < 26 * 26);
  if (!M.said && nearFleet) {
    M.said = true;
    A.help('🛹🚲\u{1F680} THE FLEET at the spaceport — a hover board, a space rider, and the ship at Berth 1, the future the King declared from the research. Walk up and ride.', 8);
  } else if (dx * dx + dz * dz > 40 * 40 && bx * bx + bz * bz > 40 * 40) M.said = false;
}
function radar(rg, blip, flying) {
  if (M.state !== 'ready') return;
  M.crafts.forEach(function (e) {
    blip(e.park.x, e.park.z, e.blipColor, flying ? 3 : 6, flying ? '' : e.name);
  });
}
window.CityFleet = { update: update, radar: radar, actHere: actHere, act: act, active: active