<!-- SOLID CITY · part 3199 -->
ById('ending').classList.add('hidden');openDrawer('stats')});
  window.addEventListener('resize',resize);state=initialState();spawnPhase();resize();draw();updateStations();
})();
</script>
</body>
</html>


===== FILE: public/city/skytrain-place.js =====
/* Solid City — places THE SKY TRAINS (public/city/skytrain.js).
 * "Now we will build space trains in the sky." — the King, 2026-10-14.
 * The placer follows the mountain pattern: wait for the built
 * city, bring the network in, expose the hooks the standing law
 * reads (CitySkyTrain.standY, added to updateWalk + standingAt
 * in the same breath, as the Law of the Two Lists commands),
 * the context action that boards a dwelling train, and the drag
 * that carries a rider standing on the observation deck.
 * Boarding is by presence, not by possession: stand on the deck
 * and the train takes you; step off at any station — or leap,
 * if the sky is calling; the fall is the world's own business. */
(function () {
'use strict';
if (!window.SC || !SC.api) return;
var A = SC.api, S = A.S;
var M = { state: 'idle', sky: null, wait: 4.5, riding: -1, wasDeck: false };
function hyperStops() {
  var H = window.CityHyper; if (!H || !H.pois || !M.sky) return;
  M.sky.stations.forEach(function (st) {
    var id = 'sky-' + st.id;
    if (H.pois.some(function (p) { return p.id === id; })) return;
    H.pois.push({ id: id, name: st.name, short: 'SKY', x: st.x, z: st.z, stand: { x: st.x, z: st.z },
      alias: 'sky train station port shuttle ' + st.name.toLowerCase(),
      text: st.name + ' — a station of the Sky Trains. The mountain roads were rejected by the King\'s word on the night of October 14, 2026; the ports and stations are the way between places now, and the trains run their floating track above the country. Wait for the train, step onto the observation deck, and ride.' });
  });
  if (H.regrid) H.regrid();
}
async function build() {
  if (M.state !== 'idle') return;
  M.state = 'building';
  try {
    var mod = await import('/city/skytrain.js?v=20261014g');
    var extras = [];
    try {
      var mp = (window.CityMountainHomes && CityMountainHomes.group()) ? CityMountainHomes.group().userData.ports || [] : [];
      mp.forEach(function (p) { if (p.kind === 'private') extras.push({ id: 'pv-' + extras.length, name: p.name, x: p.x, z: p.z, gy: p.y, grand: /HOTEL|MANSION/.test(p.name) }); });
    } catch (e) {}
    M.sky = mod.createSkyTrains({ THREE: A.T, heightAt: A.heightAt, extraStations: extras });
    A.world.add(M.sky.group);
    M.state = 'ready';
    hyperStops();
  } catch (e) { M.state = 'idle'; }
}
function standY(x, z, py) {
  if (!M.sky || M.state !== 'ready') return -1000;
  var c = M.sky.deckClaim(x, z, py == null ? 0 : py);
  return c ? c.y : -1000;
}
function actHere(x, z) {
  if (!M.sky || M.state !== 'ready') return null;
  if (M.riding >= 0) return '🚆 STEP OUT';
  var near = M.sky.trainNear(x, z);
  return near ? '🚆 RIDE THE SKY TRAIN' : null;
}
function board() {
  if (!M.sky || M.state !== 'ready') return;
  var P = S.p;
  if (M.riding >= 0) { M.riding = -1; if (A.help) A.help('You step out of the sky train.'); return; }
  var near = M.sky.trainNear(P.x, P.z);
  if (near) { M.riding = near.train; if (A.help) A.help('🚆 Riding ' + 'the sky train — step out at any station.'); }
}
function update(dt) {
  if (M.state === 'idle') { M.wait -= dt; var mReady = !!(window.CityMountainHomes && CityMountainHomes.state && CityMountainHomes.state() === 'ready'); if (M.wait <= 0 && A.isBuilt() && (mReady || M.wait < -20)) build(); return; }
  if (M.state !== 'ready' || !M.sky) return;
  M.sky.update(dt);
  /* THE DRAG: a rider standing on the observation deck is
   * carried by it — each frame the player is drawn to the
   * deck's anchor, and the standing law (through our own
   * standY hook) keeps their feet on the plate. Boarding put
   * them here; stepping out, or the deck's claim lapsing for
   * good, lets them go. */
  var P = S.p;
  if (M.riding >= 0 && P) {
    var anchor = M.sky.deckAnchor(M.riding);
    if (anchor) {
      var dx = anchor.x - P.x, dz = anchor.z - P.z;
      if (Math.abs(dx) > 30 || Math.abs(dz) > 30) { M.riding = -1; }
      else {
        var claim = M.sky.deckClaim(P.x, P.z, P.y);
        if (claim && claim.train === M.riding) { P.x = anchor.x; P.z = anchor.z; M.wasDeck = true; }
        else if (M.wasDeck) { M.wasDeck = false; }
        else M.riding = -1;
      }
    } else M.riding = -1;
  }
}
function addLanding(p) {
  if (!M.sky || M.state !== 'ready' || !p) return false;
  if (M.sky.landings.some(function (l) { return l.id === p.id; })) return true;
  M.sky.landings.push({ id: p.id, name: p.name, x: p.x, z: p.z, gy: p.gy, grand: !!p.grand, ownPad: false });
  return true;
}
window.CitySkyTrain = { update: update, standY: standY, actHere: actHere, board: board, addLanding: addLanding, state: function () { return M.state; }, sky: function () { return M.sky; } };
})();


===== FILE: p