<!-- SOLID CITY · part 2466 -->
 P[0][1]); for (let i = 1; i < P.length; i++) s.lineTo(P[i][0], P[i][1]); s.closePath();
  const g = new T.ExtrudeGeometry(s, { depth: depth, bevelEnabled: false, curveSegments: 1 });
  g.translate(0, 0, -depth / 2);
  return g;
}

export function createFallenWorldsMonument(opts) {
  opts = opts || {};
  const T = opts.THREE || (typeof window !== 'undefined' && window.THREE);
  if (!T) throw new Error('createFallenWorldsMonument: pass { THREE }');
  const curve = typeof opts.curve === 'function' ? opts.curve : function (m) { return m; };
  const pos = opts.position || { x: 0, y: 0, z: 0 };

  const black = new Merger(T), stone = new Merger(T), gold = new Merger(T), red = new Merger(T), names = new Merger(T), stele = new Merger(T);

  // ---- paving + gold rim ----
  stone.add('paving', new T.CylinderGeometry(18, 18, 0.2, 24), 0, 0.1, 0);
  gold.add('paving rim', new T.CylinderGeometry(18.4, 18.4, 0.16, 24), 0, 0.08, 0);
  // ---- stepped plinth (three tiers, gold trims) ----
  const tiers = [[18, 1.0], [14, 1.0], [10, 1.2]]; let y = 0.2;
  tiers.forEach(function (t, i) {
    black.add('tier ' + (i + 1), new T.BoxGeometry(t[0], t[1], t[0]), 0, y + t[1] / 2, 0);
    gold.add('tier ' + (i + 1) + ' trim', new T.BoxGeometry(t[0] + 0.3, 0.25, t[0] + 0.3), 0, y + t[1] - 0.2, 0);
    y += t[1];
  });
  const TOP = y; // 3.4
  // ---- the obelisk (square, tapered) + gold pyramidion ----
  const OB_H = 64, rB = 3.4, rT = 1.6;
  black.add('obelisk', new T.CylinderGeometry(rT, rB, OB_H, 4, 1), 0, TOP + OB_H / 2, 0, 0, Math.PI / 4, 0);
  gold.add('pyramidion', new T.ConeGeometry(rT * 1.02, 6, 4, 1), 0, TOP + OB_H + 3, 0, 0, Math.PI / 4, 0);
  // red bands (slightly proud of the faces, tapered faces → tiny tilt ignored at this thickness)
  [0.18, 0.55, 0.86].forEach(function (f, i) {
    const r = rB + (rT - rB) * f, side = r * Math.SQRT2 + 0.3;
    red.add('obelisk band ' + (i + 1), new T.BoxGeometry(side, 0.5, side), 0, TOP + OB_H * f, 0);
  });
  // title plate on the obelisk front (atlas cell 9)
  const ty = TOP + 9, tr = rB + (rT - rB) * (9 / OB_H), half = tr / Math.SQRT2, slope = Math.atan((rB - rT) / Math.SQRT2 / OB_H);
  names.add('obelisk title plate', plateBox(T, half * 2 - 0.3, (half * 2 - 0.3) / 5.12, 0.25, 9), 0, ty, half + 0.1, slope, 0, 0);
  // ---- the Twin Lightnings: two bolts striking the plinth either side of the obelisk ----
  const BH = 50, BW = 11, BD = 1.0;
  red.add('lightning (right)', boltGeometry(T, false, BH, BW, BD), 3.0, TOP, 3.0, 0, 0, -0.06);
  red.add('lightning (left)', boltGeometry(T, true, BH, BW, BD), -3.0, TOP, 3.0, 0, 0, 0.06);
  // the strikes: gold-glow scorch where each bolt lands
  red.add('strike (right)', new T.CylinderGeometry(0.9, 1.1, 0.2, 8), 3.0, TOP + 0.1, 3.0);
  red.add('strike (left)', new T.CylinderGeometry(0.9, 1.1, 0.2, 8), -3.0, TOP + 0.1, 3.0);
  // ---- the broken gate + stele at the front ----
  const GZ = 12.6;
  black.add('gate pillar (left)', new T.BoxGeometry(1.8, 11, 1.8), -5.2, 0.2 + 5.5, GZ);
  black.add('gate lintel stub (broken)', new T.BoxGeometry(4.6, 1.4, 2.0), -3.2, 0.2 + 11.7, GZ, 0, 0, -0.05);
  gold.add('gate capital (left)', new T.BoxGeometry(2.2, 0.35, 2.2), -5.2, 0.2 + 10.6, GZ);
  black.add('gate pillar (right, broken)', new T.BoxGeometry(1.8, 7.2, 1.8), 5.2, 0.2 + 3.6, GZ);
  black.add('gate pillar shard', new T.BoxGeometry(1.6, 1.2, 1.6), 5.0, 0.2 + 7.6, GZ, 0.2, 0.3, 0.45);
  black.add('fallen lintel', new T.BoxGeometry(4.4, 1.3, 1.9), 6.0, 0.2 + 0.65, GZ + 3.8, 0, 0.9, 0);
  black.add('stele base', new T.BoxGeometry(8.2, 0.6, 1.6), 0, 0.2 + 0.3, GZ);
  gold.add('stele frame', new T.BoxGeometry(7.8, 7.0, 0.5), 0, 0.8 + 3.5, GZ - 0.05);
  stele.add('stele face', frontOnlyBox(T, 7.4, 6.6, 0.6), 0, 0.8 + 3.5, GZ);   // 1.0 m–7.4 m: readable on foot
  // ---- nine broken world-stones in a ring, each with its name in gold (gap at the front for the gate) ----
  const heights = [2.6, 3.2, 2.3, 3.4, 2.8, 2.4, 3.1, 2.7, 2.5];
  for (let i = 0; i < 9; i++) {
    const a = (i + 1) * (Math.PI * 2 / 10), R = 15.4, sx = Math.sin(a) * R, sz = Math.cos(a) * R, h = heights[i];
    black.add('world-stone ' + FALLEN_WORLDS[i], new T.BoxGeometry(2.2, h, 1.5), sx, 0.2 + h / 2, sz, 0, a, 0);
    black.add('world-stone break ' + FALLEN_WORLDS[i], new T.BoxGeometry(1.4, 0.7, 1.3), sx - Math.cos(a) * 0.35, 0.2 + h + 0.2, sz + Math.sin(a) * 0.35, 0, a, (i % 2 ? 0.35 : -0.35));
    const px = sx + Math.sin(a) * 0.83, pz = sz + Math.cos(a) * 0.83;
    names.add('name plate ' + FALLEN_WORLDS[i], plateBox(T, 2.0, 0.42, 0.16, i), px, 0.2 + 1.45, pz, 0, a, 0);   // eye level
    gold.add('stone foot ' + FALLEN_WORLDS[i], new T.BoxGeometry(2.5, 0.2, 1.8), sx, 0.2 + 0.1, sz, 0, a, 0);
  }

  // ---- materials (Lambert like the city; signs Basic so they read at night) ----
  const steleTex = steleTexture(T), namesTex = namesTexture(T);
  const mats = {
    black: curve(new T.MeshLambertMaterial({ color: C.black })),
    stone: curve(new T