<!-- SOLID CITY · part 2961 -->
 lk * Math.PI / 4));
    [170, 250, 330, 410, 492].forEach(function (rb, bi) {
      solid.add(new T.RingGeometry(rb - 4, rb + 4, 72), localMatrix(cf2, 0, 0.05, 0, -Math.PI / 2, 0, 0), bi % 2 ? C.lawnDeep : C.lawn);
      for (var sk = 0; sk < 42; sk++) {
        var sa2 = R() * Math.PI * 2, sr = rb + (R() - 0.5) * 6.4;
        var sf = frame(sr, sa2);
        box(glow, sf, 0.55, 0.3, 0.55, 0, 0.16, 0, [C.flower1, C.flower2, C.flower3][sk % 3]);
      }
    });
    var lampAngles = {
      170: [0.7, 1.9, 3.2, 4.1, 5.6, 6.2], 250: [0.5, 1.8, 2.7, 3.7, 4.85, 6.2],
      330: [0.85, 1.45, 2.4, 3.4, 4.75, 6.15], 410: [0.57, 1.9, 3.8, 4.45, 5.55, 6.2],
      492: [0.62, 2.5, 3.15, 4.0, 4.9, 6.2]
    };
    Object.keys(lampAngles).forEach(function (rb) { lampAngles[rb].forEach(function (a) { buildLamp(frame(+rb, a)); }); });
    glow.add(new T.TorusGeometry(528, 0.3, 6, 96), localMatrix(cf2, 0, 0.1, 0, Math.PI / 2, 0, 0), C.gold);
    buildings.push({ name: 'THE FLAME PLAZA', kind: 'plaza', x: CX, z: CZ, y: BASE, ring: 0, district: DISTRICTS.hearth });
  }

  group.add(solid.build(new T.MeshLambertMaterial({ vertexColors: true })));
  group.add(glassB.build(new T.MeshLambertMaterial({ vertexColors: true, transparent: true, opacity: 0.55 })));
  group.add(glow.build(new T.MeshBasicMaterial({ vertexColors: true })));

  var counts = { houses: 0, hotels: 0, mansions: 0, pavilions: 0, fountains: 0, trees: trees.length, stalls: 6 };
  buildings.forEach(function (b) { if (counts[b.kind] != null) counts[b.kind + 's'] = (counts[b.kind + 's'] || 0) + 1; });
  counts.houses = buildings.filter(function (b) { return b.kind === 'house'; }).length;
  counts.hotels = buildings.filter(function (b) { return b.kind === 'hotel'; }).length;
  counts.mansions = buildings.filter(function (b) { return b.kind === 'mansion'; }).length;
  counts.pavilions = buildings.filter(function (b) { return b.kind === 'pavilion'; }).length;
  counts.fountains = buildings.filter(function (b) { return b.kind === 'fountain'; }).length;

  group.userData = {
    buildings: buildings, footprint: footprints, standRects: standRects, port: port,
    counts: counts, center: { x: CX, z: CZ }, topY: BASE, isSky: SKY, glassFloor: SKY, beautified: !!opts.beautify, pools: group.userDataPools || 0,
    update: function (dt, t) { halo.rotation.z = (t || 0) * 0.12; }
  };
  return group;
}
export default createRingCity;


===== FILE: public/city/ring-city.js.bak-all-20261014 =====
/* 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().getNormalMatrix(matrix);
    var c = new T.Color(color), v = new T.Vector3(), nv = ne