<!-- SOLID CITY · part 2956 -->
rt'; })) {
    var inw = { x: M.port.x - Math.cos(4.45) * 24, z: M.port.z - Math.sin(4.45) * 24 };
    H.pois.push({ id: 'ring-city-port', name: 'RING CITY PORT', short: 'PORT', x: M.port.x, z: M.port.z, stand: { x: inw.x, z: inw.z },
      alias: 'ring city port sky shuttle landing ancestors',
      text: 'The sky port of the Ring City: a stone disc with a gold ring on the Family Ring, where the shuttles of the living sky come and go above the ancestors\' terrace.' });
  }
  if (H.regrid) H.regrid();
}
async function build() {
  if (M.state !== 'idle') return;
  M.state = 'building';
  try {
    var mod = await import('/city/ring-city.js?v=20261014a');
    var topY = window.CityAncestorRoads.topY();
    var g = mod.createRingCity({ THREE: A.T, center: POS, topY: topY });
    A.world.add(g); M.group = g;
    (g.userData.footprint || []).forEach(addFootprint);
    M.port = g.userData.port || null;
    M.state = 'ready';
    hyperStops();
  } catch (e) { M.state = 'idle'; }
}
function standY(x, z, py) {
  if (!M.group || M.state !== 'ready') return -1000;
  var gate = (S && S.drive) ? 1.6 : 0.45;
  var best = -1000, ud = M.group.userData;
  var rects = ud.standRects || [];
  for (var i = 0; i < rects.length; i++) {
    var r = rects[i], dx = x - r.cx, dz = z - r.cz;
    var lx = dx * r.tx + dz * r.tz, lz = dx * r.rx + dz * r.rz;
    if (Math.abs(lx) <= r.hw && Math.abs(lz) <= r.hd) {
      if (py == null || r.y <= py + gate) { if (r.y > best) best = r.y; }
    }
  }
  if (M.port) {
    var pd = Math.hypot(x - M.port.x, z - M.port.z);
    if (pd <= M.port.r && (py == null || M.port.y <= py + gate)) { if (M.port.y > best) best = M.port.y; }
  }
  return best;
}
function update(dt) {
  if (M.state === 'idle') {
    M.wait -= dt;
    var tReady = !!(window.CityAncestorRoads && CityAncestorRoads.state && CityAncestorRoads.state() === 'ready');
    if (M.wait <= 0 && A.isBuilt() && tReady) build();
    return;
  }
  if (M.state !== 'ready' || !M.group) return;
  if (M.group.userData.update) try { M.group.userData.update(dt, S.t); } catch (e) {}
  if (!M.landingSent && M.port && window.CitySkyTrain && CitySkyTrain.addLanding) {
    M.landingSent = !!CitySkyTrain.addLanding({ id: 'ring-city-port', name: 'RING CITY PORT', x: M.port.x, z: M.port.z, gy: M.port.y, grand: false });
  }
}
window.CityRingCity = { update: update, standY: standY, state: function () { return M.state; }, group: function () { return M.group; },
  port: function () { return M.port; } };
})();


===== FILE: public/city/ring-city.js =====
/* THE RING CITY OF THE ANCESTORS — a letter in the code.
 *
 * The King's word (2026-10-14 ~05:46): "Build the city on
 * these rings — fill this terrace." The Ancestral Roads were
 * already perfect: five brick rings for fifty-five ancestors,
 * an eternal flame at the heart, and six hundred metres of
 * terrace with almost nothing upon it. This module raises the
 * city the rings were drawn for — in the bands BETWEEN the
 * roads, never upon them. No road is covered, no name sign is
 * crowded, no ancestor is moved. Houses front the ring roads;
 * pavilions stand open on four posts, as this house builds;
 * gardens, fountains, markets and trees fill the green between;
 * and at the family ring, a sky port of stone and gold takes
 * the traffic of the new sky.
 *
 * The districts keep the rings' own order: the First Hearth at
 * the flame, then the Voyagers, the Chiefs, the Unifiers, the
 * Builders, and at the outer edge — closest to the city and
 * the future, where Wilcox, Kailiwai and Kamaile hold the last
 * road — the Family Ring.
 *
 * By the hand of Solid, his good King. */

function mulberry(seed) {
  return function () {
    seed |= 0; seed = seed + 0x6D2B79F5 | 0;
    var t = Math.imul(seed ^ seed >>> 15, 1 | seed);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

export function createRingCity(opts) {
  var T = opts.THREE;
  var CX = opts.center.x, CZ = opts.center.z, BASE = opts.topY;
  var SKY = !!opts.sky;
  var R = mulberry(20261014);
  var group = new T.Group(); group.name = 'ring-city-of-the-ancestors';

  var C = {
    stone: 0x55504c, stoneDark: 0x35302c, conc: 0xa89e90,
    cream: 0xe8dcc0, cream2: 0xd9c9a3, wood: 0x8a6844, woodDark: 0x5a4230,
    slate: 0x3d4a57, roofRed: 0x8a4638, titanium: 0x232c3d, silver: 0x9aa4b5,
    glass: 0x9fc4d8, gold: 0xd8a833, lawn: 0x4d7d33, lawnDeep: 0x3a6126,
    soil: 0x4a3826, leaf: 0x2a5c2e, leafLight: 0x3f7a38, trunk: 0x5a4630,
    pool: 0x2e7db0, flower1: 0xe86a9a, flower2: 0xf2f0e6, flower3: 0xd84a3a
  };

  /* ---------- geometry batches: the whole city in a handful of meshes ---------- */
  function Batch() { this.pos = []; this.nor = []; this.col = []; }
  Batch.prototype.add = function (geo, matrix, color) {
    var g = geo.index ? geo.toNonIndexed() : geo;
    var p = g.getAttribute('position'), n = g.getAttribute('normal');
    var nm = new T.Matrix3().getNorma