<!-- SOLID CITY · part 2975 -->
n 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 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 = new T.Vector3();
    for (var i = 0; i < p.count; i++) {
      v.fromBufferAttribute(p, i).applyMatrix4(matrix);
      this.pos.push(v.x, v.y, v.z);
      nv.fromBufferAttribute(n, i).applyMatrix3(nm).normalize();
      this.nor.push(nv.x, nv.y, nv.z);
      this.col.push(c.r, c.g, c.b);
    }
  };
  Batch.prototype.build = function (mat) {
    var g = new T.BufferGeometry();
    g.setAttribute('position', new T.Float32BufferAttribute(this.pos, 3));
    g.setAttribute('normal', new T.Float32BufferAttribute(this.nor, 3));
    g.setAttribute('color', new T.Float32BufferAttribute(this.col, 3));
    var m = new T.Mesh(g, mat); m.frustumCulled = false; return m;
  };
  var solid = new Batch(), glow = new Batch(), glassB = new Batch();

  /* ---------- frames: local X = tangent, local Z = radial (outward) ---------- */
  function frame(radius, angle) {
    var rx = Math.cos(angle), rz = Math.sin(angle);
    return { r: radius, a: angle, rx: rx, rz: rz, tx: -rz, tz: rx, x: CX + rx * radius, z: CZ + rz * radius };
  }
  function localMatrix(f, lx, ly, lz, ex, ey, ez, sx, sy, sz) {
    var basis = new T.Matrix4().makeBasis(
      new T.Vector3(f.tx, 0, f.tz), new T.Vector3(0, 1, 0), new T.Vector3(f.rx, 0, f.rz));
    basis.setPosition(f.x, BASE, f.z);
    var local = new T.Matrix4().compose(
      new T.Vector3(lx, ly, lz),
      new T.Quaternion().setFromEuler(new T.Euler(ex || 0, ey || 0, ez || 0)),
      new T.Vector3(sx == null ? 1 : sx, sy == null ? 1 : sy, sz == null ? 1 : sz));
    return basis.multiply(local);
  }
  function box(batch, f, w, h, d, lx, ly, lz, color, ex, ey, ez) {
    batch.add(new T.BoxGeometry(w, h, d), localMatrix(f, lx, ly, lz, ex, ey, ez), color);
  }
  function cyl(batch, f, rt, rb, h, seg, lx, ly, lz, color, ex, ey, ez) {
    batch.add(new T.CylinderGeometry(rt, rb, h, seg), localMatrix(f, lx, ly, lz, ex, ey, ez), color);
  }
  function sph(batch, f, r, lx, ly, lz, color, sx, sy, sz) {
    batch.add(new T.SphereGeometry(r, 10, 7), localMatrix(f, lx, ly, lz, 0, 0, 0, sx, sy, sz), color);
  }
  function disc(batch, f, r, ly, color) {
    batch.add(new T.CircleGeometry(r, 36), localMatrix(f, 0, ly, 0, -Math.PI / 2, 0, 0), color);
  }

  var buildings = [], footprints = [], standRects = [], trees = [];
  function footprint(f, w, d, y0, y1) { footprints.push({ x: f.x, z: f.z, w: w, d: d, a: f.a, y0: y0, y1: y1 }); }
  function standRect(f, w, d, y, lx, lz) {
    standRects.push({ cx: f.x + f.tx * (lx || 0) + f.rx * (lz || 0), cz: f.z + f.tz * (lx || 0) + f.rz * (lz || 0), hw: w / 2, hd: d / 2, tx: f.tx, tz: f.tz, rx: f.rx, rz: f.rz, y: y });
  }
  function record(name, kind, f, district) {
    var rec = { name: name, kind: kind, x: Math.round(f.x * 10) / 10, z: Math.round(f.z * 10) / 10, y: BASE, ring: Math.round(f.r), district: district };
    buildings.push(rec); return rec;
  }

  /* ---------- the builders --------