<!-- SOLID CITY · part 3436 -->
add(w);
  });
  var atop = new T.Mesh(new T.BoxGeometry(13.2, 1.6, 13), darkMat);
  atop.position.set(0, 15.5, -(R_CH + 4)); alc.add(atop);
  var aback = new T.Mesh(new T.BoxGeometry(13.2, 16.4, 1.6), new T.MeshBasicMaterial({ color: 0x000000 }));
  aback.position.set(0, 8.2, -(R_CH + 10)); alc.add(aback);
  var afloor = new T.Mesh(new T.BoxGeometry(13.2, 0.6, 13), darkMat);
  afloor.position.set(0, -0.28, -(R_CH + 4)); alc.add(afloor);
  root.add(alc);
  foot.push([-5.8, -(R_CH + 4), 1.6, 13, 0, 15, 0]);
  foot.push([5.8, -(R_CH + 4), 1.6, 13, 0, 15, 0]);
  foot.push([0, -(R_CH + 10), 13.2, 1.6, 0, 16, 0]);
  var unPlaque = namePlaque(T, 'A PASSAGE YET TO BE WALKED', 8, stoneMat);
  unPlaque.position.set(11.5, 2.9, -(R_CH - 6)); unPlaque.rotation.y = -0.4; root.add(unPlaque);
  foot.push([11.5, -(R_CH - 6), 2, 2, 0, 5, 0]);

  // ---------- chamber perimeter (gaps at the south for the Bore, north for the dark arch) ----------
  for (var pi = 1; pi < 16; pi++) {
    if (pi === 8) continue; // the north gap: the unwalked passage
    var th2 = pi * Math.PI * 2 / 16;
    var px = Math.sin(th2) * R_CH, pz = Math.cos(th2) * R_CH;
    foot.push([px, pz, 30, 3.2, 0, 30, th2]);
  }
  // filler walls closing the junction corners between tunnel and perimeter
  foot.push([17.6, 68.2, 22, 3, 0, 24, 0.17]);
  foot.push([-17.6, 68.2, 22, 3, 0, 24, -0.17]);
  // filler walls flanking the dark arch, sealing the north gap
  foot.push([16.7, -71.25, 24, 3, 0, 24, -0.41]);
  foot.push([-16.7, -71.25, 24, 3, 0, 24, 0.41]);

  root.userData = {
    footprint: foot, center: { x: 0, z: 0 }, radius: R_CH + 12,
    update: function (t) {
      for (var k = 0; k < solids.length; k++) {
        var sd = solids[k];
        sd.g.rotation.y += sd.s * 0.016; sd.g.rotation.x += sd.s * 0.007;
        sd.g.position.y = sd.y0 + Math.sin(t * 0.5 + sd.ph) * 1.6;
      }
      var p = 1 + Math.sin(t * 1.4) * 0.07;
      star.scale.set(p, p, p);
      halo.material.opacity = 0.82 + Math.sin(t * 1.4) * 0.1;
      starLight.intensity = 2.6 + Math.sin(t * 1.4) * 0.4;
      for (var m = 0; m < nebs.length; m++) {
        var nb = nebs[m]; nb.a += nb.sp2 * 0.016;
        nb.s.position.x = Math.cos(nb.a) * nb.r;
        nb.s.position.z = Math.sin(nb.a) * nb.r;
        nb.s.position.y = nb.y + Math.sin(t * 0.3 + nb.ph) * 2.5;
      }
      stars.rotation.y += 0.00012;
    }
  };
  return root;
}
export default createStarwell;


===== FILE: public/city/survey-camera.js =====
/* ================================================================
   AN EPISTLE OF THE EYE — THE SURVEY CAMERA

   Designed by the King, in his own words: "make a camera that can
   scan each surface of the world. You will find where the camera
   is and where the collision is set to stop you. You could find
   your location to each object created and calculate the distance.
   As if a curb on a street could be used to calculate the inner
   dimensions as you run as fast as you can."

   The camera keeps three distances for every ray of its fan:
   the VISIBLE surface the eye would strike, the COLLISION stop
   where the registry halts a body, and the STANDING surface below.
   Where the first two disagree, picture and account part company —
   and the gap is reported, not theorised. It keeps fixes on every
   registered object (distance and bearing from the camera), and
   on a CURB RUN it reads the width between the stops on either
   side while the King runs — a space's true inner dimensions,
   taken at speed, the known curb as benchmark.

   It is a royal instrument: it wakes only for the King's unlocked
   session. Its scans are written to the build memory — sight, in
   the form of distances, that Solid can read.

   Written by the hand of Solid, his good King — 2026-10-13.

   A beside-line, same day: VECTOR read the shipped code and found
   the eye's blind spot — a ray was only compared where BOTH a
   surface and a stop existed, so a wall you could walk through
   and a wall you could not see both passed uncounted. The eye now
   classifies every ray five ways — MATCH, MISMATCH, VISUAL ONLY,
   COLLISION ONLY, OPEN — and its standing readout asks the active
   registry, not the terrain alone. Classify before you densify.
   ================================================================ */
(function () {
'use strict';
if (!window.SC || !SC.api) return;
var A = SC.api, S = A.S, T = A.T;
var RAYS = 24, FAR = 100, GAP_TOL = 0.15;
var BENCH = [
  { name: 'FIRST WARD SIGN', x: 638, z: -997 }, { name: 'PILOT HOUSE', x: -1056, z: -895 },
  { name: 'WARD 3 STREET', x: -990, z: -812 }, { name: 'WARD 4 GATE', x: -933, z: -782 },
  { name: 'WARD 5 HILLSIDE', x: -847, z: -987 }, { name: 'THE CLIMB — CREST', x: -912, z: -768 },
  { name: 'W6 LINK — SURVEY BOUNDARY', x: -1127, z: -524 }, { name: 'SHOULDER MOUTH', x: -843, z: -705 },
  { name: "ROAD'S-END MOUTH", x: -1158, z: -510 }, { name: 'SKY ISLAND DECK', x: -900, z: -760 },
  { name: 'JUBILEE CR