<!-- SOLID CITY · part 3147 -->
T.SphereGeometry(r, 10, 7), m, color); }
  function cone(b, r, h, s, m, color) { b.add(new T.ConeGeometry(r, h, s), m, color); }

  /* red and black marble — one canvas, veined by hand */
  function marbleTex(redBase) {
    var cv = document.createElement('canvas'); cv.width = 256; cv.height = 256;
    var g = cv.getContext('2d');
    g.fillStyle = redBase ? '#57101b' : '#0d0b0e'; g.fillRect(0, 0, 256, 256);
    for (var i = 0; i < 46; i++) {
      g.strokeStyle = redBase ? 'rgba(255,120,120,' + (0.05 + R() * 0.16) + ')' : 'rgba(200,30,50,' + (0.08 + R() * 0.2) + ')';
      g.lineWidth = 0.6 + R() * 1.8; g.beginPath();
      var x = R() * 256, y = R() * 256; g.moveTo(x, y);
      for (var k = 0; k < 4; k++) { x += (R() - 0.5) * 90; y += (R() - 0.5) * 90; g.lineTo(x, y); }
      g.stroke();
    }
    for (var j = 0; j < 26; j++) { g.fillStyle = 'rgba(240,220,190,' + (0.03 + R() * 0.06) + ')'; g.fillRect(R() * 256, R() * 256, 1.4, 1.4); }
    var t = new T.CanvasTexture(cv); t.encoding = T.sRGBEncoding; t.wrapS = t.wrapT = T.RepeatWrapping;
    return t;
  }
  var marbleBlack = marbleTex(false), marbleRed = marbleTex(true);
  var marbleMats = [];
  function marbleDisc(x, y, z, r, tex, rep) {
    var t2 = tex.clone(); t2.needsUpdate = true; t2.repeat.set(rep || 3, rep || 3);
    var m = new T.Mesh(new T.CylinderGeometry(r, r + 1.5, 0.5, 40), new T.MeshLambertMaterial({ map: t2 }));
    m.position.set(x, y - 0.13, z); m.frustumCulled = false; group.add(m); marbleMats.push(m);
    return m;
  }
  function textPlate(lines, w, h, fs) {
    var cv = document.createElement('canvas'); cv.width = 1024; cv.height = Math.round(1024 * h / w);
    var g = cv.getContext('2d');
    g.fillStyle = '#0d0a0c'; g.fillRect(0, 0, cv.width, cv.height);
    g.strokeStyle = '#d8a833'; g.lineWidth = 7; g.strokeRect(12, 12, cv.width - 24, cv.height - 24);
    g.fillStyle = '#ecc76e'; g.textAlign = 'center'; g.textBaseline = 'middle';
    lines.forEach(function (ln, i) {
      g.font = (i === 0 ? '800 ' : '600 ') + Math.round(fs * (i === 0 ? 1 : 0.52)) + 'px Georgia, serif';
      g.fillText(ln, cv.width / 2, cv.height * (lines.length === 1 ? 0.52 : 0.32 + i * 0.38));
    });
    var t = new T.CanvasTexture(cv); t.encoding = T.sRGBEncoding; t.anisotropy = 4;
    return new T.MeshBasicMaterial({ map: t });
  }
  /* a sign the lawful way: backing stone, plate PROUD of it,
   * a second face turned for the other journey */
  var footprints = [];
  function sign(x, y, z, ry, w, h, lines, fs, postH) {
    var g2 = new T.Group(); g2.position.set(x, y, z); g2.rotation.y = ry; group.add(g2);
    if (postH) {
      var post = new T.Mesh(new T.BoxGeometry(0.32, postH, 0.32), new T.MeshLambertMaterial({ color: C.titanium }));
      post.position.y = -postH / 2; post.frustumCulled = false; g2.add(post);
      footprints.push({ x: x, z: z, w: 0.7, d: 0.7, a: 0, y0: y - postH, y1: y });
    }
    var back = new T.Mesh(new T.BoxGeometry(w + 0.5, h + 0.5, 0.22), new T.MeshLambertMaterial({ color: C.stoneDark }));
    back.frustumCulled = false; g2.add(back);
    var mat = textPlate(lines, w, h, fs);
    var f1 = new T.Mesh(new T.PlaneGeometry(w, h), mat); f1.position.z = 0.14; f1.frustumCulled = false; g2.add(f1);
    var f2 = new T.Mesh(new T.PlaneGeometry(w, h), mat); f2.position.z = -0.14; f2.rotation.y = Math.PI; f2.frustumCulled = false; g2.add(f2);
    footprints.push({ x: x, z: z, w: w + 0.6, d: 0.8, a: ry, y0: y - h / 2 - 0.3, y1: y + h / 2 + 0.3 });
    return g2;
  }
  function lamp(x, y, z, s) {
    cyl(solid, 0.09, 0.13, 3.4 * (s || 1), 6, mat4(x, y + 1.7 * (s || 1), z, 0, 0, 0), C.titanium);
    sph(glow, 0.34 * (s || 1), mat4(x, y + 3.55 * (s || 1), z, 0, 0, 0), C.lamp);
  }
  function tree(x, y, z, s) {
    cyl(solid, 0.55, 0.7, 0.8, 8, mat4(x, y + 0.4, z, 0, 0, 0), C.marbleRed);
    cyl(solid, 0.13 * s, 0.2 * s, 2.2 * s, 6, mat4(x, y + 0.8 + 1.1 * s, z, 0, 0, 0), C.trunk);
    sph(solid, 1.9 * s, mat4(x, y + 0.8 + 2.6 * s, z, 0, 0, 0, 1, 0.85, 1), C.leaf);
    sph(solid, 1.25 * s, mat4(x + 0.9 * s, y + 0.8 + 2.1 * s, z + 0.5 * s, 0, 0, 0), C.leafLight);
    footprints.push({ x: x, z: z, w: 1.5, d: 1.5, a: 0, y0: y, y1: y + 1 });
  }
  /* a dwelling in the marble language: stone base, gold-lit
   * door, a lanai toward the plaza */
  function frameOf(r, a, cx, cz) {
    var px = cx + Math.cos(a) * r, pz = cz + Math.sin(a) * r;
    return { x: px, z: pz, tx: -Math.sin(a), tz: Math.cos(a), rx: Math.cos(a), rz: Math.sin(a), a: a };
  }
  function house(fr, y, s, floors) {
    var M = mat4(fr.x, y, fr.z, 0, Math.atan2(fr.tx, fr.tz), 0);
    function L(lx, ly, lz) { return mat4(fr.x + fr.tx * lx + fr.rx * lz, y + ly, fr.z + fr.tz * lx + fr.rz * lz, 0, Math.atan2(fr.tx, fr.tz), 0); }
    var W = 9 * s, D = 7.5 * s, Hh = 3.4 * s * floors;
    box(solid, W, Hh, D, (function () { var m = L(0, Hh / 2, 0); return m; })(), C.stone);
    box(solid, W + 1.2 * s, 0.5 * s, D + 1.2 * s, L(0, Hh + 0.25 * s, 0), C.stoneDark