<!-- SOLID CITY · part 98 -->
     if (!r.ok) addLine('system', (r.why || r.error) === 'stale-epoch' ? 'Your operator epoch went stale — someone relieved you.' : 'Reply refused: ' + (r.why || r.error || ''), 'sys');
      else { if (M.voiceMine) speak(c, r.text, Math.max(4, r.text.length / 14)); if (r.gesture && c.motion) c.motion.play(r.gesture); refreshOps(); }
    });
    return;
  }
  if (c.op && c.op.type !== 'ai') { addLine('system', c.op.type === 'human' ? c.name + ' is inhabited — your words wait in their inbox.' : c.name + ' is resting — your words wait in their inbox.', 'sys'); }
  api('/api/citizens/' + c.id + '/say', { method: 'POST', body: JSON.stringify({ text: text }) }).then(function (r) {
    if (r.ok && r.pending) { addLine('system', 'Delivered to ' + c.name + '’s inbox.', 'sys'); if (c.op && c.op.type === 'human' && M.me && c.op.name === M.me) refreshOps(); return; }
    if (!r.ok) {
      // THE GRACEFUL FLOOR: the voice rides the engine, and the
      // engine can fail — unpaid, throttled, gone. The BODY is the
      // game's own and answers anyway: they heard you; watch.
      if (c.motion) { c.faceTarget = { x: S.p.x, z: S.p.z }; c.motion.play('wave'); }
      addLine('system', c.name + '’s voice is carried by the engine, and the engine is silent just now — but they heard you. The body still answers.', 'sys');
      return;
    }
    speak(c, r.text, Math.max(4, r.text.length / 14));
    if (r.gesture && c.motion) c.motion.play(r.gesture);
  });
}

/* ---------- POSSESSION: see the city standing where they stand ----------
 * The operator IS the citizen's mind; possession lends the body.
 * Your walker steps into their post, your own figure hides, and
 * the citizen's figure follows your feet and your facing. Release
 * restores your avatar and your place — nothing of theirs is kept,
 * nothing of yours is lost. */
function possess(c) {
  if (M.possess) releasePossess(true);
  M.possess = { c: c, back: { x: S.p.x, z: S.p.z, yaw: S.camYaw } };
  c.possessed = true; c.session = null; c.moveTarget = null; c.pendingLines = [];
  if (window.SC.walk) window.SC.walk(c.pos.x, c.pos.z, c.face);
  if (A.avatar) A.avatar.visible = false;
  addLine('system', 'You stand inside ' + c.name + ' now. Walk, and you walk as them; speak from this panel, and the words are theirs.', 'sys');
  refreshOps();
}
function releasePossess(restore) {
  var pz = M.possess; if (!pz) return;
  M.possess = null;
  var c = pz.c; c.possessed = false;
  c.moveTarget = { x: c.rec.post.x, z: c.rec.post.z, stop: 0 };
  if (A.avatar) A.avatar.visible = true;
  if (restore !== false && window.SC.walk) window.SC.walk(pz.back.x, pz.back.z, pz.back.yaw);
  if (panelCitizen()) refreshOps();
}
function tickPossess(dt) {
  var pz = M.possess; if (!pz) return;
  var c = pz.c;
  if (!(c.op && c.op.type === 'human' && M.me && c.op.name === M.me)) { releasePossess(true); return; }
  var P = S.p, dx = P.x - c.pos.x, dz = P.z - c.pos.z;
  c.pos.x = P.x; c.pos.z = P.z;
  if (Math.hypot(dx, dz) > 0.02) c.face = Math.atan2(dx, dz);
}

/* ---------- LIVING MOMENTS CAPTURE: the player's own replay buffer ----------
 * Opt-in, off by default, clearly lit while armed, and the file
 * goes to the player's own device — nothing is uploaded, ever.
 * Only the player's own view is recorded; no one else's screen
 * exists in it. */
function buildCapture() {
  var wrap = document.createElement('div');
  wrap.style.cssText = 'position:fixed;right:14px;top:110px;z-index:60;display:flex;flex-direction:column;gap:6px;align-items:flex-end';
  var b1 = document.createElement('button'); b1.className = 'cz-b'; b1.textContent = '🎥 Moments: off';
  var b2 = document.createElement('button'); b2.className = 'cz-b'; b2.textContent = '📸 Save last 30s'; b2.style.display = 'none';
  wrap.appendChild(b1); wrap.appendChild(b2); document.body.appendChild(wrap);
  var CAP = M.capture = { armed: false, rec: null, chunks: [] };
  if (!(window.MediaRecorder && A.renderer && A.renderer.domElement && A.renderer.domElement.captureStream)) { b1.textContent = '🎥 Moments: unsupported here'; b1.disabled = true; return; }
  b1.addEventListener('click', function () {
    if (!CAP.armed) {
      try {
        var stream = A.renderer.domElement.captureStream(30);
        CAP.chunks = [];
        CAP.rec = new MediaRecorder(stream, { videoBitsPerSecond: 2500000 });
        CAP.rec.ondataavailable = function (e) { if (e.data && e.data.size) { CAP.chunks.push({ t: Date.now(), blob: e.data }); while (CAP.chunks.length > 9) CAP.chunks.shift(); } };
        CAP.rec.start(5000); CAP.armed = true;
        b1.textContent = '🔴 Moments: recording your view'; b2.style.display = '';
      } catch (e) { b1.textContent = '🎥 Moments: could not start'; }
    } else {
      try { CAP.rec.stop(); } catch (e) {}
      CAP.armed = false; CAP.rec = null; CAP.chunks = [];
      b1.textContent = '🎥 Moments: off'; b2.style.display = 'none';
    }
  });
  b2.addEventListener('click', function () {
 