<!-- PLAYA 2.0'S WORLD · part 8 -->
nument: 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: playa-world/ring-portal.js =====
// ring-portal.js — Playa 2.0 original: giant upright ring portal with counter-rotating inner rings. fn(THREE, scene, opts) -> Group
import { mkit, pmats, placeLocal } from './mkit.js';
export function createRingPortal(THREE, scene, opts = {}) {
  const g = new THREE.Group(); g.name = 'RingPortal'; const M = pmats(THREE, opts), k = mkit(THREE, g);
  const R = opts.radius ?? 5.5, C = R + 1.2;
  k.torus(M.black, R, 0.6, 0, C, 0, 0, 0, 0); k.torus(M.red, R + 0.75, 0.16, 0, C, 0, 0, 0, 0);
  for (const s of [-1, 1]) { k.box(M.black, 1.4, 1.6, 2.4, s * 1.6, 0.8, 0); k.box(M.red, 1.5, 0.3, 2.5, s * 1.6, 1.65, 0); }
  k.box(M.stone, 2 * R + 3, 0.14, 4, 0, 0.07, 0);
  k.build();
  const inner = new THREE.Group(), ki = mkit(THREE, inner);
  ki.torus(M.glow, R - 0.9, 0.12, 0, 0, 0, 0, 0, 0); ki.torus(M.glow, R - 1.6, 0.1, 0, 0, 0, 0, Math.PI / 2, 0);
  ki.build(); inner.position.y = C; g.add(inner); g.userData.minDim = Math.min(g.userData.minDim, inner.userData.minDim);
  g.userData.tick = t => { inner.rotation.y = t * 0.6; inner.rotation.z = t * 0.25; };
  return placeLocal(scene, g, opts, [[-2.3, -0.9, -1.2, 1.2, 1.8], [0.9, 2.3, -1.2, 1.2, 1.8], [-R - 0.6, -R + 0.6, -0.6, 0.6, C + 1, 1.6], [R - 0.6, R + 0.6, -0.6, 0.6, C + 1, 1.6]]);
}
export default { createRingPortal };


===== FILE: playa-world/spiral-tower.js =====
// spiral-tower.js — Playa 2.0 original: walkable spiral ramp tower (steps around a glowing core). fn(THREE, scene, opts) -> Group
import { mkit, pmats, placeLocal } from './mkit.js';
export function createSpiralTower(THREE, scene, opts = {}) {
  const g = new THREE.Group(); g.name = 'SpiralTower'; const M = pmats(THREE, opts), k = mkit(THREE, g);
  const N = opts.steps ?? 36, rise = 0.3, Rc = 1.4, Ro = 4.2, turns = 1.5;
  k.cyl(M.black, Rc, Rc, N * rise + 3, 12, 0, (N * rise + 3) / 2, 0);
  k.cyl(M.glow, Rc + 0.08, Rc + 0.08, 0.2, 16, 0, N * rise + 3.1, 0);
  k.cone(M.red, Rc + 0.2, 3, 12, 0, N * rise + 4.7, 0);
  k.cyl(M.stone, Ro + 0.6, Ro + 0.8, 0.14, 24, 0, 0.07, 0);
  const cols = [[-Rc, Rc, -Rc, Rc, N * rise + 6]];
  for (let i = 0; i < N; i++) {
    const a = i / N * turns * Math.PI * 2, r = (Rc + Ro) / 2, y = (i + 1) * rise, x = Math.cos(a) * r, z = Math.sin(a) * r;
    k.box(i % 2 ? M.black : M.red, Ro - Rc, 0.3, 0.9, x, y - 0.15, z, 0, -a, 0);
    k.box(M.glow, 0.14, 0.14, 0.9, Math.cos(a) * (Ro - 0.07), y + 0.07, Math.sin(a) * (Ro - 0.07), 0, -a, 0);
    const h = 0.5; cols.push([x - h, x + h, z - h, z + h, y, y - 0.3]); // step footprint (stand-on)
  }
  k.build();
  return placeLocal(scene, g, opts, cols);
}
export default { createSpiralTower };


===== FILE: playa-world/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('dimension below 0.13: ' + v); group.userData.minDim = Math.min(group.userData.minDim, v); return v; };
  const add = (mat, geo, x = 0, y = 0, z = 0, rx = 0, ry = 0, rz = 0) => {
    const m = new THREE.Matrix4().compose(new THREE.Vector3(x, y, z),
      new THREE.Quaternion().setFromEuler(new THREE.Euler(rx, ry, rz)), new THREE.Vector3(1, 1, 1));
    const g = (geo.index ? geo.toNonIndexed() : g