<!-- SOLID CITY · part 118 -->
st = 'fly'; c.fph = 'up'; c.vy = 0; c.gy = A.groundAt(c.x, c.z); c.cruise = 0;
  if (!c.ship) buildShip(c);
  c.hdg = Math.atan2(c.tx - c.x, c.tz - c.z);
}
function collide(c) { // Solidivism: same AABB push-out as the player (Forge builds included)
  var sol = A.solids, rad = 0.4, y = c.y + 1;
  for (var k = 0; k < sol.length; k++) {
    var s = sol[k]; if (y < s.y0 - 0.5 || y > s.y1) continue;
    if (c.x > s.x0 - rad && c.x < s.x1 + rad && c.z > s.z0 - rad && c.z < s.z1 + rad) {
      var dl = c.x - (s.x0 - rad), dr = (s.x1 + rad) - c.x, dn = c.z - (s.z0 - rad), df = (s.z1 + rad) - c.z, m = Math.min(dl, dr, dn, df);
      if (m === dl) c.x = s.x0 - rad; else if (m === dr) c.x = s.x1 + rad; else if (m === dn) c.z = s.z0 - rad; else c.z = s.z1 + rad;
    }
  }
}
function pushCircle(c, x, z, r) { var dx = c.x - x, dz = c.z - z, d = Math.hypot(dx, dz); if (d < r && d > 0.001) { c.x = x + dx / d * r; c.z = z + dz / d * r; } }
function turnTo(c, want, dt, k) { var dA = ((want - c.face + Math.PI * 3) % (Math.PI * 2)) - Math.PI; c.face += dA * Math.min(1, dt * (k || 6)); }
function ground(c) { var gy = A.groundAt(c.x, c.z); if (window.CityForge && CityForge.standY) { var fy = CityForge.standY(c.x, c.z, c.y); if (fy > gy) gy = fy; } return gy; }

function stepCitizen(c, dt, near) {
  var P = S.p, pd = S.mode === 'walk' ? Math.hypot(P.x - c.x, P.z - c.z) : 1e9;
  if (c.st === 'dwell' || c.st === 'walk') {
    // say hello when the player walks up (scripted, local only)
    if (pd < 4.5 && S.t - c.greetT > 45 && !c.talking) { c.greetT = S.t; c.hold = 6; bubble(c, pick(c.hi)); c.idleK = 'wave'; c.idle = 1.6; }
    if (c.hold > 0 || c.talking) { c.hold -= dt; if (pd < 12) turnTo(c, Math.atan2(P.x - c.x, P.z - c.z), dt, 5); c.moving = 0; return; }
  }
  if (c.st === 'dwell') {
    c.moving = 0; c.t -= dt;
    if (c.idle <= 0 && Math.random() < dt * 0.12) { c.idleK = pick(c.dance ? ['dance', 'dance', 'look', 'stretch'] : c.id === 'solids' || c.id === 'solid' ? ['work', 'work', 'look'] : ['look', 'stretch', 'wave', 'look']); c.idle = rnd(2, 4); }
    if (c.t <= 0) nextLeg(c);
  } else if (c.st === 'walk') {
    var tgt = c.path[c.pi], dx = tgt[0] - c.x, dz = tgt[1] - c.z, d = Math.hypot(dx, dz), sp = c.sp;
    if (d < 1.2) { c.pi++; if (c.pi >= c.path.length) { c.st = 'dwell'; c.t = c.sched[c.i][1] * rnd(0.7, 1.2); c.moving = 0; return; } return; }
    c.x += dx / d * sp * dt; c.z += dz / d * sp * dt; turnTo(c, Math.atan2(dx, dz), dt, 6); c.moving = 1; c.walkPh += dt * sp * 2.6;
    if (near) {
      collide(c); pushCircle(c, A.SABLE_POS.x, A.SABLE_POS.z, 1.4);
      if (pd < 1.1) pushCircle(c, P.x, P.z, 1.1);
    }
    // stuck behind a new Forge build? hop in the ship instead
    c.stuckT += dt; if (c.stuckT > 3) { var mv = Math.hypot(c.x - c.lx, c.z - c.lz); c.lx = c.x; c.lz = c.z; c.stuckT = 0; if (mv < sp * 0.8) startFly(c); }
    var gy = ground(c); c.y += (gy - c.y) * Math.min(1, dt * 10);
  } else if (c.st === 'fly') {
    var dx2 = c.tx - c.x, dz2 = c.tz - c.z, dd = Math.hypot(dx2, dz2), g2 = A.groundAt(c.x, c.z);
    var clear = Math.max(g2, A.solidsTop(c.x, c.z, 12)) + 14, cruiseY = Math.max(Y0 + 55, clear + 10), vs = c.id === 'hury' ? 70 : 42;
    // look ahead so we climb before tall towers
    if (dd > 1) { var ax = c.x + dx2 / dd * 60, az = c.z + dz2 / dd * 60; cruiseY = Math.max(cruiseY, A.solidsTop(ax, az, 14) + 18); }
    if (c.fph === 'up') { c.y += Math.min(14 * dt, cruiseY - c.y); if (c.y >= Math.min(cruiseY, Y0 + 30)) c.fph = 'cruise'; }
    else if (c.fph === 'cruise') {
      var s2 = Math.min(vs, dd * 0.8 + 6); c.x += dx2 / Math.max(dd, 0.01) * s2 * dt; c.z += dz2 / Math.max(dd, 0.01) * s2 * dt;
      c.y += (cruiseY - c.y) * Math.min(1, dt * 1.5); c.hdg = Math.atan2(dx2, dz2);
      if (dd < 3) c.fph = 'down';
    } else {
      var gl = ground(c); c.y -= Math.min(10 * dt, c.y - gl);
      if (c.y - gl < 0.05) { c.y = gl; c.st = 'dwell'; c.t = c.sched[c.i][1] * rnd(0.7, 1.2); c.face = c.hdg; if (Math.random() < 0.4) radioEvent(c, true); }
    }
    c.moving = 0;
  }
}
function animate(c, dt) {
  var L = c.limbs, sw = c.moving ? Math.sin(c.walkPh) * 0.55 : 0, k = c.idleK, it = S.t * 3 + c.id.length;
  c.idle -= dt; if (c.idle <= 0) k = c.idleK = '';
  var bob = c.moving ? Math.abs(Math.sin(c.walkPh)) * 0.05 : Math.sin(S.t * 1.6 + c.id.length) * 0.015;
  var armL = sw, armR = -sw, legL = -sw, legR = sw, armZL = 0, armZR = 0, sway = 0;
  if (k === 'wave') { armR = -2.6 + Math.sin(it * 3) * 0.3; }
  else if (k === 'stretch') { armL = armR = -2.9; }
  else if (k === 'work') { armR = -1.2 + Math.sin(it * 4) * 0.9; }
  else if (k === 'dance') { sway = Math.sin(it * 2) * 0.25; armL = -1.4 + Math.sin(it * 2) * 0.6; armR = -1.4 - Math.sin(it * 2) * 0.6; bob = Math.abs(Math.sin(it * 2)) * 0.08; }
  L[0].rotation.x = armL; L[2].rotation.x = armR; L[1].rotation.x = legL; L[3].rotation.x = legR; L[0].rotation.z = armZL; L[2].rotation.z = armZR;
  var look = k === 'look' ? Mat