<!-- SOLID CITY · part 2825 -->
 so the
   * crown reads as LEAVES all the way through, not a lid */
  var podLobes = [];
  pods.forEach(function (p) {
    for (var li = 0; li < 5; li++) {
      var la3 = (li / 5) * 6.283 + R() * 0.7, lr3 = p.cr * (0.52 + R() * 0.2);
      podLobes.push({ x: p.x + Math.cos(la3) * lr3, z: p.z + Math.sin(la3) * lr3, y: p.y + 7.6 - p.cr * 0.13, s: p.cr * (0.30 + R() * 0.1), ry: R() * 6.28 });
    }
  });
  instanced('pod-lobes', podCanGeo, lambert(0xffffff, { flatShading: true }), podLobes,
    function (d, p) { d.position.set(p.x, p.y, p.z); d.rotation.set(0, p.ry, 0); d.scale.set(p.s, p.s * 0.42, p.s); },
    function (c) { greens(c, 0.18, 0.10); });

  /* ——— THE BANYANS: pillars from the sky ——— */
  var banTrunkGeo = new T.CylinderGeometry(1.25, 1.7, 7.5, 10); banTrunkGeo.translate(0, 3.75, 0); bendGeo(banTrunkGeo, 0.6, 7.5);
  var banCanGeo = new T.IcosahedronGeometry(1, 1); fbmDisplace(banCanGeo, 0.85, 0.19, 4.7, 1);
  var banyans = scatterGiants(hiTier ? 8 : 5, 110, function (x, z, h) { if (h < 3 || h > 110 || slope(x, z, h) > 0.5) return null; return { x: x, z: z, y: h, ry: R() * 6.28, cr: 10 + R() * 3.5, ch: 10.5 + R() * 2 }; },
    [[-150, 700], [500, 500], [-700, -250], [1500, 900]]);
  counts.banyans = banyans.length; note('banyans', banyans, 8);
  banyans.forEach(function (p) { leafSpawns.push({ x: p.x, y: p.y + p.ch - 1.2, z: p.z }); });
  instanced('banyan-trunks', banTrunkGeo, lambert(0x54432e), banyans,
    function (d, p) { d.position.set(p.x, p.y - 0.15, p.z); d.rotation.set(0, p.ry, 0); d.scale.setScalar(1); }, null);
  instanced('banyan-canopies', banCanGeo, lambert(0xffffff, { flatShading: true }), banyans,
    function (d, p) { d.position.set(p.x, p.y + p.ch, p.z); d.rotation.set(0, p.ry, 0); d.scale.set(p.cr, p.cr * 0.34, p.cr); },
    function (c) { greens(c, 0.17, 0.09); });
  /* lobes between the pillars: the banyan's crown is a
   * CITY of leaf masses — satellites fill the spans the
   * prop roots hold up, each above its own pillar ring */
  var banLobes = [];
  banyans.forEach(function (p) {
    for (var li = 0; li < 6; li++) {
      var la4 = (li / 6) * 6.283 + R() * 0.6, lr4 = p.cr * (0.55 + R() * 0.25);
      banLobes.push({ x: p.x + Math.cos(la4) * lr4, z: p.z + Math.sin(la4) * lr4, y: p.y + p.ch - p.cr * 0.10, s: p.cr * (0.34 + R() * 0.12), ry: R() * 6.28 });
    }
  });
  instanced('banyan-lobes', banCanGeo, lambert(0xffffff, { flatShading: true }), banLobes,
    function (d, p) { d.position.set(p.x, p.y, p.z); d.rotation.set(0, p.ry, 0); d.scale.set(p.s, p.s * 0.4, p.s); },
    function (c) { greens(c, 0.16, 0.09); });
  var props = [], hangers = [];
  banyans.forEach(function (p) {
    var n = 6 + Math.floor(R() * 3);
    for (var i = 0; i < n; i++) {
      var a = (i / n) * 6.283 + R() * 0.5, rr = p.cr * (0.55 + R() * 0.38);
      var rx = p.x + Math.cos(a) * rr, rz = p.z + Math.sin(a) * rr;
      var gy = H(rx, rz), topY = p.y + p.ch - 0.4, len = topY - gy;
      if (len > 2) props.push({ x: rx, z: rz, yMid: gy + len / 2, len: len, s: 0.30 + R() * 0.16 });
    }
    for (var j = 0; j < 4; j++) {
      var a2 = R() * 6.283, rr2 = p.cr * (0.3 + R() * 0.5), hl = 1.8 + R() * 2.4;
      hangers.push({ x: p.x + Math.cos(a2) * rr2, z: p.z + Math.sin(a2) * rr2, yMid: p.y + p.ch - 0.6 - hl / 2, len: hl, s: 0.055 });
    }
  });
  counts.props = props.length; counts.hangers = hangers.length;
  var propGeo = new T.CylinderGeometry(0.5, 0.68, 1, 6);
  var propMat = lambert(0x5d4630);
  instanced('banyan-props', propGeo, propMat, props,
    function (d, p) { d.position.set(p.x, p.yMid, p.z); d.rotation.set(0, 0, 0); d.scale.set(p.s, p.len, p.s); }, null);
  instanced('banyan-hangers', propGeo, propMat, hangers,
    function (d, p) { d.position.set(p.x, p.yMid, p.z); d.rotation.set(0, 0, 0); d.scale.set(p.s, p.len, p.s); }, null);

  /* FALLING LEAVES (Dirksen's particles chapter: drive
   * the vertices directly) — a points cloud seeded under
   * the great crowns; each leaf's fall is computed in the
   * vertex shader from its own seed: a slow cycle down,
   * swirling wider as it drops. No CPU per frame; the
   * wind uniform is the only clock. */
  var fallingLeaves = 0;
  (function () {
    if (!leafSpawns.length) return;
    var n = hiTier ? 260 : 130;
    var pos = new Float32Array(n * 3), seed = new Float32Array(n), hh = new Float32Array(n);
    for (var i = 0; i < n; i++) {
      var sp = leafSpawns[Math.floor(R() * leafSpawns.length)], a = R() * 6.28, rr = R() * R() * 3.4;
      pos[i * 3] = sp.x + Math.cos(a) * rr; pos[i * 3 + 1] = sp.y; pos[i * 3 + 2] = sp.z + Math.sin(a) * rr;
      seed[i] = R(); hh[i] = 4 + R() * 5;
    }
    var g = new T.BufferGeometry();
    g.setAttribute('position', new T.BufferAttribute(pos, 3));
    g.setAttribute('aSeed', new T.BufferAttribute(seed, 1));
    g.setAttribute('aH', new T.BufferAttribute(hh, 1));
    var m = new T.PointsMaterial({ color: 0x86b04c, size: 0.34, sizeAttenuation: true, transparent: t