<!-- SOLID CITY · part 3037 -->
inStartY != null) {
        prof[0] = spec.pinStartY;
        for (i = 1; i < N; i++) { var dpc = st[i].ds || stepM; var ndc = prof[i - 1] - gradeCap * dpc; if (prof[i] < ndc) prof[i] = ndc; }
      }
    }
    /* the pins are promises, and promises have feet: the cap-chains
     * that honour them meet the natural profile at a corner. A few
     * raise-only passes round those feet with honest fill, the
     * envelope re-proves every grade, and then the pins are set
     * exact once more — the promise stands, and the road walks up
     * to it like a road, not a step. */
    if (spec.pinStartY != null || spec.pinEndY != null) {
      for (var rp = 0; rp < 3; rp++) {
        var rsm = prof.slice();
        for (i = 1; i < N - 1; i++) { var rcand = prof[i - 1] * 0.3 + prof[i] * 0.4 + prof[i + 1] * 0.3; if (rcand > prof[i]) rsm[i] = rcand; }
        prof = rsm;
      }
      envelope();
      if (spec.pinStartY != null) prof[0] = spec.pinStartY;
      if (spec.pinEndY != null) prof[N - 1] = spec.pinEndY;
    }
    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]));

    /* THE JUNCTION THROAT: a road that must braid past its
     * neighbour in the first metres from a node may be built
     * narrower there, widening smoothly as the lines part —
     * spec.taperStart / taperEnd {width, over}. Only the SURFACE
     * narrows; the profile above was computed for the full
     * breadth, and the height law does not change. */
    var arcOf = [0];
    for (i = 1; i < N; i++) arcOf.push(arcOf[i - 1] + (st[i].ds || stepM));
    for (i = 0; i < N; i++) {
      var hwI = hw;
      if (spec.taperStart && arcOf[i] < spec.taperStart.over) { var uS = arcOf[i] / spec.taperStart.over, smS = uS * uS * (3 - 2 * uS); hwI = Math.min(hwI, spec.taperStart.width / 2 + (hw - spec.taperStart.width / 2) * smS); }
      if (spec.taperEnd && (arcOf[N - 1] - arcOf[i]) < spec.taperEnd.over) { var uE = (arcOf[N - 1] - arcOf[i]) / spec.taperEnd.over, smE = uE * uE * (3 - 2 * uE); hwI = Math.min(hwI, spec.taperEnd.width / 2 + (hw - spec.taperEnd.width / 2) * smE); }
      st[i].hw = hwI;
    }

    /* 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 * s2.hw, s2.y - Ry * s2.hw, s2.z - Rz * s2.hw];
      s2.R = [s2.x + Rx * s2.hw, s2.y + Ry * s2.hw, s2.z + Rz * s2.hw];
      s2.Rx = Rx; s2.Ry = Ry; s2.Rz = Rz;
    }

    var walkSide = spec.sidewalk === false ? 0 : upSide;   // the uphill side, named for the street kit

    /* surfaces */
    var ribbon = newSurf(), curbL = newSurf(), curbR = newSurf(), goldL = newSurf(), goldR = newSurf();
    var walk = newSurf(), skirtL = newSurf(), skirtR = newSurf(), fans = newSurf();
    var shoL = newSurf(), shoR = newSurf(), rail = newSurf(), posts = newSurf();
    var stand = [];
    var stats = { length: 0, stations: N, maxGrade: 0, maxKink: 0, maxBank: 0, maxSkirtL: 0, maxSkirtR: 0, cutBank: 0, bank: 0, bankUnmet: 0, worstGradeAt: null, worstKinkAt: null };
    for (i = 0; i < N; i++) {
      var g = Math.abs(st[i].grade);
      if (g > stats.maxGrade) { stats.maxGrade = g; stats.worstGradeAt = [st[i].x, st[i].z]; }
      if (Math.abs(st[i].bank) > stats.maxBank) stats.maxBank = Math.abs(st[i].bank);
      if (i) {
        stats.length += st[i].ds;
        var kink = Math.abs(st[i].grade - st[i - 1].grade);
        if (kink > stats.maxKink) { stats.maxKink = kink; stats.worstKinkAt = [st[i].x, st[i].z]; }
      }
    }
    function edgePt(s, side, out, lift) {
      var d = s.hw + out;
      return [s.x + s.Rx * side * d, s.y + s.Ry * side * d + (lift || 0), s.z + s.Rz * side * d];
    }
    for (i = 0; i < N - 1; i++) {
      var A = st[i], B = st[i + 1];
      quad(ribbon, A.L, B.L, B.R, A.R); standQuad(stand, A.L, B.L, B.R, A.R);
      /* curbs: a top ribbon and an outer face, both edges; the top stands */
      var sides = [-1, 1];
      for (var q = 0; q < 2; q++) {
        var sd = sides[q], curb = sd < 0 ? curbL : curbR, gold = sd < 0 ? goldL : goldR;
        var out0 = 0.32, lift0 = 0.0;
        if (spec.furniture === 'bench') {
          /* THE BENCH KIT (the King's photographs): a dirt shoulder on
           * both edges — the carriageway's 