<!-- SOLID CITY · part 2541 -->
atrix4();
    }
    // a mailbox where the path meets the drive
    BASE = M4(pkX - rx * 2.2, groundAt(pkX - rx * 2.2, pkZ - rz * 2.2), pkZ - rz * 2.2, rec.ry, 0, 0);
    box(solid, 0.14, 1.05, 0.14, 0, 0.52, 0, C.woodDark);
    box(solid, 0.75, 0.4, 0.45, 0, 1.2, 0, R() < 0.5 ? 0x8a3030 : C.stoneDark);
    BASE = new T.Matrix4();
    rec.door = [doorX, doorZ]; rec.park = [pkX, pkZ];
  }
  var CAR_COLS = [0xb8bcc2, 0x7a2e2e, 0x2e4a6a, 0xd8d3c3, 0x3a3a3e, 0x6a7a4a];

  /* ================= CLUSTERS — homes for Hawaiian families ================= */
  var plates = [];   // {x, z, ry, name}
  function plateFor(name, x, z) {
    var rp = nearestRoad(x, z);
    plates.push({ x: x, z: z, ry: faceAngle(x, z, rp.x, rp.z), name: name });
  }
  function compound(cx, cz, name, mainStyle, satStyles, opts) {
    opts = opts || {};
    var main = findSpot(cx, cz, 45, 13);
    if (!main) return 0;
    var rp = nearestRoad(main[0], main[1]);
    var mrec = buildHome(mainStyle, main[0], main[1], faceAngle(main[0], main[1], rp.x, rp.z), 1.12, 'full');
    placed.push([main[0], main[1], 17]);
    serveHome(mrec, rp);
    var count = 1;
    for (var i = 0; i < satStyles.length; i++) {
      var a = (i / satStyles.length) * Math.PI * 2 + R() * 0.8;
      var sx = cx + Math.cos(a) * (opts.ring || 30), sz = cz + Math.sin(a) * (opts.ring || 30);
      var sp = findSpot(sx, sz, 30, 11);
      if (!sp) continue;
      var srec = buildHome(satStyles[i], sp[0], sp[1], faceAngle(sp[0], sp[1], cx, cz), 0.82 + R() * 0.2, i === 0 ? 'full' : 'simple');
      placed.push([sp[0], sp[1], 15]);
      // satellites share the compound's parking — a path of yellow stones to the yard instead of a second drive
      var fx = Math.sin(srec.ry), fz = Math.cos(srec.ry), frontL = srec.padCz + srec.padD / 2;
      steppedPath(sp[0] + fx * (frontL + 1), sp[1] + fz * (frontL + 1), cx, cz, 1.6, C.path, 2.6);
      srec.door = [sp[0] + fx * (frontL + 0.9), sp[1] + fz * (frontL + 0.9)];
      count++;
    }
    // the shared yard: a great monkeypod, taro at the low side, lava-rock edging
    plantAt(cx, cz, function () { monkeypod(1.25); });
    plantAt(cx + (R() - 0.5) * 16, cz + (R() - 0.5) * 16, function () { taroPatch(7.5, 5); });
    dressHome(mrec, { taro: null, palm: true });
    homes.slice(-count).forEach(function (r2) { if (r2 !== mrec) dressHome(r2, {}); });
    plateFor(name, cx, cz);
    return count;
  }
  function singles(cx, cz, name, n, styles, rad) {
    var count = 0;
    for (var i = 0; i < n; i++) {
      var sp = findSpot(cx, cz, rad || 95, 12);
      if (!sp) continue;
      var rp = nearestRoad(sp[0], sp[1]);
      var style = styles[i % styles.length];
      var rec = buildHome(style, sp[0], sp[1], faceAngle(sp[0], sp[1], rp.x, rp.z), 0.92 + R() * 0.3, R() < 0.5 ? 'full' : 'simple');
      placed.push([sp[0], sp[1], 16]);
      serveHome(rec, rp);
      dressHome(rec, { palm: R() < 0.4 });
      count++;
    }
    if (count) plateFor(name, cx, cz);
    return count;
  }
  function scatterTrees(n) {
    var tries = 0, done = 0;
    while (done < n && tries < n * 6) {
      tries++;
      var x = -1050 + R() * 2450, z = -2650 + R() * 1900;
      if (!clearOf(x, z, 13)) continue;
      var h = groundAt(x, z);
      if (h < 9 || h > 420) continue;
      if (slopeAt(x, z, 9) > 0.5) continue;
      var rp = nearestRoad(x, z);
      if (rp.dist < 10) continue;
      var kind = R();
      plantAt(x, z, function () {
        if (kind < 0.34) monkeypod(0.8 + R() * 0.5);
        else if (kind < 0.58) ohia(0.9 + R() * 0.5);
        else if (kind < 0.78) palm(0.85 + R() * 0.4);
        else breadfruit(0.9 + R() * 0.4);
      });
      placed.push([x, z, 7]);
      done++;
    }
    return done;
  }

  /* ================= NAME PLATES — one quiet plate per cluster ================= */
  var extras = [];
  function buildPlates() {
    plates.forEach(function (p) {
      var gy = groundAt(p.x, p.z);
      BASE = M4(p.x, gy, p.z, p.ry, 0, 0);
      box(solid, 0.22, 2.6, 0.22, -4.6, 1.3, 0, C.woodDark);
      box(solid, 0.22, 2.6, 0.22, 4.6, 1.3, 0, C.woodDark);
      BASE = new T.Matrix4();
      if (typeof document === 'undefined' || !document.createElement) return;
      try {
        var cv = document.createElement('canvas'); cv.width = 1024; cv.height = 160;
        var cx2 = cv.getContext('2d');
        cx2.fillStyle = '#23272e'; cx2.fillRect(0, 0, 1024, 160);
        cx2.strokeStyle = '#c9a44a'; cx2.lineWidth = 5; cx2.strokeRect(10, 10, 1004, 140);
        cx2.fillStyle = '#e8c86a'; cx2.textAlign = 'center'; cx2.textBaseline = 'middle';
        var fs = 64; cx2.font = 'bold ' + fs + 'px Georgia, serif';
        while (cx2.measureText(p.name).width > 940 && fs > 26) { fs -= 4; cx2.font = 'bold ' + fs + 'px Georgia, serif'; }
        cx2.fillText(p.name, 512, 84);
        var tex = new T.CanvasTexture(cv);
        var m = new T.Mesh(new T.PlaneGeometry(11, 1.72), new T.MeshBasicMaterial({ map: tex, side: T.DoubleS