<!-- SOLID CITY · part 3035 -->
 + nz * outward[1] < 0) { var t = b; b = c; c = t; nx = -nx; ny = -ny; nz = -nz; } }
    else if (ny < 0) { var t2 = b; b = c; c = t2; nx = -nx; ny = -ny; nz = -nz; }
    S.pos.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]);
    for (var i = 0; i < 3; i++) S.nor.push(nx, ny, nz);
  }
  function quad(S, a, b, c, d, outward) { tri(S, a, b, c, outward); tri(S, a, c, d, outward); }
  /* a standing triangle list is the same corners, flattened */
  function standQuad(T, a, b, c, d) { T.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]); T.push(a[0], a[1], a[2], c[0], c[1], c[2], d[0], d[1], d[2]); }

  /* ---------- the centerline: Catmull–Rom through the waypoints ---------- */
  function splinePoints(pts, stepM) {
    var P = [pts[0]].concat(pts, [pts[pts.length - 1]]);
    var dense = [];
    for (var i = 1; i < P.length - 2; i++) {
      var p0 = P[i - 1], p1 = P[i], p2 = P[i + 1], p3 = P[i + 2];
      var span = Math.hypot(p2[0] - p1[0], p2[1] - p1[1]);
      var n = Math.max(2, Math.ceil(span / stepM));
      for (var j = (i === 1 ? 0 : 1); j <= n; j++) {
        var t = j / n, t2 = t * t, t3 = t2 * t;
        dense.push([
          0.5 * ((2 * p1[0]) + (-p0[0] + p2[0]) * t + (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 + (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3),
          0.5 * ((2 * p1[1]) + (-p0[1] + p2[1]) * t + (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 + (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3)
        ]);
      }
    }
    return dense;
  }
  function resample(dense, stepM) {
    var out = [dense[0]], acc = 0;
    for (var i = 1; i < dense.length; i++) {
      var d = Math.hypot(dense[i][0] - dense[i - 1][0], dense[i][1] - dense[i - 1][1]);
      acc += d;
      if (acc >= stepM) { out.push(dense[i]); acc = 0; }
    }
    if (out[out.length - 1] !== dense[dense.length - 1]) out.push(dense[dense.length - 1]);
    return out;
  }

  /* ---------- buildRoad: the whole craft ---------- */
  function buildRoad(spec) {
    var pts = spec.points, W = spec.width || 7.6, hw = W / 2;
    var stepM = spec.stationStep || 2;
    var gradeCap = spec.gradeCap || 0.38;
    var bankCap = (spec.bankCapDeg || 5) * Math.PI / 180;
    var dense = splinePoints(pts, 0.5);
    var line = resample(dense, stepM);
    /* a spline that doubles back on itself can leave two stations a
     * few centimetres apart in plan though metres apart along the
     * path — such a micro-station invents a grade out of rounding
     * (a 53% "grade" over 0.66 m was measured at a junction). Merge
     * it: no station stands within 1.2 m of its predecessor. */
    var keptLine = [line[0]];
    for (var li = 1; li < line.length - 1; li++) {
      var lastK = keptLine[keptLine.length - 1];
      if (Math.hypot(line[li][0] - lastK[0], line[li][1] - lastK[1]) >= 1.2) keptLine.push(line[li]);
    }
    keptLine.push(line[line.length - 1]);
    line = keptLine;
    var N = line.length;

    /* tangents and headings */
    var i, st = [];
    for (i = 0; i < N; i++) {
      var a = line[Math.max(0, i - 1)], b = line[Math.min(N - 1, i + 1)];
      var fx = b[0] - a[0], fz = b[1] - a[1], fl = Math.hypot(fx, fz) || 1;
      fx /= fl; fz /= fl;
      st.push({ x: line[i][0], z: line[i][1], fx: fx, fz: fz, rx: fz, rz: -fx, h: Math.atan2(fx, fz), ds: 0 });
    }
    /* NOTE the right vector: forward (fx, fz) with the city's heading
     * convention (heading = atan2(fx, fz), forward = (sin, cos)) has its
     * right hand at (fz, -fx) in plan — at heading 0 (facing +z) the
     * right is +x: (fz, -fx) = (1, 0). Verified against the factory's
     * own frames before a single triangle was drawn. */
    for (i = 1; i < N; i++) st[i].ds = Math.hypot(st[i].x - st[i - 1].x, st[i].z - st[i - 1].z);

    /* curvature (signed): positive = turning toward the right hand */
    for (i = 0; i < N; i++) {
      var h0 = st[Math.max(0, i - 1)].h, h1 = st[Math.min(N - 1, i + 1)].h;
      var run = (st[Math.min(N - 1, i + 1)].ds || stepM) + (st[i].ds || stepM);
      st[i].kappa = wrapAngle(h1 - h0) / Math.max(run, 0.5);
    }

    /* THE BENCH (the King's technique, from his photographs of the
     * Waipiʻo road, 2026-10-13): a mountain road is DUG INTO the
     * topography — dirt on ONE side. The uphill edge keys to the land
     * and the cut bank rises above it; the downhill edge carries a
     * shoulder and a guardrail over the drop. Dirt on both sides
     * belongs only to a through-cut where a road pierces a ridge.
     * So: first learn which side is uphill over the road's whole run. */
    var upScore = 0;
    for (i = 0; i < N; i++) {
      var sU = st[i];
      var uRg = groundAt(sU.x + sU.rx * (hw + 1.2), sU.z + sU.rz * (hw + 1.2));
      var uLg = groundAt(sU.x - sU.rx * (hw + 1.2), sU.z - sU.rz * (hw + 1.2));
      upScore += (uRg - uLg);
    }
    var upSide = upScore >= 0 ? 1 : -1;

    /* the profile: keyed to the uphill edge, floored by the surface.
     * desired: the uphil