<!-- SOLID CITY · part 3754 -->
assList.contains('watching') || Math.abs(e.clientX - x0) + Math.abs(e.clientY - y0) > 10) return; root.classList.toggle('watch-full'); WS.full = root.classList.contains('watch-full') ? 6 : 0; });
})();
addEventListener('keydown', function (e) {
  var tg = e.target && e.target.tagName; if (tg === 'INPUT' || tg === 'TEXTAREA') return;
  if (e.code === 'KeyC' && (WS.state === 'route' || WS.state === 'landing')) nextRig(true);
  if (e.code === 'KeyV' && WS.state === 'paused') doResume();
  if (e.code === 'Escape' && !pick.hidden) pick.hidden = true;
});

window.CityWatch = {
  open: open, start: start, fly: fly, cam: cam, update: update, radar: radar, state: WS, routes: ROUTES, order: ORDER, resume: doResume, exit: exitWatch, rig: setRig, ruleRoute: ruleRoute,
  seek: function (fr) { if (WS.path && WS.state === 'route') { WS.s = clamp(fr, 0, 0.999) * WS.path.len; WS.path._i = idxAt(WS.path, WS.s); WS.v = WS.path.V[WS.path._i]; WS.capI = 0; while (WS.capI < WS.caps.length && WS.caps[WS.capI].s < WS.s) WS.capI++; WS.flyby = null; WS.offInit = false; WS.rigT = 30; } },
  peak: function () { var p = WS.path, m = 0, mi = 0; for (var i = 0; i < p.n; i++) if (p.Y[i] > m) { m = p.Y[i]; mi = i; } return { y: m, frac: p.C[mi] / p.len }; },
  estimate: function (k) { var fr = new T.Vector3(PAD.x, PAD.y + 600, PAD.z), p = buildPath(wpsOf(k), fr, new T.Vector3(0, 0, -1), 220); return { dur: p.dur + 25, km: p.len / 1000, n: p.n }; }
};
})();


===== FILE: public/city/webrtc-lock-test.html =====
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>WebRTC Lock Test — epistles experiment</title>
<style>
  *{box-sizing:border-box}
  html,body{margin:0;padding:0;background:#050308;color:#f5e9e9;font-family:system-ui,-apple-system,sans-serif;min-height:100%}
  body{padding:16px;max-width:560px;margin:0 auto}
  h1{font-size:18px;letter-spacing:2px;color:#e8c872;margin:0 0 4px}
  p.sub{color:#9a8a8a;font-size:13px;margin:0 0 16px}
  #start,#stop{display:block;width:100%;padding:18px;font-size:18px;border-radius:12px;border:2px solid #5eead4;background:#0a1214;color:#fff;cursor:pointer;margin:8px 0}
  #stop{border-color:#c1121f;display:none}
  #start:disabled{opacity:.4}
  .panel{border:1px solid #3a1520;border-radius:12px;padding:12px;margin:12px 0;background:#0c0a10}
  .panel h2{font-size:12px;letter-spacing:2px;color:#e8c872;margin:0 0 8px}
  .row{display:flex;justify-content:space-between;font-size:13px;padding:3px 0;color:#cfc0c0}
  .row b{color:#5eead4;font-weight:600}
  #meter{height:14px;border-radius:7px;background:#1a1014;overflow:hidden;margin:8px 0}
  #meterfill{height:100%;width:0%;background:linear-gradient(90deg,#5eead4,#e8c872)}
  #log{font:12px/1.5 ui-monospace,monospace;color:#9ad;max-height:220px;overflow:auto;white-space:pre-wrap}
  ol{font-size:14px;line-height:1.6;color:#e8d9d9;padding-left:22px}
  ol li{margin:6px 0}
  .warn{color:#ff9d9d;font-size:13px}
  audio{display:none}
</style>
</head>
<body>
  <h1>WEBRTC LOCK TEST</h1>
  <p class="sub">Isolated experiment — does WebRTC audio survive iPhone screen lock? Nothing is recorded. No audio leaves this page.</p>

  <button id="start" type="button">START TEST</button>
  <button id="stop" type="button">STOP TEST</button>

  <div class="panel">
    <h2>STATUS</h2>
    <div class="row"><span>microphone</span><b id="s-mic">idle</b></div>
    <div class="row"><span>connection</span><b id="s-pc">idle</b></div>
    <div class="row"><span>remote audio</span><b id="s-track">idle</b></div>
    <div id="meter"><div id="meterfill"></div></div>
  </div>

  <div class="panel">
    <h2>EVENT LOG (timestamps survive lock)</h2>
    <div id="log">waiting…</div>
  </div>

  <div class="panel">
    <h2>HOW TO RUN IT</h2>
    <ol>
      <li>Tap START TEST. Allow the microphone <b>once</b> if asked.</li>
      <li>Speak. You should hear your own voice come back with a short delay — that is the live WebRTC connection.</li>
      <li>While it is running, <b>lock your iPhone</b> (side button).</li>
      <li>Keep speaking. Listen: does your echo keep coming?</li>
      <li>Unlock. Read this page: what do the status and log say?</li>
      <li>Report: did audio continue while locked? What happened after unlock?</li>
    </ol>
    <p class="warn">Run once in Safari, then once from a Home Screen web app (Share → Add to Home Screen, open it, run again). Report both.</p>
  </div>

  <audio id="remote" playsinline></audio>

<script>
(function () {
  'use strict';
  var $ = function (id) { return document.getElementById(id); };
  var logEl = $('log'), first = true;
  function log(msg) {
    var t = new Date();
    var stamp = ('0'+t.getHours()).slice(-2)+':'+('0'+t.getMinutes()).slice(