<!-- SOLID CITY · part 2465 -->
('taken by the Twin Lightnings', W / 2, 230);
    // twin bolts divider
    g.fillStyle = '#c99a3a'; g.fillRect(120, 290, 300, 5); g.fillRect(W - 420, 290, 300, 5);
    g.fillStyle = '#ff2a40';
    [[W / 2 - 30, 1], [W / 2 + 30, -1]].forEach(function (b) { const x = b[0], s = b[1]; g.beginPath(); g.moveTo(x - 10 * s, 262); g.lineTo(x + 14 * s, 262); g.lineTo(x + 2 * s, 288); g.lineTo(x + 16 * s, 288); g.lineTo(x - 12 * s, 322); g.lineTo(x - 2 * s, 296); g.lineTo(x - 16 * s, 296); g.closePath(); g.fill(); });
    // nine names, two columns, engraved gold
    g.fillStyle = '#e8c36a';
    for (let i = 0; i < 9; i++) {
      const col = i < 5 ? 0 : 1, row = col ? i - 5 : i, x = col ? W * 0.73 : W * 0.29, y = 395 + row * 98 + (col ? 49 : 0);
      g.font = '700 68px ' + SERIF; fitFont(g, FALLEN_WORLDS[i].toUpperCase(), '700', 68, SERIF, W * 0.42);
      g.fillStyle = '#3a2a10'; g.fillText(FALLEN_WORLDS[i].toUpperCase(), x + 3, y + 4);   // engraved shadow
      g.fillStyle = '#e8c36a'; g.fillText(FALLEN_WORLDS[i].toUpperCase(), x, y);
    }
    g.fillStyle = '#c99a3a'; g.fillRect(W / 2 - 2, 380, 4, 470);
    g.fillRect(120, 878, W - 240, 4);
    g.fillStyle = '#b8a07a'; fitFont(g, 'RAISED BY ORDER OF THE KING · 9 OCTOBER 2026', '600', 34, 'system-ui, sans-serif', W - 160);
    g.fillText('RAISED BY ORDER OF THE KING · 9 OCTOBER 2026', W / 2, 930);
  });
}
/* one atlas: 10 cells of 512x102 — 9 names + the obelisk title; plain dark row at the bottom for edges */
function namesTexture(THREE) {
  return canvasTex(THREE, 512, 1024, function (g, W, H) {
    g.fillStyle = '#0b080c'; g.fillRect(0, 0, W, H);
    g.textAlign = 'center'; g.textBaseline = 'middle';
    const labels = FALLEN_WORLDS.map(function (n) { return n.toUpperCase(); }).concat(['THE FALLEN WORLDS']);
    for (let i = 0; i < 10; i++) {
      const y0 = i * 100;
      g.strokeStyle = '#c99a3a'; g.lineWidth = 6; g.strokeRect(6, y0 + 6, W - 12, 88);
      fitFont(g, labels[i], '700', 56, SERIF, W - 50);
      g.fillStyle = '#3a2a10'; g.fillText(labels[i], W / 2 + 2, y0 + 53);
      g.fillStyle = '#e8c36a'; g.fillText(labels[i], W / 2, y0 + 50);
    }
  });
}

/* ---- tiny geometry merger (no BufferGeometryUtils needed in r128 global build) ---- */
function Merger(THREE) {
  this.T = THREE; this.parts = []; this.pieces = [];
}
Merger.prototype.add = function (name, geo, x, y, z, rx, ry, rz) {
  const T = this.T;
  geo.computeBoundingBox();
  const s = new T.Vector3(); geo.boundingBox.getSize(s);
  this.pieces.push({ name: name, dims: [+s.x.toFixed(3), +s.y.toFixed(3), +s.z.toFixed(3)], min: +Math.min(s.x, s.y, s.z).toFixed(3) });
  const g = geo.index ? geo.toNonIndexed() : geo;
  const m = new T.Matrix4().compose(new T.Vector3(x || 0, y || 0, z || 0), new T.Quaternion().setFromEuler(new T.Euler(rx || 0, ry || 0, rz || 0)), new T.Vector3(1, 1, 1));
  g.applyMatrix4(m);
  this.parts.push(g);
  return this;
};
Merger.prototype.build = function () {
  const T = this.T; let n = 0;
  this.parts.forEach(function (p) { n += p.attributes.position.count; });
  const pos = new Float32Array(n * 3), nor = new Float32Array(n * 3), uv = new Float32Array(n * 2); let o = 0;
  this.parts.forEach(function (p) {
    const c = p.attributes.position.count;
    pos.set(p.attributes.position.array, o * 3); nor.set(p.attributes.normal.array, o * 3);
    if (p.attributes.uv) uv.set(p.attributes.uv.array, o * 2);
    o += c; p.dispose();
  });
  const g = new T.BufferGeometry();
  g.setAttribute('position', new T.BufferAttribute(pos, 3)); g.setAttribute('normal', new T.BufferAttribute(nor, 3)); g.setAttribute('uv', new T.BufferAttribute(uv, 2));
  g.computeBoundingSphere(); g.computeBoundingBox();
  return g;
};
/* box whose +Z face shows one atlas cell (rows of 100px in a 1024px atlas), other faces a dark strip */
function plateBox(T, w, h, d, cell) {
  const g = new T.BoxGeometry(w, h, d), uv = g.attributes.uv;
  const v0 = 1 - (cell * 100 + 100) / 1024, v1 = 1 - (cell * 100) / 1024, dark = 1 - 1012 / 1024;
  for (let i = 0; i < uv.count; i++) {
    const face = Math.floor(i / 4);           // BoxGeometry: +x,-x,+y,-y,+z,-z, 4 verts each
    if (face === 4) uv.setXY(i, uv.getX(i), v0 + uv.getY(i) * (v1 - v0));
    else uv.setXY(i, 0.5, dark);
  }
  return g;
}
/* front-only texture for the stele (other faces take a dark border pixel) */
function frontOnlyBox(T, w, h, d) {
  const g = new T.BoxGeometry(w, h, d), uv = g.attributes.uv;
  for (let i = 0; i < uv.count; i++) if (Math.floor(i / 4) !== 4) uv.setXY(i, 0.005, 0.005);
  return g;
}
function boltGeometry(T, mirror, H, W, depth) {
  // classic bolt, tip at the bottom (flat 0.6 m end so nothing is thinner than THE LAW)
  let P = [[0.30, 1.0], [0.75, 1.0], [0.52, 0.60], [0.78, 0.60], [0.21, 0.0], [0.15, 0.0], [0.34, 0.44], [0.08, 0.44]];
  P = P.map(function (p) { return [(p[0] - 0.18) * W * (mirror ? -1 : 1), p[1] * H]; });
  if (mirror) P.reverse();
  const s = new T.Shape(); s.moveTo(P[0][0],