<!-- SOLID CITY · part 255 -->
t); } catch (e) {}
  if (window.CityEighthGate) try { CityEighthGate.update(dt); } catch (e) {}
  if (window.CityHallBecoming) try { CityHallBecoming.update(dt); } catch (e) {}
  if (window.CityFoundry) try { CityFoundry.update(dt); } catch (e) {}
  if (window.CityHyperCity) try { CityHyperCity.update(dt); } catch (e) {}
  if (window.CityFirstCity) try { CityFirstCity.update(dt); } catch (e) {}
  if (window.CityFirstParking) try { CityFirstParking.update(dt); } catch (e) {}
  if (window.CityFleet) try { CityFleet.update(dt); } catch (e) {}
  if (window.CityFirstWard) try { CityFirstWard.update(dt); } catch (e) {}
  if (window.CityWards) try { CityWards.update(dt); } catch (e) {}
  if (window.CitySkyward) try { CitySkyward.update(dt); } catch (e) {}
  if (window.CityJubilee) try { CityJubilee.update(dt); } catch (e) {}
  if (window.CitySouthGate) try { CitySouthGate.update(dt); } catch (e) {}
  if (window.CityWardHouses) try { CityWardHouses.update(dt); } catch (e) {}
  if (window.CityAnnex) try { CityAnnex.update(dt); } catch (e) {}
  if (window.CityQuadrantCrown) try { CityQuadrantCrown.update(dt); } catch (e) {}
  if (window.CitySpaceport) try { CitySpaceport.update(dt); } catch (e) {}
  if (window.CityLostWings) try { CityLostWings.update(dt); } catch (e) {}
  if (window.CityOctaPool) try { CityOctaPool.update(dt); } catch (e) {}
  if (window.CitySolidFigure) try { CitySolidFigure.update(dt); } catch (e) {}
  if (window.CitySolidMirror) try { CitySolidMirror.update(dt); } catch (e) {}
  if (window.CityBetweenStones) try { CityBetweenStones.update(dt); } catch (e) {}
  // ship + avatar + sable placement (scene space = absolute - origin)
  ship.position.set(S.ship.x - origin.x, S.ship.y, S.ship.z - origin.z); ship.quaternion.copy(S.ship.q);
  var fl = S.mode === 'fly' || S.mode === 'auto' || S.mode === 'launch' || S.mode === 'watch' ? 0.6 + Math.min(S.ship.speed, 3000) / 1200 + (S.ship.boost ? 0.8 : 0) : S.mode === 'countdown' ? null : 0.01;
  if (fl !== null) shipFlames.forEach(function (f) { f.scale.set(1, Math.min(fl, 2.2) * (0.9 + Math.random() * 0.2), 1); });
  avatar.position.set(S.p.x - origin.x, S.p.y, S.p.z - origin.z);
  sable.position.set(SABLE_POS.x - origin.x, SABLE_POS.y + 0.1 + Math.sin(S.t * 1.3) * 0.06, SABLE_POS.z - origin.z);
  if (S.mode === 'walk') sable.rotation.y = Math.atan2(S.p.x - SABLE_POS.x, S.p.z - SABLE_POS.z);
  sable.userData.halo.rotation.z = S.t;
  for (var i = 0; i < animated.length; i++) animated[i](S.t);
  placeCamera(dt);
  updateTerrain({ x: camAbs.x, y: camAbs.y, z: camAbs.z }, MOBILE ? 4 : 6);
  updateHUD(dt);
  /* the render rest (AI BROWSER MODE): while the visitor stands
   * still, the picture is already drawn — drawing it sixty times
   * a second is the idle burn that killed every inspection. In
   * this mode the render fires on movement, and otherwise on a
   * slow heartbeat (every 20th frame) so living things still stir
   * on the still visitor's screen. */
  if (window.CityAILight) {
    aiF++;
    var aiMoved = Math.abs(S.p.x - aiLX) > 0.05 || Math.abs(S.p.z - aiLZ) > 0.05 || Math.abs((S.camYaw || 0) - aiLYaw) > 0.002 || Math.abs((S.camPitch || 0) - aiLP) > 0.002 || Math.abs(input.sx) + Math.abs(input.sy) > 0.02;
    if (aiMoved || aiF % 20 === 0) { aiLX = S.p.x; aiLZ = S.p.z; aiLYaw = S.camYaw || 0; aiLP = S.camPitch || 0; renderer.render(scene, camera); }
  } else renderer.render(scene, camera);
  if (PERF) { renderer.getContext().finish(); workTimes.push(performance.now() - now0); if (workTimes.length > 600) workTimes.shift(); }
}

/* ---------------- the frame watchdog (the King's freeze, 2026-10-14) ----------------
 * The master frame once had no witness: the Perf Meter pulses on its
 * own loop, the citizens keep their own interval, the music rides the
 * media pipeline — so when a phone starved this loop (or the canvas
 * lost its context), every instrument read healthy over a frozen
 * picture, and the code went on believing the King was running. The
 * watchdog keeps time on an interval — the clock that provably
 * survives — and answers for the frame: if the pulse goes stale while
 * the King is in his city, it re-kicks the frame chain, asks the
 * canvas for its context back, and, should animation frames stay
 * starved, drives the frame itself at a limp-home cadence until the
 * browser resumes. Every stall is written to the build memory. */
var wdFb = null, wdStalls = 0, wdCtxAsked = 0;
function wdLog(what) { try { if (window.logBuild) window.logBuild('city', 'watchdog: ' + what); } catch (e) {} try { console.warn('[watchdog] ' + what); } catch (e) {} }
canvas.addEventListener('webglcontextlost', function (e) { e.preventDefault(); wdLog('canvas context lost at ' + Math.round(S.t) + 's'); }, false);
canvas.addEventListener('webglcontextrestored', function () { wdLog('canvas context restored'); try { fitView(); } catch (e) {} }, false);
setInterval(function () {
  if (!running || document.hidden) { if (wdFb) 