<!-- SOLID CITY · part 2113 -->
oz, 0.35, 10, f2y, f2y + 2.8);
    box(3.5, 0.9, 1.8, deskM, ox, f2y + 0.45, oz); solid(ox, oz, 3.5, 1.8, f2y, f2y + 0.9);
  });
  // ---- roof deck ----
  box(W, 1, sD, floorM, 0, roofY - 0.5, sZ);
  box(nwW, 1, nD, floorM, nwX, roofY - 0.5, nZ);
  box(neW, 1, nD, floorM, neX, roofY - 0.5, nZ);
  standFloors.push({ x0: o.x - W / 2, x1: o.x + W / 2, z0: o.z - D / 2, z1: o.z + D / 2, y: roofY });
  box(W, 1.1, 0.35, steel, 0, roofY + 0.55, -D / 2 + 0.2); solid(0, -D / 2 + 0.2, W, 0.35, roofY, roofY + 1.1);
  box(W, 1.1, 0.35, steel, 0, roofY + 0.55, D / 2 - 0.2); solid(0, D / 2 - 0.2, W, 0.35, roofY, roofY + 1.1);
  box(0.35, 1.1, D, steel, -W / 2 + 0.2, roofY + 0.55, 0); solid(-W / 2 + 0.2, 0, 0.35, D, roofY, roofY + 1.1);
  box(0.35, 1.1, D, steel, W / 2 - 0.2, roofY + 0.55, 0); solid(W / 2 - 0.2, 0, 0.35, D, roofY, roofY + 1.1);
  [[-W / 4, -D / 4], [W / 4, -D / 4], [-W / 4, D / 4], [W / 4, D / 4]].forEach(function (p) {
    box(2.5, 1.2, 2.5, deskM, p[0], roofY + 0.6, p[1]); solid(p[0], p[1], 2.5, 2.5, roofY, roofY + 1.2);
  });
  var rplq = new T.Mesh(new T.PlaneGeometry(6, 1.5),
    new T.MeshBasicMaterial({ map: textPlate(T, 'ROOF DECK'), side: T.DoubleSide }));
  rplq.position.set(0, roofY + 3, -D / 2 + 1); g.add(rplq);
  root.add(g);
  return g;
}

/* PORTAL GATE — Phase 2 "GO FOR LAUNCH" (2026-10-11)
 * A ground-level hero porthole: gold posts + lintel, glowing swirl disc.
 * Walk into the disc → transit → next node in the ring. The King's words:
 * "the rest will be port holes aka portals" — now they take you somewhere.
 */
function buildPortalGate(T, root, foot, o) {
  var gold = o.gold;
  var stone = new T.MeshLambertMaterial({ color: 0x2c313a });
  var g = new T.Group();
  g.position.set(o.x, o.y || 0, o.z);
  function box(w, h, d, mat, x, y, z) {
    var m = new T.Mesh(new T.BoxGeometry(w, h, d), mat);
    m.position.set(x, y, z); g.add(m); return m;
  }
  box(0.6, 5.5, 0.6, gold, -2.4, 2.75, 0);
  box(0.6, 5.5, 0.6, gold, 2.4, 2.75, 0);
  box(5.4, 0.7, 0.7, gold, 0, 5.6, 0);
  box(6, 0.5, 3.4, stone, 0, 0.25, 0);
  var disc = new T.Mesh(new T.CircleGeometry(2.4, 28),
    new T.MeshBasicMaterial({ map: o.portTex, transparent: true, side: T.DoubleSide }));
  disc.position.set(0, 2.9, 0);
  disc.name = 'portalgate';
  g.add(disc);
  var plq = new T.Mesh(new T.PlaneGeometry(7.5, 1.6),
    new T.MeshBasicMaterial({ map: textPlate(T, 'PORTAL \u2192 ' + o.nextName), side: T.DoubleSide }));
  plq.position.set(0, 6.9, 0);
  g.add(plq);
  // collision: posts solid, disc center walk-through (the doorway)
  var gy = o.y || 0;
  foot.push([o.x - 2.4, o.z, 0.8, 0.8, gy, gy + 5.5, 0]);
  foot.push([o.x + 2.4, o.z, 0.8, 0.8, gy, gy + 5.5, 0]);
  root.add(g);
  return { disc: disc, x: o.x, z: o.z, y: gy };
}

export function createHyperCity(o){
  var T=o.THREE,P=o.position||{x:0,y:0,z:0};
  var root=new T.Group();root.name='hyper-city';root.position.set(P.x,P.y,P.z);
  var foot=[];
  var R=mulberry(1379);
  var PHI=1.6180339887;
  // materials
  var glassA=new T.MeshLambertMaterial({map:facadeTex(T),emissive:0x1a2a40,emissiveIntensity:0.35});
  var glassB=new T.MeshLambertMaterial({map:facadeTex(T,'rgba(30,50,80,0.5)'),emissive:0x16263c,emissiveIntensity:0.35});
  var steel=new T.MeshLambertMaterial({color:0x3a4048});
  var darkSteel=new T.MeshLambertMaterial({color:0x23272e});
  var gold=new T.MeshLambertMaterial({color:0xd8aa48,emissive:0x4a2e08,emissiveIntensity:0.7});
  var deckM=new T.MeshLambertMaterial({color:0x2c313a});
  var roadM=new T.MeshLambertMaterial({color:0x1e2228});
  var goldTrim=new T.MeshBasicMaterial({color:0xd8aa48});

  // ---- podium: the district platform (420 x 380), skirted ----
  var pod=new T.Mesh(new T.BoxGeometry(420,10,380),deckM);
  pod.position.y=-5;root.add(pod);
  var podRim=new T.Mesh(new T.BoxGeometry(424,1.2,384),steel);
  podRim.position.y=0.1;root.add(podRim);
  foot.push([0,0,424,384,-10,0.4,0]); // walkable top is implicit (podium); block below

  // ---- streets: grid avenues (visual strips on the podium) ----
  var i;
  for(i=-1;i<=1;i++){
    var av=new T.Mesh(new T.BoxGeometry(420,0.3,14),roadM);
    av.position.set(0,0.35,i*90);root.add(av);
    var av2=new T.Mesh(new T.BoxGeometry(14,0.3,380),roadM);
    av2.position.set(i*110,0.35,0);root.add(av2);
  }
  // streetlights along avenues
  var lampG=new T.CylinderGeometry(0.3,0.4,9,6);
  var lampM=new T.MeshLambertMaterial({color:0x444a54});
  var bulbM=new T.MeshBasicMaterial({color:0xffe8a0});
  for(i=-3;i<=3;i++){
    [-135,-45,45,135].forEach(function(z){
      var lx=i*60;
      if(Math.abs(lx)<25&&Math.abs(z)<25)return; // not in plaza
      var pole=new T.Mesh(lampG,lampM);pole.position.set(lx,4.5,z);root.add(pole);
      var bulb=new T.Mesh(new T.SphereGeometry(0.7,8,6),bulbM);bulb.position.set(lx,9.2,z);root.add(bulb);
      foot.push([lx,z,1,1,0,9,0]);
    });
    [-100,0,100].forEach(function(x){
      var lz=i*60;
      if(Math.abs(x)<25&&Math.abs(lz)<25)return;
      