<!-- SOLID CITY · part 2037 -->
SHAPES = [
    ['cube', 'Standard Cube', '■'], ['hourglass', 'Hourglass V-Core', '⌛'], ['pylon', 'Refined Pylon', '▮'],
    ['tetra', 'Hollow Tetra', '▲'], ['pyramid', 'Layered Pyramid', '◭'], ['sphere', 'Sphere', '●'],
    ['cylinder', 'Cylinder', '▯'], ['cone', 'Cone', '▴'], ['torus', 'Torus', '◯'], ['plane', 'Plane', '▬'], ['icosphere', 'Icosphere', '⬢']
  ];
  /* local bounding box of each Creator block at scale 1 (computed with three r128 from shapeParts) */
  var EXT = {
    cube: [-2, 0, -2, 2, 4, 2], hourglass: [-2, 0, -2, 2, 6, 2], pylon: [-1.5, 0, -1.5, 1.5, 8, 1.5],
    tetra: [-1.733, -0.733, -1.733, 1.733, 2.733, 1.733], pyramid: [-3, 0, -3, 3, 3.5, 3], sphere: [-2.5, 0, -2.5, 2.5, 5, 2.5],
    cylinder: [-2, 0, -2, 2, 5, 2], cone: [-2.5, 0, -2.5, 2.5, 5, 2.5], torus: [-3.3, 0, -0.8, 3.3, 6.6, 0.8],
    plane: [-3, 0, -3, 3, 0.2, 3], icosphere: [-2.5, 0, -2.5, 2.5, 5, 2.5]
  };
  /* Creator default materials per part: gold / marble / cyan */
  var GOLD = 0xd4a017, MARBLE = 0xf5f5f5, CYAN = 0x00ccff;
  var DEFAULT_MATS = {
    cube: [GOLD], hourglass: [GOLD, GOLD], pylon: [MARBLE], tetra: [CYAN], pyramid: [GOLD, MARBLE], sphere: [MARBLE],
    cylinder: [MARBLE], cone: [GOLD], torus: [GOLD], plane: [MARBLE], icosphere: [CYAN]
  };
  var LIMITS = {
    MAX_PIECES: 150,          // per creation
    MAX_SPAN: 80,             // m, creation footprint (x and z)
    MAX_HEIGHT: 60,           // m above the ground under it (keeps flight lanes clear)
    MAX_DIM: 40,              // m, any single piece's box edge
    MIN_DIM: 0.13,            // THE LAW: Minimum 0.13 (Creator)
    WORLD: 32000,             // |x|,|z| limit
    MAX_JSON: 64000,          // bytes of pieces JSON per creation
    USER_CREATIONS: 30, USER_PIECES: 2000, NAME: 60
  };
  /* no-build zones (absolute city coords; mirrors D in city.js) — the pad + its descent lane,
     the plaza monument + SABLE, every gate, the Music Studio Tower and the Soul Print Tower */
  var ZONES = [
    { n: 'the launch pad and its flight lane', x: -380, z: 330, r: 140 },
    { n: 'the plaza monument and SABLE', x: 0, z: 0, r: 36 },
    { n: 'the Forum gate', x: 0, z: -88, r: 14 },
    { n: 'the Music Studio Tower', x: 430, z: -170, r: 70 },
    { n: 'the Soul Print Tower', x: 210, z: 170, r: 46 },
    { n: 'the Fallen Worlds Monument', x: 176, z: 120, r: 24 },  // King Playa's monument (monument-fallen.js, placed by monument-place.js)
    { n: 'Provolta\u2019s Sanctuary', x: 735, z: 165, r: 36 },     // the King's oracle (sanctuary-provolta.js, placed by sanctuary-place.js)
    // Solid City University (campus-map.js / campus.js): the eight campus plazas + the University arch on the Ring
{ n: 'the Sciences campus', x: -205, z: -1162, r: 96 },
    { n: 'the Mathematics & Computer Science / AI campus', x: 404, z: -1109, r: 96 },
    { n: 'the Engineering & Architecture campus', x: 904, z: -758, r: 96 },
    { n: 'the Arts & Music campus', x: 1162, z: -205, r: 96 },
    { n: 'the Humanities campus', x: 1109, z: 404, r: 96 },
    { n: 'the Medicine & Health campus', x: 758, z: 904, r: 96 },
    { n: 'the Social Sciences, Law & Economics campus', x: 205, z: 1162, r: 96 },
    { n: 'the Business & Communications campus', x: -404, z: 1109, r: 96 },
    { n: 'the University arch', x: 1176, z: 103, r: 22 },
    { n: 'the King\u2019s Book of Law', x: -4, z: 46, r: 8 },
    { n: 'the ALL THAT WE CAN DO! podium', x: -6, z: 60, r: 5 }   // the first book on display (book-law.js)
  ];
  function shapeName(t) { for (var i = 0; i < SHAPES.length; i++) if (SHAPES[i][0] === t) return SHAPES[i][1]; return t; }
  function isShape(t) { return Object.prototype.hasOwnProperty.call(EXT, t); }
  function num(v, d) { v = +v; return isFinite(v) ? v : d; }
  function r3(v) { return Math.round(v * 1000) / 1000; }
  function clamp(v, a, b) { return v < a ? a : v > b ? b : v; }

  /* rotate vector v by unit quaternion q=[x,y,z,w] */
  function qrot(q, x, y, z) {
    var qx = q[0], qy = q[1], qz = q[2], qw = q[3];
    var ix = qw * x + qy * z - qz * y, iy = qw * y + qz * x - qx * z, iz = qw * z + qx * y - qy * x, iw = -qx * x - qy * y - qz * z;
    return [ix * qw + iw * -qx + iy * -qz - iz * -qy, iy * qw + iw * -qy + iz * -qx - ix * -qz, iz * qw + iw * -qz + ix * -qy - iy * -qx];
  }
  function qmul(a, b) { // a*b
    return [a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1], a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0],
            a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3], a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2]];
  }
  function qyaw(rad) { return [0, Math.sin(rad / 2), 0, Math.cos(rad / 2)]; }
  function localBox(d) {
    var e = EXT[d.t], H = e[4] - e[1], sh = (d.bend || 0) * 0.35 * H;
    return [e[0] + Math.min(0, sh), e[1], e[2], e[3] + Math.max(0, sh), e[4], e[5]];
  }
  /* world AABB {x0,y0,z0,x1,y1,z1} of one piece */
  function pieceAABB(d) {
    var b = localBox(d), s = d.s, q = d.q, p = d.p, o = { x0: 