<!-- SOLID CITY · part 3088 -->
x) 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 uphill edge sits a hair under its own ground (the
     * dirt meets the road), and the surface rises from that edge
     * toward the centre at a civil cross slope. Hard floor: the
     * surface never sinks more than 0.30 under the highest ground
     * beneath it — no dirt tongue ever crosses the carriageway; where
     * the land insists on crossing higher, the road rides over and
     * the crossing reads as a through-cut at the edges only. */
    var prof = [], floorY = [];
    for (i = 0; i < N; i++) {
      var s = st[i];
      var gC = groundAt(s.x, s.z);
      var gL = groundAt(s.x - s.rx * hw, s.z - s.rz * hw);
      var gR = groundAt(s.x + s.rx * hw, s.z + s.rz * hw);
      var crossMax = Math.max(gL, gC, gR);
      var gUp = upSide > 0 ? gR : gL;
      var sCross = Math.max(-0.04, Math.min(0.10, (gC - gUp) / hw));
      var want = gUp - 0.06 + hw * sCross;
      if (want > gC + 1.05) want = gC + 1.05;        // never fill the centre higher
      prof.push(want);
      floorY.push(crossMax - 0.30);
      s.gC = gC; s.crossMax = crossMax; s.gUp = gUp;
    }
    /* The fairing is an ENVELOPE, not an average: the profile is the
     * LOWEST surface that never sinks under its floor and never
     * grades beyond the cap in either direction. Constraint passes
     * only ever RAISE a station — a limiter that could also lower
     * would drag the surface under the floor it was sworn to keep
     * (the first build's burial was exactly that crime). */
    for (i = 0; i < N; i++) if (prof[i] < floorY[i]) prof[i] = floorY[i];
    function envelope() {
      var i2;
      for (i2 = 1; i2 < N; i2++) { var d = st[i2].ds || stepM; var need = prof[i2 - 1] - gradeCap * d; if (prof[i2] < need) prof[i2] = need; }
      for (i2 = N - 2; i2 >= 0; i2--) { var d2 = st[i2 + 1].ds || stepM; var need2 = prof[i2 + 1] - gradeCap * d2; if (prof[i2] < need2) prof[i2] = need2; }
    }
    envelope(); envelope(); envelope();
    /* vertical curvature, honestly bought: each smoothing pass lifts
     * the low side of a grade break toward the chord, rounding the
     * ramp's foot and crest into a curve — the price is fill on the
     * approaches, and the examiner reports it. */
    for (var pass = 0; pass < 6; pass++) {
      var sm = prof.slice();
      for (i = 1; i < N - 1; i++) { var cand = prof[i - 1] * 0.3 + prof[i] * 0.4 + prof[i + 1] * 0.3; if (cand > prof[i]) sm[i] = cand; }
      prof = sm; envelope();
    }
    for (i = 0; i < N; i++) { st[i].y = prof[i]; st[i].grade = i ? (prof[i] - prof[i - 1]) / (st[i].ds || stepM) : 0; }

    /* banking: from curvature alone, smoothed over the runoff, capped */
    var bank = st.map(function (s) { var b = -s.kappa * 1.31; return Math.max(-bankCap, Math.min(bankCap, b)); });
    for (var bp = 0; bp < 2; bp++) {
      var bs = bank.slice();
      for (i = 0; i < N; i++) {
        var sum = 0, cnt = 0;
        for (var k = -4; k <= 4; k++) { var j = i + k; if (j >= 0 && j < N) { sum += bank[j]; cnt++; } }
        bs[i] = sum / cnt;
      }
      bank = bs;
    }
    for (i = 0; i < N; i++) st[i].bank = Math.max(-bankCap, Math.min(bankCap, bank[i]));

    /* corners: the banked right vector carries the height difference */
    for (i = 0; i < N; i++) {
      var s2 = st[i], cb = Math.cos(s2.bank), sb = Math.sin(s2.bank);
      var Rx = s2.rx * cb, Ry = sb, Rz = s2.rz * cb;
      s2.L = [s2.x - Rx * hw, s2.y - Ry * hw, s2.z - Rz * hw];
      s2.R = [s2.x + Rx * hw, s2.y + Ry * hw, s2.z + Rz * hw];
      