<!-- SOLID CITY · part 3224 -->
 P.ys[lo] + (P.ys[hi] - P.ys[lo]) * t,
      z: P.zs[lo] + (P.zs[hi] - P.zs[lo]) * t,
      tx: P.xs[hi] - P.xs[lo], ty: P.ys[hi] - P.ys[lo], tz: P.zs[hi] - P.zs[lo]
    };
  }

  /* ================= THE TRACK =================
   * A floating guideway: titanium beam, gold edge-light, and a
   * beacon every ~120 m — an octahedron of gold light hung just
   * above the beam, pulsing slow, so the line can be read from
   * the ground at noon and from the sky at speed. */
  var beaconPos = [];
  ROUTES.forEach(function (route) {
    var P = route.pts, pos = [], strip = [];
    function quad(arr, a, b, c, d) { arr.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2], a[0], a[1], a[2], c[0], c[1], c[2], d[0], d[1], d[2]); }
    for (var i = 0; i < P.xs.length - 1; i++) {
      var ax = P.xs[i], ay = P.ys[i], az = P.zs[i], bx = P.xs[i + 1], by = P.ys[i + 1], bz = P.zs[i + 1];
      var dx = bx - ax, dz = bz - az, dl = Math.hypot(dx, dz) || 1, rx = -dz / dl, rz = dx / dl;
      var w = 0.85, dep = 0.55;
      var A1 = [ax + rx * w, ay, az + rz * w], A2 = [ax - rx * w, ay, az - rz * w], B1 = [bx + rx * w, by, bz + rz * w], B2 = [bx - rx * w, by, bz - rz * w];
      var A1b = [A1[0], ay - dep, A1[2]], A2b = [A2[0], ay - dep, A2[2]], B1b = [B1[0], by - dep, B1[2]], B2b = [B2[0], by - dep, B2[2]];
      quad(pos, A2, B2, B1, A1);              // top
      quad(pos, A1, B1, B1b, A1b);            // right side
      quad(pos, B2, A2, A2b, B2b);            // left side
      quad(pos, B1b, B2b, A2b, A1b);          // underside
      var gw = 0.16, go = w - 0.12;
      var G1 = [ax + rx * go, ay + 0.02, az + rz * go], G2 = [bx + rx * go, by + 0.02, bz + rz * go], G3 = [ax + rx * (go - gw), ay + 0.02, az + rz * (go - gw)], G4 = [bx + rx * (go - gw), by + 0.02, bz + rz * (go - gw)];
      quad(strip, G3, G4, G2, G1);
      var H1 = [ax - rx * go, ay + 0.02, az - rz * go], H2 = [bx - rx * go, by + 0.02, bz - rz * go], H3 = [ax - rx * (go - gw), ay + 0.02, az - rz * (go - gw)], H4 = [bx - rx * (go - gw), by + 0.02, bz - rz * (go - gw)];
      quad(strip, H2, H1, H3, H4);
    }
    var g1 = new T.BufferGeometry(); g1.setAttribute('position', new T.Float32BufferAttribute(pos, 3)); g1.computeVertexNormals();
    var beam = new T.Mesh(g1, MAT.titanium); beam.frustumCulled = false; group.add(beam);
    var g2 = new T.BufferGeometry(); g2.setAttribute('position', new T.Float32BufferAttribute(strip, 3)); g2.computeVertexNormals();
    var sm = new T.Mesh(g2, MAT.gold); sm.frustumCulled = false; group.add(sm);
    for (var bs = 60; bs < route.pts.len - 30; bs += 120) { var bp = sample(route, bs); beaconPos.push([bp.x, bp.y + 2.2, bp.z]); }
  });
  var beaconMesh = null;
  if (beaconPos.length) {
    beaconMesh = new T.InstancedMesh(new T.OctahedronGeometry(0.55), MAT.gold.clone(), beaconPos.length);
    var M4d = new T.Matrix4();
    beaconPos.forEach(function (p, i) { M4d.makeTranslation(p[0], p[1], p[2]); beaconMesh.setMatrixAt(i, M4d); });
    beaconMesh.frustumCulled = false; group.add(beaconMesh);
  }

  /* ================= STATION DRESSING =================
   * A halo of gold light floats over every station, turning
   * slow — the port's name written in light for the sky. The
   * two ward stations also receive their discs here: stone,
   * skirt, and gold ring, the same pattern as the ports. */
  var halos = [];
  STATIONS.forEach(function (s) {
    var halo = new T.Mesh(new T.TorusGeometry(11, 0.14, 8, 44), MAT.goldDim);
    halo.rotation.x = Math.PI / 2; halo.position.set(s.x, s.gy + 7, s.z);
    group.add(halo); halos.push(halo);
    if (s.ownPad) {
      var SEG = 24, rim = [], k;
      for (k = 0; k < SEG; k++) { var an = k / SEG * Math.PI * 2; rim.push([s.x + Math.cos(an) * 10, s.gy, s.z + Math.sin(an) * 10]); }
      var pos = [], sk = [], gl = [];
      function tri(arr, a, b, c) { arr.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]); }
      var ctr = [s.x, s.gy, s.z];
      for (k = 0; k < SEG; k++) {
        var r1 = rim[k], r2 = rim[(k + 1) % SEG];
        tri(pos, ctr, r1, r2);
        var d1 = [r1[0], heightAt(r1[0], r1[2]) - 0.4, r1[2]], d2 = [r2[0], heightAt(r2[0], r2[2]) - 0.4, r2[2]];
        tri(sk, r1, r2, d2); tri(sk, r1, d2, d1);
        var a1 = k / SEG * Math.PI * 2, a2 = (k + 1) / SEG * Math.PI * 2;
        tri(gl, [s.x + Math.cos(a1) * 8.55, s.gy + 0.02, s.z + Math.sin(a1) * 8.55], [s.x + Math.cos(a2) * 8.55, s.gy + 0.02, s.z + Math.sin(a2) * 8.55], [s.x + Math.cos(a2) * 9.0, s.gy + 0.02, s.z + Math.sin(a2) * 9.0]);
        tri(gl, [s.x + Math.cos(a1) * 8.55, s.gy + 0.02, s.z + Math.sin(a1) * 8.55], [s.x + Math.cos(a2) * 9.0, s.gy + 0.02, s.z + Math.sin(a2) * 9.0], [s.x + Math.cos(a1) * 9.0, s.gy + 0.02, s.z + Math.sin(a1) * 9.0]);
      }
      [[pos, MAT.stone], [sk, MAT.stoneDark], [gl, MAT.gold]].forEach(function (pr) {
        var g = new T.BufferGeometry(); g.setAttribute('position', new T.Float32BufferAttribute(pr[0], 3)); g.computeVertexNorm