<!-- SOLID CITY · part 860 -->
 R / F). Tap WALK to land.', 6); }
    else if (!on && S.rfly) { S.rflyLand = true; }
    return true;
  },
  flying: function () { return !!S.rfly; },
  setRise: function (v) { S.rflyRise = v; },
  onLand: null
};
/* FIRST DRIVES (2026-10-13) — the South Lot rides. The avatar is the real
   human of this world; the rides were built for him to roll in. CityDrive
   owns the drive state and updateWalk integrates it (driveStep), exactly
   the way royal flight is integrated: one sanctioned hook in the physics.
   Any visitor may roll a lot ride, as any visitor may ride the train. */
window.CityDrive = {
  active: function () { return !!S.drive; },
  state: function () { return S.drive || null; },
  begin: function (mesh, yaw, ox, oz, carId) {
    if (S.mode !== 'walk' || S.rfly || S.drive) return false;
    S.drive = { mesh: mesh, yaw: yaw || 0, speed: 0, _thr: 0, _str: 0, ox: ox || 0, oz: oz || 0, carId: carId || null };
    if (avatar) avatar.visible = false;
    help('You roll. Push the stick up to drive, sideways to steer. The action button steps you out.', 7);
    return true;
  },
  end: function () {
    var D = S.drive; if (!D) return false;
    var P = S.p;
    if (D.mesh) { D.mesh.position.set(P.x - D.ox, P.y, P.z - D.oz); D.mesh.rotation.y = D.yaw; }
    var rx = Math.cos(D.yaw), rz = -Math.sin(D.yaw);
    P.x -= rx * 2.6; P.z -= rz * 2.6;
    P.face = D.yaw;
    S.drive = null;
    if (avatar) avatar.visible = true;
    help('You step out. The ride stays where you left it.', 5);
    return true;
  }
};
function driveStep(dt) {
  var D = S.drive, P = S.p; if (!D) return;
  var thr = D._thr || 0, str = D._str || 0;
  var target = thr * (thr >= 0 ? 26 : 9);
  var rate = Math.abs(target) > Math.abs(D.speed) ? 1.9 : 3.4;
  D.speed += (target - D.speed) * Math.min(1, dt * rate);
  if (thr === 0 && Math.abs(D.speed) < 0.03) D.speed = 0;
  var sf = Math.min(1, Math.abs(D.speed) / 7);
  D.yaw -= str * sf * dt * 1.75 * (D.speed < 0 ? -1 : 1);
  P.x += Math.sin(D.yaw) * D.speed * dt;
  P.z += Math.cos(D.yaw) * D.speed * dt;
  P.face = D.yaw;
  if (D.mesh) { D.mesh.position.set(P.x - D.ox, P.y, P.z - D.oz); D.mesh.rotation.y = D.yaw; }
}

function returnToCity() { if (S.mode === 'fly' || S.mode === 'auto') startAuto(false); }
function startAuto(landNow) { S.mode = 'auto'; S.auto = { phase: 'cruise', land: true }; help('Autopilot: returning to the Orbital Spaceport. Move the stick to take back control.'); }
var helpTimer = 0;
function help(t, sec) { var h = $('sc-help'); h.textContent = t; h.style.opacity = 1; helpTimer = sec || 6; }

/* ---------------- walking ---------------- */
function updateWalk(dt) {
  var k = input.keys, mx = input.sx, my = input.sy;
  if (k.KeyW || k.ArrowUp) my -= 1; if (k.KeyS || k.ArrowDown) my += 1; if (k.KeyA || k.ArrowLeft) mx -= 1; if (k.KeyD || k.ArrowRight) mx += 1;
  var mag = Math.min(1, Math.hypot(mx, my)), P = S.p;
  var DRV = S.drive || null;
  if (DRV) { DRV._thr = clamp(-my, -1, 1); DRV._str = clamp(mx, -1, 1); mx = 0; my = 0; mag = 0; }
  if (mag > 0.08) {
    var sp = (k.ShiftLeft || k.ShiftRight || mag > 0.95 && IS_TOUCH ? 15 : 8) * mag;
    if (S.rfly) sp *= 2.2; // royal flight: the superhero covers ground
    var fx = -Math.sin(S.camYaw), fz = -Math.cos(S.camYaw), rx = Math.cos(S.camYaw), rz = -Math.sin(S.camYaw);
    var vx = (fx * -my + rx * mx) / (Math.hypot(mx, my) || 1), vz = (fz * -my + rz * mx) / (Math.hypot(mx, my) || 1);
    P.x += vx * sp * dt; P.z += vz * sp * dt;
    var want = Math.atan2(vx, vz), dA = ((want - P.face + Math.PI * 3) % (Math.PI * 2)) - Math.PI; P.face += dA * Math.min(1, dt * 10);
    P.walk = (P.walk || 0) + dt * sp * 1.4;
  }
  if (S.ai && !DRV) { // Hyper Controller AI walk: user input always wins
    if (mag > 0.08) S.ai(null);
    else { var st = S.ai(dt); if (st) { P.x += st.vx * dt; P.z += st.vz * dt; var wa = Math.atan2(st.vx, st.vz), da = ((wa - P.face + Math.PI * 3) % (Math.PI * 2)) - Math.PI; P.face += da * Math.min(1, dt * 8); P.walk = (P.walk || 0) + dt * st.sp * 1.1; mag = 1; } }
  }
  if (DRV) driveStep(dt);
  // everything visible is solid
  collideXZ(P, DRV ? 1.15 : 0.45, P.y + 1);
  var sx = SABLE_POS.x - P.x, sz = SABLE_POS.z - P.z, sd = Math.hypot(sx, sz); if (sd < 1.3 && sd > 0) { P.x = SABLE_POS.x - sx / sd * 1.3; P.z = SABLE_POS.z - sz / sd * 1.3; }
  var RF = !!S.rfly;
  if (RF) {
    var rise = S.rflyRise || 0;
    if (k.KeyR || k.Space) rise = 1; if (k.KeyF) rise = -1;
    S.rflyT = (S.rflyT || 0) + dt;
    if (S.rflyLand) rise = -1; else if (rise === 0 && S.rflyT < 1.1) rise = 1; // the lift-off
    S.rflyV = (S.rflyV || 0) + (rise * 14 - (S.rflyV || 0)) * Math.min(1, dt * 6);
    P.y += S.rflyV * dt;
    if (P.y > 420) P.y = 420;
  }
  var gy = groundAt(P.x, P.z); if (gy < 0.5) { gy = 0.5; } // wading at the shore, no deeper
  if (window.CityForge) { var fy = CityForge.standY(P.x, P.z, P.y); if (fy > gy) gy = fy; }   // stand on Forge pieces (steps, slabs)
  if (window.CityAn