<!-- SOLID CITY · part 2974 -->
 the houses */
  var treeBands = [
    { r: 170, as: [0.7, 1.95, 3.2, 4.1, 5.65] }, { r: 250, as: [0.5, 1.55, 2.7, 3.6, 4.85] },
    { r: 330, as: [0.95, 2.4, 3.4, 4.7, 5.5] }, { r: 410, as: [0.55, 1.55, 2.7, 3.8, 4.95, 5.65] },
    { r: 492, as: [0.62, 1.55, 2.35, 3.15, 4.0, 4.85, 6.15] }
  ];
  treeBands.forEach(function (tb) { tb.as.forEach(function (a, i) { buildTree(frame(tb.r + (i % 2 ? 9 : -9), a), i % 3 === 0); }); });
  [1.2, 2.5, 3.9, 5.6].forEach(function (a, i) { buildTree(frame(96, a), i % 2 === 0); });

  /* ---------- THE FILLING (the sky twin only) ----------
   * The King's word (~06:18): "fill it with homes, big and
   * small." A second sowing in the lawful gaps of every band —
   * great houses and small cottages between the first city's
   * buildings, under the same law: never on a road's radius,
   * never crowding a sign, never on the processional way. */
  if (opts.dense) {
    house(88, 4.5, DISTRICTS.hearth, 2, 1, 0.62);
    house(86, 1.15, DISTRICTS.hearth, 4, 1, 0.62);
    house(170, 0.7, DISTRICTS.voyagers, 1, 2, 1.45);
    house(170, 2.4, DISTRICTS.voyagers, 3, 1, 0.62);
    house(170, 3.2, DISTRICTS.voyagers, 0, 1, 1);
    house(170, 4.05, DISTRICTS.voyagers, 2, 1, 0.62);
    house(170, 5.65, DISTRICTS.voyagers, 4, 1, 1);
    house(170, 6.15, DISTRICTS.voyagers, 1, 2, 1.45);
    house(250, 0.5, DISTRICTS.chiefs, 2, 1, 1);
    house(250, 1.9, DISTRICTS.chiefs, 0, 1, 0.62);
    house(250, 2.7, DISTRICTS.chiefs, 3, 2, 1.45);
    house(250, 3.6, DISTRICTS.chiefs, 1, 1, 1);
    house(250, 4.75, DISTRICTS.chiefs, 4, 1, 0.62);
    house(330, 0.95, DISTRICTS.unifiers, 1, 1, 0.62);
    house(330, 1.45, DISTRICTS.unifiers, 3, 1, 1);
    house(330, 2.4, DISTRICTS.unifiers, 0, 2, 1.45);
    house(330, 3.4, DISTRICTS.unifiers, 2, 1, 0.62);
    house(330, 6.15, DISTRICTS.unifiers, 4, 1, 1);
    house(410, 0.6, DISTRICTS.builders, 2, 2, 1.45);
    house(410, 1.85, DISTRICTS.builders, 4, 1, 0.62);
    house(410, 3.75, DISTRICTS.builders, 1, 1, 1);
    house(410, 5.6, DISTRICTS.builders, 3, 1, 0.62);
    house(410, 6.25, DISTRICTS.builders, 0, 1, 1);
    house(492, 0.62, DISTRICTS.family, 3, 1, 1);
    house(492, 2.45, DISTRICTS.family, 1, 1, 0.62);
    house(492, 3.15, DISTRICTS.family, 2, 2, 1.45);
    house(492, 4.0, DISTRICTS.family, 0, 1, 1);
    house(492, 4.85, DISTRICTS.family, 4, 1, 0.62);
    house(492, 6.15, DISTRICTS.family, 1, 1, 1);
  }

  /* ---------- THE RING CITY PORT ---------- */
  var portF = frame(492, 4.45);
  cyl(solid, portF, 18, 18, 0.22, 36, 0, 0.11, 0, C.stone);
  var ringGeo = new T.TorusGeometry(15.6, 0.16, 6, 48);
  glow.add(ringGeo, localMatrix(portF, 0, 0.26, 0, Math.PI / 2, 0, 0), C.gold);
  cyl(solid, portF, 0.14, 0.2, 5.4, 6, 0, 2.7, 16.5, C.titanium);
  sph(glow, portF, 0.5, 0, 5.7, 16.5, C.gold);
  footprints.push({ x: portF.x + portF.rx * 16.5, z: portF.z + portF.rz * 16.5, w: 0.4, d: 0.4, a: portF.a, y0: BASE, y1: BASE + 5.4 });
  var port = { name: 'RING CITY PORT', x: portF.x, z: portF.z, y: BASE + 0.22, r: 18 };
  var halo = new T.Mesh(new T.TorusGeometry(20, 0.18, 6, 48), new T.MeshBasicMaterial({ color: C.gold }));
  halo.rotation.x = Math.PI / 2; halo.position.set(portF.x, BASE + 8.5, portF.z);
  group.add(halo);

  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,
    update: function (dt, t) { halo.rotation.z = (t || 0) * 0.12; }
  };
  return group;
}
export default createRingCity;


===== FILE: public/city/ring-city.js.bak-skytwin-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 draw