<!-- NEW8 — EMBER CITY STRUCTURES · part 5 -->
max(hx, hz) <= I.band.hw;
  if (!chosen && fits) for (let n = 0; n < cands.length; n++) { const o = cands[(pref + n) % cands.length]; if (spotFree(ctx, I, o.r, kind === 'bridge')) { chosen = o; break; } }
  if (!chosen) throw new Error('no open spot for ' + kind + ' footprint ' + hx + 'x' + hz);
  (ctx.__new8Claims ||= []).push(chosen.r); return chosen;
}
// place group: with ctx -> open spot + ctx.groundY + colliders; without ctx -> opts.position as before.
function place(scene, group, opts, foot) {
  const ctx = opts.ctx, s = opts.scale ?? 1; if (s < MIN) throw new Error('scale below 0.13'); group.scale.setScalar(s);
  if (ctx && ctx.layout) {
    const sp = pickSpot(ctx, opts, foot.kind, foot.hx * s, foot.hz * s, opts.spot ?? foot.pref ?? 0);
    const gy = typeof ctx.groundY === 'function' ? ctx.groundY(sp.x, sp.z) : 0;
    group.position.set(sp.x, gy, sp.z); group.rotation.y = sp.ry;
  } else {
    const p = opts.position || [0, 0, 0]; group.position.set(p[0], p[1], p[2]); group.rotation.y = opts.rotationY || 0;
  }
  scene.add(group);
  const cols = [];
  if (ctx && typeof ctx.addCollider === 'function') {
    scene.updateMatrixWorld(true); const m = group.matrixWorld, THREE_V = group.position.constructor;
    for (const [x0, x1, z0, z1, h] of foot.cols || []) {
      let a = Infinity, b = -Infinity, c = Infinity, d = -Infinity;
      for (const [x, z] of [[x0, z0], [x1, z0], [x0, z1], [x1, z1]]) { const v = new THREE_V(x, 0, z).applyMatrix4(m); a = Math.min(a, v.x); b = Math.max(b, v.x); c = Math.min(c, v.z); d = Math.max(d, v.z); }
      const top = group.position.y + h * s; ctx.addCollider(a, b, c, d, top); cols.push([a, b, c, d, top]);
    }
  }
  group.userData.colliders = cols; group.userData.footprint = foot.kind; return group;
}

export function createCentralPlaza(THREE, scene, opts = {}) {
  const g = new THREE.Group(); g.name = 'CentralPlaza';
  const M = mats(THREE, opts), k = kit(THREE, g);
  // with ctx the plaza goes to an open spot in the outer ring (never the Grand Plaza at 0,0), so it is sized to fit
  const fitR = opts.ctx && opts.ctx.layout ? layoutInfo(opts.ctx.layout).band.hw - 0.5 : Infinity;
  const R = Math.min(opts.radius ?? 18, fitR), f = Math.min(1, R / 18), q = v => Math.max(0.14, v * f), T = 0.14;
  const water = new THREE.MeshStandardMaterial({ color: 0x1a0306, emissive: 0x5a0010, emissiveIntensity: 0.6, metalness: 0.9, roughness: 0.1, transparent: true, opacity: 0.85 });
  k.cyl(M.stone, R, R + 0.3, 0.3, 48, 0, 0.15, 0);
  for (let i = 0; i < 3; i++) k.torus(M.glow, R * (0.45 + i * 0.2), 0.07, 0, 0.32, 0);
  for (let i = 0; i < 16; i++) { const a = i / 16 * Math.PI * 2; k.box(i % 2 ? M.black : M.red, R * 0.5, T, Math.max(T, 0.3 * f), Math.cos(a) * R * 0.7, 0.33, Math.sin(a) * R * 0.7, 0, -a, 0); }
  // fountain basin
  const B = 6.2 * f;
  k.cyl(M.black, 6 * f, B, 0.7, 40, 0, 0.65, 0);
  k.torus(M.red, 6.05 * f, Math.max(0.075, 0.15 * f), 0, 1.0, 0);
  k.add(water, new THREE.CylinderGeometry(k.d(5.6 * f * 2) / 2, 5.6 * f, k.d(0.14), 40), 0, 0.95, 0);
  // monument: tiered pedestal + twisted red prism + glowing ring
  k.cyl(M.black, 2.2 * f, 2.6 * f, 1.0, 8, 0, 1.4, 0);
  k.cyl(M.graphite, 1.6 * f, 2.0 * f, 0.8, 8, 0, 2.3, 0);
  for (let i = 0; i < 10; i++) k.box(i % 3 === 2 ? M.glow : M.red, q(1.4 - i * 0.08), 0.6, q(1.4 - i * 0.08), 0, 3.0 + i * 0.6, 0, 0, i * 0.18, 0);
  k.cyl(M.black, 0.07, Math.max(0.07, 0.5 * f), 2.5, 8, 0, 10.0, 0);
  k.torus(M.glow, 2.4 * f, Math.max(0.07, 0.1 * f), 0, 7.5, 0, Math.PI / 2 - 0.3, 0, 0.2);
  // jets (solid emissive columns, no particles)
  for (let i = 0; i < 8; i++) { const a = i / 8 * Math.PI * 2; k.cyl(M.glow, 0.07, Math.max(0.07, 0.12 * f), 1.6, 6, Math.cos(a) * 4 * f, 1.8, Math.sin(a) * 4 * f); }
  // benches + lamp posts on perimeter (between basin and rim)
  const br = Math.max(B + 1, R - 2 * f), lr = Math.max(B + 1.6, R - 0.8 * f);
  for (let i = 0; i < 8; i++) {
    const a = i / 8 * Math.PI * 2 + Math.PI / 8;
    k.box(M.black, Math.max(0.6, 2.2 * f), 0.45, 0.6, Math.cos(a) * br, 0.52, Math.sin(a) * br, 0, -a + Math.PI / 2, 0);
    const la = a + Math.PI / 8;
    k.cyl(M.black, 0.08, 0.1, 3.2, 6, Math.cos(la) * lr, 1.9, Math.sin(la) * lr);
    k.cyl(M.glow, 0.2, 0.2, 0.4, 8, Math.cos(la) * lr, 3.6, Math.sin(la) * lr);
  }
  const mon = 2.6 * f;
  k.build(); return place(scene, g, opts, { kind: 'land', hx: R + 0.3, hz: R + 0.3, pref: 18, cols: [[-B, B, -B, B, 1.0], [-mon, mon, -mon, mon, 11.25]] });
}
export default { createCentralPlaza };


===== FILE: public/new8/structures-20.js =====
// structures-20.js — landmark towers (red/black). Usage: fn(THREE, sceneOrGroup, opts) -> THREE.Group; opts.ctx (new8 core) => open-spot placement, groundY, colliders
// ---- shared inline helpers (standalone module, no imports) ----
const MIN = 0.13;
function kit(THREE, group) {
  group.userData.minDim = Infinity;
  const buckets = new Map();
  const d = (v) => { if (v < MIN) throw new Error(