<!-- SOLID CITY · part 3225 -->
als();
        var m = new T.Mesh(g, pr[1]); m.frustumCulled = false; group.add(m);
      });
    }
  });


  /* ================= THE TRAINS =================
   * Four cars to a train: three enclosed, and the last an open
   * observation car — a flat deck with a gold rail, riding flush
   * with the station pads so a traveller steps from stone to
   * deck in one pace. The train hovers 0.15 above its track. */
  function buildCar(open) {
    var car = new T.Group();
    if (!open) {
      car.add(box(2.9, 2.2, 8.4, MAT.titanium, 0, 1.55, 0));
      car.add(box(2.5, 0.5, 7.6, MAT.silver, 0, 2.85, 0));
      car.add(box(2.94, 0.8, 8.44, MAT.glass, 0, 1.95, 0));
      car.add(box(2.2, 0.18, 8.0, MAT.gold, 0, 0.28, 0));
    } else {
      car.add(box(2.6, 0.35, 8.2, MAT.titanium, 0, 0.28, 0));
      car.add(box(2.9, 0.12, 8.4, MAT.silver, 0, 0.51, 0));            // the deck plate — travellers stand at +0.57 over the car origin
      car.add(box(0.08, 0.85, 8.4, MAT.gold, -1.41, 1.0, 0));
      car.add(box(0.08, 0.85, 8.4, MAT.gold, 1.41, 1.0, 0));
      car.add(box(2.9, 0.85, 0.08, MAT.gold, 0, 1.0, -4.16));
      car.add(box(1.7, 0.45, 0.5, MAT.titanium, 0, 0.8, 1.6));          // a bench to sit the sky on
      car.add(box(1.7, 0.45, 0.5, MAT.titanium, 0, 0.8, -1.6));
    }
    return car;
  }
  function buildConsist() {
    var g = new T.Group(), cars = [];
    for (var i = 0; i < 4; i++) { var c = buildCar(i === 3); g.add(c); cars.push(c); }
    var prow = box(2.5, 1.5, 1.7, MAT.titanium, 0, 1.35, 4.9); prow.rotation.x = 0.5; cars[0].add(prow);
    cars[0].add(box(1.7, 0.5, 0.1, MAT.white, 0, 1.5, 5.35));
    cars[3].add(box(1.7, 0.5, 0.1, MAT.red, 0, 0.75, -4.3));
    return { group: g, cars: cars };
  }
  var trains = [];
  ROUTES.forEach(function (route, ri) {
    var built = buildConsist();
    group.add(built.group);
    var stops = route.stops;
    trains.push({
      id: ri, route: route, mesh: built.group, cars: built.cars,
      s: stops[0].s + 0.01, dir: 1, v: 0, mode: 'dwell', dwellT: 2 + ri * 2,
      stopIdx: 0, deck: { x: 0, y: -1000, z: 0, yaw: 0 }, station: stops[0].station
    });
  });
  var _v1 = new T.Vector3(), _v2 = new T.Vector3();
  function placeTrain(tr, dt) {
    for (var i = 0; i < 4; i++) {
      var sc = tr.s - tr.dir * (i * 9.4);
      var p = sample(tr.route, sc), q = sample(tr.route, sc + tr.dir * 4);
      var car = tr.cars[i];
      car.position.set(p.x, p.y + 0.15, p.z);
      _v1.set(q.x, q.y + 0.15, q.z); car.lookAt(_v1);
      if (i === 3) {
        tr.deck.x = p.x; tr.deck.y = p.y + 0.15 + 0.57; tr.deck.z = p.z;
        tr.deck.yaw = Math.atan2(q.x - p.x, q.z - p.z);
      }
    }
  }
  function stepTrain(tr, dt) {
    var stops = tr.route.stops;
    if (tr.mode === 'dwell') {
      tr.dwellT -= dt; tr.v = 0;
      if (tr.dwellT <= 0) {
        var nxt = tr.stopIdx + tr.dir;
        if (nxt < 0 || nxt >= stops.length) { tr.dir = -tr.dir; nxt = tr.stopIdx + tr.dir; }
        tr.stopIdx = nxt; tr.mode = 'run';
      }
    } else {
      var target = stops[tr.stopIdx], dStop = (target.s - tr.s) * tr.dir;
      if (dStop <= 0.05) {
        tr.s = target.s; tr.mode = 'dwell'; tr.dwellT = 4; tr.station = target.station;
      } else {
        var vT = dStop > 120 ? 26 : Math.max(0.5, 26 * dStop / 120);
        tr.v += Math.max(-3.8 * dt, Math.min(3.0 * dt, vT - tr.v));
        tr.s += tr.v * dt * tr.dir;
        if ((target.s - tr.s) * tr.dir < 0) tr.s = target.s;
      }
    }
    placeTrain(tr, dt);
  }

  /* ================= SHUTTLES =================
   * Small craft hopping port to port on their own business:
   * climb, cruise, descend, hover a while beside the disc,
   * and away again. The sky's ordinary errands. */
  function buildShuttle() {
    var g = new T.Group();
    var hull = new T.Mesh(new T.SphereGeometry(1.6, 12, 8), MAT.titanium); hull.scale.set(1, 0.55, 2.2); hull.position.y = 0.4; g.add(hull);
    var dome = new T.Mesh(new T.SphereGeometry(0.95, 10, 6), MAT.glass); dome.scale.set(1, 0.6, 1.4); dome.position.set(0, 0.95, 0.7); g.add(dome);
    g.add(box(1.9, 0.14, 3.4, MAT.gold, 0, -0.25, 0));
    g.add(box(0.3, 0.3, 0.3, MAT.white, 0, 0.5, 3.55));
    g.add(box(0.3, 0.3, 0.3, MAT.red, 0, 0.5, -3.55));
    return g;
  }
  var shuttles = [];
  for (var si = 0; si < 6; si++) {
    var sh = { mesh: buildShuttle(), mode: 'hover', t: 2 + si * 5, at: STATIONS[1 + (si * 3) % 16], from: null, to: null, ph: 0, laneY: 0, x: 0, y: -1000, z: 0, yaw: 0 };
    sh.x = sh.at.x + 14; sh.z = sh.at.z; sh.y = sh.at.gy + 12;
    group.add(sh.mesh); shuttles.push(sh);
  }
  function pickDest(notId) {
    var c = LANDINGS.filter(function (s) { return s.id !== notId; });
    return c[Math.floor(Math.random() * c.length)];
  }
  function stepShuttle(sh, dt) {
    sh.t -= dt;
    if (sh.mode === 'hover') {
      sh.y = sh.at.gy + 12 + Math.sin(perfNow * 0.9 + sh.x) * 0.3;
      if (sh.t <= 0) { sh.from = sh.at; sh.to = pickDest(sh.at.id); v