<!-- SOLID CITY · part 2998 -->
GAPS, and a gap reads as a
         * bug where a bank reads as a hillside. So the deep drape
         * returns — but as grass, not the full staircase of
         * segments, and the factory pours it with its lawn. The
         * grass itself is no longer emitted here, station by
         * station: THE ONE BLOCK (the King's next word) sweeps it
         * after the run is surveyed — see bankSweep below. */
        var dropA = eA[1] - pA[pA.length - 1][1], riseA = pA[pA.length - 1][1] - eA[1];
        if (sd < 0 && Math.max(dropA, riseA) > stats.maxSkirtL) stats.maxSkirtL = Math.max(dropA, riseA);
        if (sd > 0 && Math.max(dropA, riseA) > stats.maxSkirtR) stats.maxSkirtR = Math.max(dropA, riseA);
        if (sd === upSide) { if (riseA > stats.cutBank) stats.cutBank = riseA; }
        else { if (dropA > stats.bank) stats.bank = dropA; if (!dCache[i].met) stats.bankUnmet++; }
      }
    }
    /* THE ONE BLOCK (the King's design, 2026-10-13 ~22:15):
     * "finding the measurements of the clear area and set it to
     * one block instead of many." The clear area beneath each bank
     * is MEASURED from the drape the survey already walked — the
     * brow point and the foot point at every station — and built
     * as ONE swept grass strip per stretch. A station earns a kept
     * point only where the brow's true line strays more than half
     * a metre from the chord across it, or the stretch runs past
     * 24 stations: a straight bank becomes a few long blocks, a
     * curving bank keeps its points exactly where the land turns.
     * One block instead of many, measured before it is built. */
    function bankSweep(cache, sdSign) {
      var brow = [], foot = [];
      for (var i2 = 0; i2 < N; i2++) {
        var dc = cache[i2];
        if (dc && dc.pts.length - 1 > 2) { brow[i2] = dc.pts[2]; foot[i2] = dc.pts[dc.pts.length - 1]; }
        else { brow[i2] = null; foot[i2] = null; }
      }
      function dev3(p, a, b) {
        var abx = b[0] - a[0], aby = b[1] - a[1], abz = b[2] - a[2];
        var t = ((p[0] - a[0]) * abx + (p[1] - a[1]) * aby + (p[2] - a[2]) * abz) / (abx * abx + aby * aby + abz * abz || 1);
        t = Math.max(0, Math.min(1, t));
        var dx = p[0] - (a[0] + abx * t), dy = p[1] - (a[1] + aby * t), dz = p[2] - (a[2] + abz * t);
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
      }
      var a3 = 0;
      while (a3 < N - 1) {
        if (!brow[a3]) { a3++; continue; }
        var b3 = a3 + 1;
        while (b3 < N - 1) {
          var nb = b3 + 1;
          if (!brow[nb]) break;
          var bad = false;
          for (var m = a3 + 1; m < nb; m++) { if (brow[m] && dev3(brow[m], brow[a3], brow[nb]) > 0.5) { bad = true; break; } }
          if (bad || nb - a3 > 24) break;
          b3 = nb;
        }
        if (brow[b3]) {
          var outw3 = [sdSign * st[a3].rx, sdSign * st[a3].rz];
          quad(bankGrass, brow[a3], brow[b3], foot[b3], foot[a3], outw3);
          quad(bankGrass, brow[b3], brow[a3], foot[a3], foot[b3], outw3);
        }
        a3 = b3;
      }
    }
    bankSweep(drapeCacheL, -1); bankSweep(drapeCacheR, 1);
    /* arrival fans at both ends. A fan is not a flat disc dropped on
     * the ramp's end — the first drive-through caught that step
     * (0.9 m at the seam). The fan is the road's own PLANE extended:
     * each rim vertex carries the end grade along the road's axis
     * and the end bank across it, so ribbon and fan meet as one
     * surface. Rim skirts below, so they never float. */
    var ends = [st[0], st[N - 1]];
    for (var e = 0; e < 2; e++) {
      var C0 = ends[e], fr = C0.hw + 0.9, SEG = 14;
      var src = e === 0 ? st[1] : st[N - 2];
      var fGrade = e === 0 ? src.grade : st[N - 1].grade;
      var fBank = Math.tan(src.bank);
      var rim = [];
      for (var k2 = 0; k2 < SEG; k2++) {
        var an = k2 / SEG * Math.PI * 2;
        var dxr = Math.cos(an) * fr, dzr = Math.sin(an) * fr;
        var along = dxr * C0.fx + dzr * C0.fz, across = dxr * C0.rx + dzr * C0.rz;
        rim.push([C0.x + dxr, C0.y + fGrade * along + fBank * across, C0.z + dzr]);
      }
      var rimProf = rim.map(function (rp) { var dl = Math.hypot(rp[0] - C0.x, rp[2] - C0.z) || 1; return drapeFrom(rp, (rp[0] - C0.x) / dl, (rp[2] - C0.z) / dl).pts; });
      for (k2 = 0; k2 < SEG; k2++) {
        var r1 = rim[k2], r2 = rim[(k2 + 1) % SEG];
        var ctr = [C0.x, C0.y + 0.02, C0.z];
        tri(fans, ctr, r1, r2); stand.push(ctr[0], ctr[1], ctr[2], r1[0], r1[1], r1[2], r2[0], r2[1], r2[2]);
        var qA = rimProf[k2], qB = rimProf[(k2 + 1) % SEG];
        var outw = [r1[0] - C0.x, r1[2] - C0.z];
        for (var dk2 = 0; dk2 < Math.min(2, qA.length - 1); dk2++) { quad(fans, qA[dk2], qB[dk2], qB[dk2 + 1], qA[dk2 + 1], outw); quad(fans, qB[dk2], qA[dk2], qA[dk2 + 1], qB[dk2 + 1], outw); }   // the fan's rim: brow painted as the fan
        if (qA.length - 1 > 2) { quad(bankGrass, qA[2], qB[2], qB[qB.length - 1]