<!-- SOLID CITY · part 2146 -->
Mesh(new T.CylinderGeometry(0.3,0.3,12,6),boatMastM);
      mast.position.set(bx,2,bz);root.add(mast);
      var sail=new T.Mesh(new T.PlaneGeometry(7,9),sailM);
      sail.position.set(bx,5,bz+0.5);root.add(sail);
    }
  });

  // animated bits
  var beaconM=null,beamM=null,fw=null,fj=null;
  root.traverse(function(m){
    if(m.name==='beacon')beaconM=m;
    if(m.name==='beam')beamM=m;
    if(m.name==='fountainwater')fw=m;
    if(m.name==='fountainjet')fj=m;
  });
  var boats=[];root.traverse(function(m){if(m.name==='boat')boats.push(m);});
  root.userData={
    footprint:foot,center:{x:0,z:0},radius:260,height:580,
    elevators:elevators, standFloors:standFloors,
    update:function(t,dt,A){
      if(beaconM){var p=0.6+0.4*Math.sin(t*2.4);beaconM.scale.setScalar(p);}
      if(beamM){beamM.material.opacity=0.2+0.12*Math.sin(t*2.4);}
      if(fw){fw.position.y=2.6+Math.sin(t*3)*0.15;}
      if(fj){fj.scale.y=1+Math.sin(t*5)*0.12;}
      for(var b=0;b<boats.length;b++){boats[b].position.y=-2.5+Math.sin(t*1.2+b*1.7)*0.5;boats[b].rotation.z=Math.sin(t*0.9+b)*0.04;}
      if(window._hyperPortholes){ for(var ph=0; ph<window._hyperPortholes.length; ph++){ var hm=window._hyperPortholes[ph]; hm.material.opacity=0.85+0.15*Math.sin(t*2+ph*0.7); hm.rotation.z=t*0.3+ph; } }
      // elevators: animate cabs, carry the rider
      if(dt&&A&&A.S){
        var S=A.S;
        for(var e=0;e<elevators.length;e++){
          var el=elevators[e];
          if(el.moving){
            el.t+=dt;
            var dur=3.0, k=Math.min(1,el.t/dur);
            var ease=k<0.5?2*k*k:1-Math.pow(-2*k+2,2)/2; // easeInOut
            var y=el.from+(el.to-el.from)*ease;
            el.cab.position.y=y+1.75;
            // carry the player if inside the cab (world coords)
            var dx=S.p.x-(P.x+el.x), dz=S.p.z-(P.z+el.z);
            if(Math.abs(dx)<3&&Math.abs(dz)<3&&Math.abs(S.p.y-(P.y+y))<4){
              S.p.y=P.y+y+0.2;
            }
            if(k>=1){ el.moving=false; el.idx=el.toIdx; }
          }
        }
      }
    },
    actHere:function(wx,wz){
      // near an elevator cab? offer the ride
      for(var e=0;e<elevators.length;e++){
        var el=elevators[e];
        if(el.moving)continue;
        var dx=wx-(P.x+el.x), dz=wz-(P.z+el.z);
        if(Math.abs(dx)<4&&Math.abs(dz)<4){
          var nf=(el.idx+1)%el.floors.length;
          var names=['LOBBY','FLOOR 2','FLOOR 3','FLOOR 4','FLOOR 5','OBSERVATORY','ROOF'];
          return 'ELEVATOR \u2192 '+(names[nf]||('FLOOR '+(nf+1)));
        }
      }
      return null;
    },
    boardElevator:function(wx,wz){
      for(var e=0;e<elevators.length;e++){
        var el=elevators[e];
        if(el.moving)continue;
        var dx=wx-(P.x+el.x), dz=wz-(P.z+el.z);
        if(Math.abs(dx)<4&&Math.abs(dz)<4){
          el.toIdx=(el.idx+1)%el.floors.length;
          el.from=el.floors[el.idx]; el.to=el.floors[el.toIdx];
          el.t=0; el.moving=true;
          return true;
        }
      }
      return false;
    }
  };
  return root;
}
export default createHyperCity;


===== FILE: public/city/hyper-train-place.js =====
/* Solid City — places THE HYPER TRAIN & WORMHOLE (public/city/hyper-train.js):
   the King's rail through the Bore: THE DEPARTURE (mouth station) -> 800m ride
   -> THE WORMHOLE (the only crossing) -> transit -> THE ARRIVAL (Starwell).
   Lazy: imported after the city is built; any import/build error is caught and
   the city carries on. Floor height is read from the village at runtime,
   matching the Bore exactly.
   Adds: station/train/wormhole collision, the wormhole seal (no walk-through),
   radar blip, standY hooks for both platforms. Boarding is physical via the
   city action button (city.js actTarget kind 'train'). */
(function () {
'use strict';
if (!window.SC || !SC.api) return;
var A = SC.api, S = A.S;
var POS = { x: 2000, z: 1880 }; // mouth; same frame as the Bore
var M = { state: 'idle', group: null, wait: 8.0, floorY: 22, trainSolid: null };
function villageFloor() {
  try {
    var V = window.CityVillageMauna;
    if (!V || V.state() !== 'ready') return null;
    var g = V.group(); if (!g) return null;
    return g.position.y + 1.1;
  } catch (e) { return null; }
}
function addBox(cx, cz, w, d, rot, y0, y1) {
  var nx = Math.max(1, Math.ceil(w / 2.5)), nz = Math.max(1, Math.ceil(d / 2.5)), cw = w / nx, cd = d / nz, c = Math.cos(rot), s = Math.sin(rot);
  var hx = cw / 2 * Math.abs(c) + cd / 2 * Math.abs(s), hz = cw / 2 * Math.abs(s) + cd / 2 * Math.abs(c);
  for (var i = 0; i < nx; i++) for (var j = 0; j < nz; j++) {
    var ox = -w / 2 + cw * (i + 0.5), oz = -d / 2 + cd * (j + 0.5), lx = cx + ox * c + oz * s, lz = cz - ox * s + oz * c;
    A.solids.push({ x0: POS.x + lx - hx, x1: POS.x + lx + hx, z0: POS.z + lz - hz, z1: POS.z + lz + hz, y0: M.floorY + y0, y1: M.floorY + y1, hyperTrain: true });
  }
}
function collide(ud) {
  (ud.footprint || []).forEach(function (b) { addBox(b[0], b[1], b[2], b[3], b[6]