<!-- SOLID CITY · part 3077 -->
if (!dCache[i]) dCache[i] = drapeFrom(eA, A.rx * sd, A.rz * sd);
        if (!dCache[i + 1]) dCache[i + 1] = drapeFrom(eB, B.rx * sd, B.rz * sd);
        var pA = dCache[i].pts, pB = dCache[i + 1].pts;
        /* THE CLEAR BODY (the King's ruling, 2026-10-13 ~22:02):
         * only the road's skin renders colour. The drape's first
         * two segments — the brow that seals the seam where the
         * surface leaves the shoulder — stay painted, read from
         * either side. Everything deeper is the road's INSIDE:
         * it goes clear, emitting no triangles at all, because a
         * vertex spent painting the inside of a hill is a vertex
         * stolen from the frame. The drape's data still walks its
         * full profile (the census reads its drops and meets as
         * before), and standing truth never rode the skirts —
         * feet lose nothing when the inside stops dressing. */
        var SKIRT_BROW = 2;
        for (var dk = 0; dk < Math.min(SKIRT_BROW, pA.length - 1); dk++) {
          quad(sk, pA[dk], pB[dk], pB[dk + 1], pA[dk + 1], [sd * A.rx, sd * A.rz]);
          quad(sk, pB[dk], pA[dk], pA[dk + 1], pB[dk + 1], [sd * A.rx, sd * A.rz]);   // both faces: a bank is read from either side
        }
        /* THE GRASS AMENDMENT (the King's word, minutes later):
         * an invisible inside shows as GAPS, and a gap reads as a
         * bug where a bank reads as a hillside. So the deep drape
         * returns — but as ONE grass run from the brow to the
         * drape's foot, not the full staircase of segments: a
         * quarter of the old vertices, no holes anywhere, and the
         * bank wears the mountain's own green, as real road cuts
         * grow over. The factory pours bankGrass with its lawn. */
        if (pA.length - 1 > SKIRT_BROW) {
          quad(bankGrass, pA[SKIRT_BROW], pB[SKIRT_BROW], pB[pB.length - 1], pA[pA.length - 1], [sd * A.rx, sd * A.rz]);
          quad(bankGrass, pB[SKIRT_BROW], pA[SKIRT_BROW], pA[pA.length - 1], pB[pB.length - 1], [sd * A.rx, sd * A.rz]);
        }
        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++; }
      }
    }
    /* 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], qA[qA.length - 1], outw); quad(bankGrass, qB[2], qA[2], qA[qA.length - 1], qB[qB.length - 1], outw); }   // and its deep rim in grass, per the Amendment
      }
    }
    stats.fairDev = fairDev;
    return {
      stations: st, stats: stats, walkSide: walkSide, upSide: upSide,
      ribbon: ribbon, curbL: curbL, curbR: curbR, goldL: goldL, goldR: goldR,
      walk: walk, skirtL: skirtL, skirtR: skirtR, fans: fans, bankGrass: bankGrass,
      shoL: shoL, shoR: shoR, rail: rail, posts: posts,
      standTris: stand
    };
  }

  return { buildRoad: buildRoad