<!-- SOLID CITY · part 3041 -->
 if (s4.Ls > 0.01) { els.push({ kind: 'cloth', len: s4.Ls, k0: kArc, k1: 0, h0: hdg }); hdg += kArc * s4.Ls / 2; }
      px = V[vv][0] + Math.sin(phi[vv]) * s4.T; pz = V[vv][1] + Math.cos(phi[vv]) * s4.T;
      // the walker's position after the elements is integrated below; px/pz here only anchor the NEXT tangent's start in exact geometry
    }
    // final tangent to the last surveyed point (exact)
    els.push({ kind: 'line', len: 0, k0: 0, k1: 0, h0: hdg, toEnd: true });
    /* integrate: fine steps, midpoint rule on the analytic heading;
     * the final line element's length is whatever closes the chain */
    var totalEls = els.length;
    // first pass: lengths of all but the closing line
    var dense = [[V[0][0], V[0][1], phi[0], 0]];   // x, z, heading, kappa
    var cx = V[0][0], cz = V[0][1], th = phi[0], sAcc = 0;
    var DT = 0.2;
    for (var ei = 0; ei < totalEls; ei++) {
      var el = els[ei];
      var elLen = el.len;
      if (el.toEnd) {
        // direction is th (== final leg heading); length closes to V[nv-1]
        var ex = V[nv - 1][0] - cx, ez = V[nv - 1][1] - cz;
        elLen = ex * Math.sin(th) + ez * Math.cos(th);
        if (elLen < 0) elLen = 0;
      }
      var steps = Math.max(1, Math.ceil(elLen / DT)), dt = elLen / steps;
      for (var q = 0; q < steps; q++) {
        // heading at the substep's midpoint, from the element's own
        // law: θ(s) = h0 + k0·s + (k1−k0)·s²/(2L) — exact for lines,
        // arcs, and clothoids alike; the midpoint rule on an analytic
        // heading keeps position error at millimetres per element
        var sMid = (q + 0.5) * dt;
        var thMid = el.h0 + el.k0 * sMid + (elLen > 0 ? (el.k1 - el.k0) * sMid * sMid / (2 * elLen) : 0);
        cx += Math.sin(thMid) * dt; cz += Math.cos(thMid) * dt;
        sAcc += dt;
        var sEnd = (q + 1) * dt;
        var thEnd = el.h0 + el.k0 * sEnd + (elLen > 0 ? (el.k1 - el.k0) * sEnd * sEnd / (2 * elLen) : 0);
        var kEnd = el.k0 + (el.k1 - el.k0) * (elLen > 0 ? sEnd / elLen : 0);
        dense.push([cx, cz, thEnd, kEnd]);
        th = thEnd;
      }
    }
    /* the compass rule: distribute the closure error back along
     * the traverse, and set the last point exact — the junction
     * keeps its promise to the centimetre */
    var errX = dense[dense.length - 1][0] - V[nv - 1][0], errZ = dense[dense.length - 1][1] - V[nv - 1][1];
    /* two passes, always: measure the traverse BEFORE touching it.
     * A single pass that reads lengths off points it is already
     * correcting feeds on itself — the weights outrun the total
     * and a two-metre seed error leaves the county (measured:
     * trillion-metre steps from exactly that feedback). */
    var cum = [0];
    for (var di = 1; di < dense.length; di++) cum.push(cum[di - 1] + Math.hypot(dense[di][0] - dense[di - 1][0], dense[di][1] - dense[di - 1][1]));
    var totalLen = cum[cum.length - 1] || 1;
    for (di = 1; di < dense.length; di++) {
      var wgt = cum[di] / totalLen;
      dense[di][0] -= errX * wgt; dense[di][1] -= errZ * wgt;
    }
    dense[dense.length - 1][0] = V[nv - 1][0]; dense[dense.length - 1][1] = V[nv - 1][1];
    /* frequency sampling: close where curvature lives, easy elsewhere */
    var line = [[dense[0][0], dense[0][1]]], head = [dense[0][2]], kap = [dense[0][3]];
    var acc = 0;
    for (di = 1; di < dense.length; di++) {
      acc += Math.hypot(dense[di][0] - dense[di - 1][0], dense[di][1] - dense[di - 1][1]);
      var want = Math.abs(dense[di][3]) > 1 / 60 ? stepCurve : stepStraight;
      if (acc >= want || di === dense.length - 1) { line.push([dense[di][0], dense[di][1]]); head.push(dense[di][2]); kap.push(dense[di][3]); acc = 0; }
    }
    var minR = Infinity;
    for (si = 0; si < sites.length; si++) minR = Math.min(minR, sites[si].R);
    return { line: line, head: head, kap: kap, sites: sites.length, minR: minR };
  }

  /* ---------- buildRoad: the whole craft ---------- */
  function buildRoad(spec) {
    var pts = spec.points, W = spec.width || 7.6, hw = W / 2;
    var stepM = spec.stationStep || (spec.furniture === 'bench' ? 1.25 : 2);
    var gradeCap = spec.gradeCap || 0.38;
    var bankCap = (spec.bankCapDeg || 5) * Math.PI / 180;
    /* THE CORRIDOR FRONT: the alignment is fitted to elements and
     * sampled analytically; the stations inherit exact headings
     * and curvatures from the elements themselves. Frequency: the
     * corridor closes its stations where curvature lives and
     * walks easy on the straights — stepM stays the road's base
     * grain, and curves sample at 1.25 m whatever the base. */
    var aligned = fitAlignment(pts, Math.min(stepM, 1.25), stepM);
    var line = aligned.line;
    /* a fitted chain can still leave two stations close in plan
     * where an arc doubles near itself — merge micro-stations as
     * the spline era taught: no station within 1.2 m of its
     * predecessor (the ends are promises and stay ex