<!-- SOLID CITY · part 2512 -->
shuttles and the grand houses' own craft can find them. */
  var hotels = [], mansions = [];
  function buildPad(name, x, z, padY, kind) {
    var SEG = 24, ctr = [x, padY, z], rim = [], k;
    for (k = 0; k < SEG; k++) { var an = k / SEG * Math.PI * 2; rim.push([x + Math.cos(an) * 10, padY, z + Math.sin(an) * 10]); }
    var S = { pos: [], nor: [] }, SK = { pos: [], nor: [] }, GL = { pos: [], nor: [] };
    for (k = 0; k < SEG; k++) {
      var r1 = rim[k], r2 = rim[(k + 1) % SEG];
      triPush(S, ctr, r1, r2);
      standTris.push(ctr[0], ctr[1], ctr[2], r1[0], r1[1], r1[2], r2[0], r2[1], r2[2]);
      var d1 = [r1[0], groundAt(r1[0], r1[2]) - 0.4, r1[2]], d2 = [r2[0], groundAt(r2[0], r2[2]) - 0.4, r2[2]];
      triPush(SK, r1, r2, d2); triPush(SK, r1, d2, d1);
      var a1 = k / SEG * Math.PI * 2, a2 = (k + 1) / SEG * Math.PI * 2;
      var g1 = [x + Math.cos(a1) * 8.55, padY + 0.02, z + Math.sin(a1) * 8.55], g2 = [x + Math.cos(a2) * 8.55, padY + 0.02, z + Math.sin(a2) * 8.55];
      var g3 = [x + Math.cos(a2) * 9.0, padY + 0.02, z + Math.sin(a2) * 9.0], g4 = [x + Math.cos(a1) * 9.0, padY + 0.02, z + Math.sin(a1) * 9.0];
      triPush(GL, g1, g2, g3); triPush(GL, g1, g3, g4);
    }
    pourSurf(S, solid, C.stone);
    pourSurf(SK, solid, C.stoneDark);
    pourSurf(GL, glowB, 0xd8a833);
    var bx = x, bz = z - 10.8;
    BASE = M4(bx, padY - 0.1, bz, 0, 0, 0);
    streetLight();
    BASE = new T.Matrix4();
    foot.push([bx, bz, 0.3, 0.3, padY - 0.1, padY + 5.2, 0]);
    foot.push([x, z, 20, 20, padY - 6, padY, 0]);
    ports.push({ name: name, x: x, z: z, y: padY, kind: kind });
    return padY;
  }
  function buildPrivatePort(name, ax, az, keepOut) {
    var best = null;
    for (var gx = -55; gx <= 55; gx += 5) for (var gz = -55; gz <= 55; gz += 5) {
      var x = ax + gx, z = az + gz, dd = Math.hypot(gx, gz);
      if (dd < keepOut || dd > 58) continue;
      if (!clearOf(x, z, 13)) continue;
      var mn = 1e12, mx = -1e12, pts = [[0, 0]];
      for (var k = 0; k < 8; k++) pts.push([Math.cos(k * Math.PI / 4) * 9, Math.sin(k * Math.PI / 4) * 9]);
      for (var j = 0; j < pts.length; j++) { var y = groundAt(x + pts[j][0], z + pts[j][1]); if (y < mn) mn = y; if (y > mx) mx = y; }
      var range = mx - mn, score = range * 10 + dd * 0.3;
      if (!best || score < best.score) best = { x: x, z: z, mn: mn, mx: mx, range: range, score: score };
    }
    if (!best) { /* second chance, wider: a port a short walk from its house still serves it */
      for (var gx2 = -80; gx2 <= 80; gx2 += 5) for (var gz2 = -80; gz2 <= 80; gz2 += 5) {
        var x2 = ax + gx2, z2 = az + gz2, dd2 = Math.hypot(gx2, gz2);
        if (dd2 < keepOut || dd2 > 82 || !clearOf(x2, z2, 11)) continue;
        var mn2 = 1e12, mx2 = -1e12, pts2 = [[0, 0]];
        for (var k2 = 0; k2 < 8; k2++) pts2.push([Math.cos(k2 * Math.PI / 4) * 9, Math.sin(k2 * Math.PI / 4) * 9]);
        for (var j2 = 0; j2 < pts2.length; j2++) { var y2 = groundAt(x2 + pts2[j2][0], z2 + pts2[j2][1]); if (y2 < mn2) mn2 = y2; if (y2 > mx2) mx2 = y2; }
        var range2 = mx2 - mn2, score2 = range2 * 10 + dd2 * 0.3;
        if (!best || score2 < best.score) best = { x: x2, z: z2, mn: mn2, mx: mx2, range: range2, score: score2 };
      }
    }
    if (!best) return null;
    var padY = best.range <= 1.5 ? (best.mx + best.mn) / 2 + 0.15 : best.mx - 0.2;
    return buildPad(name, best.x, best.z, padY, 'private');
  }
  function findGrand(ax, az) {
    for (var ring = 0; ring <= 12; ring++) for (var k = 0; k < 16; k++) {
      var a = k / 16 * Math.PI * 2 + ring * 0.7, rr = ring * 6;
      var x = ax + Math.cos(a) * rr, z = az + Math.sin(a) * rr;
      if (benchAt(x, z, 15) >= 0 && clearOf(x, z, 22) && roadClear(x, z, 16)) return [x, z];
    }
    return null;
  }
  function buildHotel(name, ax, az) {
    var spot = findGrand(ax, az); if (!spot) return null;
    var x = spot[0], z = spot[1], ry = R() * Math.PI * 2;
    var floorY = lotHeight(x, z, 12) + 0.3;
    BASE = M4(x, floorY, z, ry, 0, 0);
    box(solid, 27, 0.6, 19, 0, -0.3, 0, C.stone, 0);
    for (var f = 0; f < 3; f++) {
      var fy = f * 3.05;
      box(solid, 20, 3.05, 13, 0, fy + 1.52, 0, f === 2 ? C.cream2 : C.cream, 0);
      box(solid, 20.8, 0.16, 3.4, 0, fy + 0.08, 8.1, C.conc, 0);
      for (var px = -9; px <= 9; px += 3) box(solid, 0.24, 2.9, 0.24, px, fy + 1.5, 9.6, C.woodDark, 0);
      box(glassB, 18.5, 1.1, 0.1, 0, fy + 1.8, 6.56, C.glass, 0);
      if (f === 2) box(glowB, 20.4, 0.14, 13.4, 0, fy + 3.1, 0, C.gold, 0);
    }
    box(solid, 21, 0.3, 14, 0, 9.3, 0, C.slate, 0);
    box(solid, 3.2, 2.4, 0.3, 0, 1.2, 6.65, C.door, 0);
    BASE = new T.Matrix4();
    foot.push([x, z, 27, 19, floorY - 0.6, floorY + 9.6, ry]);
    placed.push([x, z, 32]);
    dressHome({ x: x, z: z, ry: ry, w: 27, d: 19, lanaiDepth: 3 }, { palm: true, plumeria: true, shrub: true });
    var rec = { name: name, x: x, z: z, y: floorY };
    hotels.push(rec);
    return rec;
  }
  functio