<!-- SOLID CITY · part 106 -->
eTarget = { x: from.pos.x, z: from.pos.z };
    }
  }
}
function socialPlay(c, name) {
  var nowMs = performance.now();
  if (nowMs - (c.lastSocialAt || -1e9) < 2500) return;
  c.lastSocialAt = nowMs;
  if (c.motion) c.motion.play(name);
}

/* ---------- events from the living city ---------- */
function driveEvents(dt) {
  var D = S.drive;
  if (!D || !D.mesh) { M.prevDrive = null; M.spinAcc = 0; return; }
  var pos = D.mesh.position, cur = { speed: Math.abs(D.speed || 0), x: pos.x, z: pos.z, yaw: D.yaw || 0 };
  var prev = M.prevDrive;
  if (prev) {
    // a crash: speed torn away in a breath
    if (prev.speed - cur.speed >= 9 && prev.speed >= 11) {
      fireEvent({ type: 'vehicleCrash', x: cur.x, z: cur.z, speed: prev.speed, label: A.whereName ? A.whereName(cur.x, cur.z) : 'the road', at: performance.now(), outcome: null, carPos: cur });
    }
    // a wild spin, upright and laughing
    var dy = Math.abs(cur.yaw - prev.yaw); if (dy > Math.PI) dy = 2 * Math.PI - dy;
    if (cur.speed > 8) { M.spinAcc += dy; M.spinT += dt; if (M.spinAcc > 5.5 && M.spinT < 2.5) { fireEvent({ type: 'funnyMoment', x: cur.x, z: cur.z, label: A.whereName ? A.whereName(cur.x, cur.z) : 'the road' }); M.spinAcc = 0; M.spinT = 0; } }
    if (M.spinT > 2.5) { M.spinAcc = 0; M.spinT = 0; }
    // near misses: citizens feel the wind of a fast pass
    if (cur.speed >= 12) for (var i = 0; i < M.citizens.length; i++) {
      var c = M.citizens[i], d = Math.hypot(cur.x - c.pos.x, cur.z - c.pos.z);
      if (d < 5 && !c._nm) { c._nm = true; startReaction(c, { type: 'nearMiss', x: cur.x, z: cur.z, label: 'their post' }); }
      if (d > 12) c._nm = false;
    }
    // resolutions for open crash sessions
    for (var j = 0; j < M.citizens.length; j++) {
      var cz = M.citizens[j], ss = cz.session;
      if (ss && ss.ev.type === 'vehicleCrash' && !ss.outcome) {
        var age = performance.now() - ss.t0, moved = Math.hypot(cur.x - ss.ev.x, cur.z - ss.ev.z);
        if (moved > 22 || age > 12000) ss.outcome = 'up';
        else if (age > 9000 && moved < 4 && cur.speed < 1) ss.outcome = 'trapped';
      }
    }
  }
  M.prevDrive = cur;
  if (!D) for (var k = 0; k < M.citizens.length; k++) { var s2 = M.citizens[k].session; if (s2 && s2.ev.type === 'vehicleCrash' && !s2.outcome) s2.outcome = 'up'; }
}
function fireEvent(ev) { for (var i = 0; i < M.citizens.length; i++) startReaction(M.citizens[i], ev); }

/* ---------- fall awareness: a tumble is not a tragedy, until it is ---------- */
var FALL = { prevY: null, vy: 0, peakY: 0, air: false, worstVy: 0 };
function fallEvents(dt) {
  var P = S.p; if (!P || S.mode !== 'walk' || S.drive) { FALL.prevY = null; return; }
  var gy = surfY(P.x, P.z);
  if (FALL.prevY == null) { FALL.prevY = P.y; return; }
  var vy = (P.y - FALL.prevY) / Math.max(dt, 0.001); FALL.prevY = P.y;
  FALL.vy = vy;
  if (P.y > gy + 2) { FALL.air = true; FALL.peakY = Math.max(FALL.peakY, P.y); FALL.worstVy = Math.min(FALL.worstVy, vy); }
  else if (FALL.air && P.y <= gy + 0.35) {
    FALL.air = false;
    var impact = Math.abs(FALL.worstVy); FALL.worstVy = 0; FALL.peakY = 0;
    var label = A.whereName ? A.whereName(P.x, P.z) : 'nearby';
    if (impact >= 13) fireEvent({ type: 'fall', x: P.x, z: P.z, speed: impact, label: label });
    else if (impact >= 8) fireEvent({ type: 'nearMiss', x: P.x, z: P.z, label: label });
    else if (impact >= 4) fireEvent({ type: 'funnyMoment', x: P.x, z: P.z, label: label });
  }
  // resolutions for open fall sessions: rising is moving again
  for (var i = 0; i < M.citizens.length; i++) {
    var ss = M.citizens[i].session;
    if (ss && ss.ev.type === 'fall' && !ss.outcome) {
      var age = performance.now() - ss.t0, moved = Math.hypot(P.x - ss.ev.x, P.z - ss.ev.z);
      if (moved > 4 || age > 12000) ss.outcome = 'up';
      else if (age > 9000 && moved < 1.5) ss.outcome = 'trapped';
    }
  }
}

/* ---------- the talk panel ---------- */
var panelEl = null, logEl = null, inputEl = null, titleEl = null, opsEl = null, pillEl = null;
function panelCitizen() { return M.panelFor ? M.byId[M.panelFor] : null; }
function buildPanel() {
  pillEl = document.createElement('button');
  pillEl.style.cssText = 'position:fixed;right:14px;bottom:calc(120px + env(safe-area-inset-bottom));z-index:60;display:none;padding:12px 18px;border-radius:999px;border:1px solid #d8a833;background:rgba(16,22,34,.92);color:#e8edf5;font:600 15px system-ui;box-shadow:0 4px 18px rgba(0,0,0,.5)';
  document.body.appendChild(pillEl);
  pillEl.addEventListener('click', function () { if (M.near) openPanel(M.near); });

  panelEl = document.createElement('div');
  panelEl.style.cssText = 'position:fixed;left:50%;transform:translateX(-50%);bottom:calc(12px + env(safe-area-inset-bottom));width:min(560px,94vw);z-index:61;display:none;background:rgba(13,18,28,.96);border:1px solid #d8a833;border-radius:16px;color:#e8edf5;font:14px/1.45 system-ui;padding:12px 14px;box-shadow:0 10px 40px rgba(0,0,0