<!-- SOLID CITY · part 1801 -->
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();
  function mat4(x, y, z, ex, ey, ez, sx, sy, sz) {
    return new T.Matrix4().compose(new T.Vector3(x, y, z),
      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));
  }
  function box(b, w, h, d, x, y, z, color, ex, ey, ez) { b.add(new T.BoxGeometry(w, h, d), mat4(x, y, z, ex, ey, ez), color); }
  function cyl(b, rt, rb, h, seg, x, y, z, color, ex, ey, ez) { b.add(new T.CylinderGeometry(rt, rb, h, seg), mat4(x, y, z, ex, ey, ez), color); }
  function sph(b, r, x, y, z, color, sx, sy, sz) { b.add(new T.SphereGeometry(r, 10, 7), mat4(x, y, z, 0, 0, 0, sx, sy, sz), color); }
  function px(r, a) { return CX + Math.cos(a) * r; }
  function pz(r, a) { return CZ + Math.sin(a) * r; }

  /* ---------- the island's body ---------- */
  cyl(solid, 330, 42, 46, 48, CX, BASE - 23, CZ, C.stoneDark);
  glow.add(new T.TorusGeometry(330, 0.35, 6, 72), mat4(CX, BASE - 0.2, CZ, Math.PI / 2, 0, 0), C.gold);
  for (var hi = 0; hi < 12; hi++) {
    var ha = hi / 12 * Math.PI * 2;
    glassB.add(new T.ConeGeometry(2.2, 9, 5), mat4(px(322, ha), BASE - 6, pz(322, ha), Math.PI, 0, 0), C.crystal);
  }

  /* ---------- the land itself: a radial heightfield ---------- */
  var RINGS = 26, SECT = 84;
  var tPos = [], tCol = [], tIdx = [];
  var cGrass = new T.Color(C.lawn), cDeep = new T.Color(C.lawnDeep), cRock = new T.Color(C.rock), cPale = new T.Color(0x9aa8ad), tmpC = new T.Color();
  for (var ri = 0; ri <= RINGS; ri++) {
    var rr = ri / RINGS * 330;
    for (var si = 0; si <= SECT; si++) {
      var th = si / SECT * Math.PI * 2;
      var hh = crownHeight(rr, th);
      tPos.push(px(rr, th), BASE + hh, pz(rr, th));
      var slope = Math.abs(crownHeight(Math.min(rr + 8, 330), th) - hh) / 8;
      if (hh > 40 || slope > 0.85) tmpC.copy(cPale); else if (hh > 24 || slope > 0.5) tmpC.copy(cRock); else if ((ri + si) % 3 === 0) tmpC.copy(cDeep); else tmpC.copy(cGrass);
      tCol.push(tmpC.r, tmpC.g, tmpC.b);
      if (ri < RINGS && si < SECT) {
        var a0 = ri * (SECT + 1) + si, b0 = a0 + SECT + 1;
        tIdx.push(a0, b0, a0 + 1, b0, b0 + 1, a0 + 1);
      }
    }
  }
  var tGeo = new T.BufferGeometry();
  tGeo.setAttribute('position', new T.Float32BufferAttribute(tPos, 3));
  tGeo.setAttribute('color', new T.Float32BufferAttribute(tCol, 3));
  tGeo.setIndex(tIdx); tGeo.computeVertexNormals();
  var terrain = new T.Mesh(tGeo, new T.MeshLambertMaterial({ vertexColors: true }));
  terrain.frustumCulled = false; group.add(terrain);

  /* ---------- the spring lake and the three rivers ---------- */
  cyl(solid, 20, 23, 0.5, 24, CX, BASE + 30.15, CZ, C.pool);
  CROWN_RIVERS.forEach(function (ra) {
    var pts = [];
    for (var r2 = 28; r2 <= 330; r2 += 9.5) pts.push([px(r2, ra), BASE + crownHeight(r2, ra) + 0.22, pz(r2, ra)]);
    for (var i2 = 0; i2 < pts.length - 1; i2++) {
      var p0 = pts[i2], p1 = pts[i2 + 1];
      var d = new T.Vector3(p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]);
      var len = d.length(); d.normalize();
      var side = new T.Vector3().crossVectors(new T.Vector3(0, 1, 0), d).normalize();
      var up = new T.Vector3().crossVectors(d, side).normalize();
      var bm = new T.Matrix4().makeBasis(side, up, d);
      bm.setPosition((p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2, (p0[2] + p1[2]) / 2);
      solid.add(new T.BoxGeometry(5, 0.18, len + 1.2), bm, C.pool);
    }
  });

  /* ---------- the crystal mountain ---------- */
  var peakSolids = [];
  CROWN_PEAKS.forEach(function (p) {
    var cx = px(p.r, p.a), cz = pz(p.r, p.a), gy = BASE + crownHeight(p.r, p.a);
    var shards = 4 + Math.floor(R() * 2);
    for (var si2 = 0; si2 < shards; si2++) {
      var off = si2 === 0 ? 0 : 4 + R() * 7, oa = R() * Math.PI * 2;
      var sh = p.crystal * (si2 === 0 ? 1 : 0.45 + R() * 0.4);
      glassB.add(new T.ConeGeometry(sh * 0.24 + 1.4, sh, 6), mat4(cx + Math.cos(oa) * off, gy + sh / 2 - 1.5, cz + Math.sin(oa) * off, (R() - 0.5) * 0.14, R() * 3, (R() - 0.5) * 0.14), C.crystal);
    }
    sph(glow, 2.2, cx, gy + p.crystal * 0.55, cz, C.gold);
    peakSolids.push({ x: cx, z: cz, w: 15, d: 15, a: 0, y0: gy - 2, y1: gy + p.crystal });
  });

  /* ---------- rocks ---------- */
  for (var rk = 0; rk < 24; rk++) {
    var ra2 = R() * Math.PI *