<!-- SOLID CITY · part 3029 -->
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(), bankGrass = newSurf();
    var shoL = newSurf(), shoR = newSurf(), rail = newSurf(), posts = newSurf();
    var cliffW = newSurf();   // the cliff walls of the deep cuts — poured stone by the factory
    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];
    }
    /* THE DRAPE (the finish that answers the fins): the old bank
     * marched outward up to fourteen metres hunting for the land and,
     * not finding it, pinned its foot in the air — 3,152 sail
     * triangles by the census, floating up to thirteen metres above
     * the slope. The drape instead walks a short profile out from
     * the edge and MEETS the land: at each step its height is the
     * land itself, clamped between a falling repose (0.62) and a
     * rising cut (0.95); once it reaches the land it follows it,
     * tucked a quarter-metre under. If the land falls away faster
     * than repose for a full bench depth (3.4 m), the profile
     * becomes a benched shoulder, descending gently — a retaining
     * line at the road's own edge, never a sheet hung in the air.
     * The whole profile spans at most 6.3 m from the edge. */
    function drapeFrom(e, dirx, dirz) {
      /* THE CLIFF WALL (the King's word, 2026-10-13 ~23:47): "the
       * road if it needs to be cut in to the slab create cliff
       * walls." Where the land stands deep above the road — a
       * true piercing, the far land still 2.5 m up after the full
       * march and the graded face never meeting it — the cut does
       * not grade at 0.95 a step. It stands PLUMB (his measure:
       * "just plumb and sturdy as a river flows" — no crazy
       * up-and-down geometry): the rise cap becomes 4.2 a step,
       * a wall that meets the land in a stride or two and reads
       * as what it is — a road cut through the slab of the
       * mountain. Shallow cuts keep the graded face; only the
       * deep ones stand. */
      var lands = [];
      for (var k0 = 1; k0 <= 6; k0++) lands[k0] = groundAt(e[0] + dirx * 1.05 * k0, e[2] + dirz * 1.05 * k0) - 0.25;
      var cliff = lands[6] - e[1] > 2.5;
      if (cliff) { for (var k1 = 1; k1 <= 6; k1++) { if (lands[k1] <= e[1] + 0.95 * 1.05 * k1) { cliff = false; break; } } }
      var riseCap = cliff ? 4.2 : 0.95;
      var pts = [e], met = false;
      for (var k = 1; k <= 6; k++) {
        var d = 1.05 * k;
        var px = e[0] + dirx * d, pz = e[2] + dirz * d;
        var land = lands[k];
        var y;
        if (met) y = land;
        else {
          y = Math.max(e[1] - 0.62 * d, Math.min(land, e[1] + riseCap * d));
          var bench = e[1] - 3.4 - 0.25 * Math.max(0, d - 5.48);
          if (y < bench) y = bench;
          if (y === land) met = true;
        }
        pts.push([px, y, pz]);
      }
      /* THE DAYLIGHT CHASE (the harde