<!-- SOLID CITY · part 2052 -->

    keel.position.y = 4.2; g.add(keel);
    for (var rI = 0; rI < 5; rI++) {
      var rib = new T.Mesh(new T.BoxGeometry(0.7, 4.6, 6.5 - Math.abs(rI - 2) * 1.6), steel);
      rib.position.set(-len / 2 + 1.5 + rI * (len - 3) / 4, 6.2, 0); g.add(rib);
    }
    [-1, 1].forEach(function (s) {
      var tr = new T.Mesh(new T.BoxGeometry(1.2, 4.2, 7), basalt);
      tr.position.set(s * len * 0.32, 2.1, 0); g.add(tr);
    });
    root.add(g);
    foot.push([px2, pz2, len + 3, 10, 0, 9, rot]);
  }
  shipFrame(-20, 20, 0.6, 13); shipFrame(22, -18, -0.5, 11); shipFrame(4, -26, 1.2, 9);
  // embers: forever climbing
  var EMB = 130, epos = new Float32Array(EMB * 3), espd = new Float32Array(EMB);
  for (i = 0; i < EMB; i++) {
    epos[i * 3] = (Math.random() - 0.5) * 12;
    epos[i * 3 + 1] = 6 + Math.random() * 22;
    epos[i * 3 + 2] = (Math.random() - 0.5) * 12;
    espd[i] = 1.5 + Math.random() * 3;
  }
  var egeo = new T.BufferGeometry();
  egeo.setAttribute('position', new T.BufferAttribute(epos, 3));
  var embers = new T.Points(egeo, new T.PointsMaterial({ color: 0xffa040, size: 0.55, transparent: true, opacity: 0.9, blending: T.AdditiveBlending, depthWrite: false }));
  root.add(embers);
  // true name + seam name: quiet markers, nothing more
  var nameMark = quietMarker(T, 'THE FOUNDRY');
  nameMark.position.set(38, 0, 8); nameMark.rotation.y = Math.PI / 2 - 0.3; root.add(nameMark);
  var seamMark = quietMarker(T, 'THE YARD EDGE');
  seamMark.position.set(45.5, 0, 0); seamMark.rotation.y = Math.PI / 2; root.add(seamMark);
  root.userData = {
    footprint: foot, center: { x: 0, z: 0 }, radius: 50, height: 24,
    update: function (t, dt) {
      var k;
      molten.material.color.setHSL(0.045 + 0.012 * Math.sin(t * 2.1), 1, 0.5 + 0.06 * Math.sin(t * 3.3));
      heat.material.opacity = 0.08 + 0.04 * Math.sin(t * 1.7);
      hum.intensity = 1.6 + 0.55 * Math.sin(t * 0.9);          // the hum
      hum2.intensity = 0.8 + 0.25 * Math.sin(t * 0.9 + 1.2);
      for (k = 0; k < channels.length; k++) {
        channels[k].m.color.setHSL(0.05 + 0.015 * Math.sin(t * 2.6 - channels[k].ph), 1, 0.52);
      }
      for (k = 0; k < braziers.length; k++) {
        braziers[k].fl.scale.y = 1 + 0.22 * Math.sin(t * 9 + braziers[k].ph);
      }
      for (k = 0; k < chains.length; k++) {
        chains[k].g.rotation.x = 0.045 * Math.sin(t * 0.7 + chains[k].ph);
        chains[k].g.rotation.z = 0.045 * Math.cos(t * 0.6 + chains[k].ph);
      }
      var step = (dt || 0.016);
      for (k = 0; k < EMB; k++) {
        epos[k * 3 + 1] += espd[k] * step;
        epos[k * 3] += Math.sin(t * 2 + k) * 0.008;
        if (epos[k * 3 + 1] > 30) {
          epos[k * 3 + 1] = 6;
          epos[k * 3] = (Math.random() - 0.5) * 12;
          epos[k * 3 + 2] = (Math.random() - 0.5) * 12;
        }
      }
      egeo.attributes.position.needsUpdate = true;
    }
  };
  return root;
}
export default createFoundry;


===== FILE: public/city/gate-ancestors-place.js =====
/* Solid City — places the GATE OF THE ANCESTORS (public/city/gate-ancestors.js)
   on the north approach to the Ancestral Roads.
   Lazy: imported after the city is built; any import/build error is caught and the city carries on.
   The site is outside the city's flat zone, so the ground is sampled at runtime.
   Adds: collision (two pillar AABBs), a gold radar marker 'GATE', a Hyper
   Controller stop, and a Watch (GRAND TOUR) caption. */
(function () {
'use strict';
if (!window.SC || !SC.api) return;
var A = SC.api, S = A.S;
var POS = { x: 0, z: -950 }, NAME = 'Gate of the Ancestors';
var M = { state: 'idle', group: null, wait: 4.2, said: false, gy: 22 };
function sampleGround() {
  var g = (typeof groundAt === 'function') ? groundAt : null;
  if (!g) return 22;
  var max = -1000, x, z, h;
  for (var i = -1; i <= 1; i++) for (var j = -1; j <= 1; j++) {
    x = POS.x + i * 14; z = POS.z + j * 14;
    try { h = g(x, z); } catch (e) { h = 22; }
    if (h > max) max = h;
  }
  return max;
}
function addBox(cx, cz, w, d, rot, y0, y1) {
  var nx = Math.max(1, Math.ceil(w / 2.5)), nz = Math.max(1, Math.ceil(d / 2.5)), cw = w / nx, cd = d / nz, c = Math.cos(rot), s = Math.sin(rot);
  var hx = cw / 2 * Math.abs(c) + cd / 2 * Math.abs(s), hz = cw / 2 * Math.abs(s) + cd / 2 * Math.abs(c);
  for (var i = 0; i < nx; i++) for (var j = 0; j < nz; j++) {
    var ox = -w / 2 + cw * (i + 0.5), oz = -d / 2 + cd * (j + 0.5), lx = cx + ox * c + oz * s, lz = cz - ox * s + oz * c;
    A.solids.push({ x0: POS.x + lx - hx, x1: POS.x + lx + hx, z0: POS.z + lz - hz, z1: POS.z + lz + hz, y0: M.gy + y0, y1: M.gy + y1, gateAncestors: true });
  }
}
function collide(ud) { (ud.footprint || []).forEach(function (b) { addBox(b[0], b[1], b[2], b[3], b[6] || 0, b[4], b[5]); }); }
function hyperStop() {
  var H = window.CityHyper; if (!H || !H.pois || H.pois.some(function (p) { return p.id === 'gate-ancestors'; })) return;
  H.pois.push({ id: 'gate-ancestors', name: NAME, short