<!-- SOLID CITY · part 68 -->
.id, kind: 'inner', pts: p && p.length > 1 ? p.map(function (q) { return { x: q.x, z: q.z }; }) : [{ x: a.x, z: a.z }, { x: c.gate.x, z: c.gate.z }] });
    ROADS.push({ id: c.id, kind: 'radial', pts: [c.gate, c.radial[1]] });
  });
  for (var i = 1; i < CAMPUS.length; i++) {   // the University Ring, pad edge to pad edge
    var a0 = CAMPUS[i - 1].deg + (CM.PAD - 6) / CM.RING * 180 / Math.PI, a1 = CAMPUS[i].deg - (CM.PAD - 6) / CM.RING * 180 / Math.PI, pts = [];
    for (var d = a0; d <= a1 + 0.01; d += Math.min(1.2, (a1 - a0) / 2)) pts.push({ x: Math.cos(d * Math.PI / 180) * CM.RING, z: Math.sin(d * Math.PI / 180) * CM.RING });
    ROADS.push({ id: CAMPUS[i - 1].id + '-' + CAMPUS[i].id, kind: 'ring', pts: pts });
  }
}
function buildWorld() {
  var M = mats(), asp = new Merger(), gold = new Merger(), red = new Merger(), dark = new Merger(), glow = new Merger(), sign = new Merger(), proxy = new Merger();
  var postG = new T.BoxGeometry(0.28, 7, 0.28), armG = new T.BoxGeometry(2.2, 0.18, 0.18), headG = new T.BoxGeometry(1.2, 0.4, 0.8), signPostG = new T.BoxGeometry(0.3, 4.2, 0.3);
  ROADS.forEach(function (r) {
    var p = densify(r.pts, 14), nr = normals(p), yA = Y + (r.kind === 'inner' ? 0.14 : 0.12);
    strip(asp, p, nr, 0, 5, yA);
    strip(gold, p, nr, -4.4, 0.3, yA + 0.04); strip(gold, p, nr, 4.4, 0.3, yA + 0.04);
    dashes(red, p, 0.25, yA + 0.04);
    // lamps every ~36 m, alternating sides
    var acc = 18, side = 1;
    for (var i = 1; i < p.length; i++) {
      var a = p[i - 1], b = p[i], d = Math.hypot(b.x - a.x, b.z - a.z);
      while (acc <= d) {
        var t = acc / d, x = a.x + (b.x - a.x) * t, z = a.z + (b.z - a.z) * t, n = nr[i], lx = x + n.x * 6.4 * side, lz = z + n.z * 6.4 * side, ry = Math.atan2(n.x * side, n.z * side) - Math.PI / 2;
        var inCity = Math.hypot(lx, lz) < 790, blocked = inCity && A.solids.some(function (s) { return lx > s.x0 - 0.6 && lx < s.x1 + 0.6 && lz > s.z0 - 0.6 && lz < s.z1 + 0.6; });
        if (!blocked) {
          dark.add(postG, lx, Y + 3.5, lz);
          dark.add(armG, lx - n.x * side * 1.0, Y + 6.9, lz - n.z * side * 1.0, 0, ry, 0);
          glow.add(headG, lx - n.x * side * 2.0, Y + 6.75, lz - n.z * side * 2.0, 0, ry, 0);
        }
        side = -side; acc += 36;
      }
      acc -= d;
    }
  });
  // campus pads: a paved plaza with a gold ring, and the University arch over the ring road
  CAMPUS.forEach(function (c) {
    asp.add(new T.CircleGeometry(CM.PAD - 2, 40), c.x, Y + 0.1, c.z, -Math.PI / 2, 0, 0);
    gold.add(new T.RingGeometry(CM.PAD - 3.2, CM.PAD - 2.2, 48), c.x, Y + 0.18, c.z, -Math.PI / 2, 0, 0);
    gold.add(new T.RingGeometry(30, 30.8, 40), c.x, Y + 0.18, c.z, -Math.PI / 2, 0, 0);
  });
  var hubN = { x: Math.cos(5 * Math.PI / 180), z: Math.sin(5 * Math.PI / 180) }, hubRy = Math.atan2(hubN.x, hubN.z);
  [-8.5, 8.5].forEach(function (o) { dark.add(new T.BoxGeometry(2.2, 14, 2.2), HUB.x + hubN.x * o, Y + 7, HUB.z + hubN.z * o); gold.add(new T.BoxGeometry(2.6, 0.6, 2.6), HUB.x + hubN.x * o, Y + 14.2, HUB.z + hubN.z * o); });
  gold.add(new T.BoxGeometry(1.2, 1.4, 20), HUB.x, Y + 13.4, HUB.z, 0, hubRy + Math.PI, 0);
  signBoard(sign, atlasCell('SOLID CITY UNIVERSITY', 'The new learning space for humans and AI'), HUB.x, HUB.z, hubRy + Math.PI / 2, 15, 3.4, Y + 9.6);
  // sign posts: at each district (road start), at the city gate, and at each campus forecourt
  CAMPUS.forEach(function (c) {
    var inner = ROADS.filter(function (r) { return r.id === c.id && r.kind === 'inner'; })[0], len = 0;
    for (var i = 1; i < inner.pts.length; i++) len += Math.hypot(inner.pts[i].x - inner.pts[i - 1].x, inner.pts[i].z - inner.pts[i - 1].z);
    var total = len + Math.hypot(c.radial[1].x - c.gate.x, c.radial[1].z - c.gate.z) + CM.PAD;
    function post(px, pz, dirx, dirz, l1, l2) {   // board faces the walker coming along -dir; offset to the road's right side
      var l = Math.hypot(dirx, dirz) || 1, ux = dirx / l, uz = dirz / l, ox = px - uz * 7.2, oz = pz + ux * 7.2;
      if (Math.hypot(ox, oz) < 790 && A.solids.some(function (s) { return ox > s.x0 - 1 && ox < s.x1 + 1 && oz > s.z0 - 1 && oz < s.z1 + 1; })) { ox = px + uz * 7.2; oz = pz - ux * 7.2; }
      dark.add(signPostG, ox, Y + 2.1, oz); signBoard(sign, atlasCell(l1, l2, '\u2192'), ox, oz, Math.atan2(-ux, -uz) + Math.PI / 2 + Math.PI / 2, 5.6, 1.7, Y + 3.0);
      A.solids.push({ x0: ox - 0.4, x1: ox + 0.4, z0: oz - 0.4, z1: oz + 0.4, y0: Y, y1: Y + 4.5, campus: true });
    }
    var p0 = inner.pts[0], p1 = inner.pts[Math.min(1, inner.pts.length - 1)];
    post(p0.x, p0.z, p1.x - p0.x, p1.z - p0.z, c.short + ' CAMPUS', 'University road \u00b7 ' + Math.round(total / 10) * 10 + ' m');
    post(c.gate.x, c.gate.z, c.radial[1].x - c.gate.x, c.radial[1].z - c.gate.z, c.short + ' CAMPUS', 'Solid City University \u00b7 ' + Math.round((total - len) / 10) * 10 + ' m');
    // far proxy (inside the detailed hall's main mass, so it hides once the hall is built)