<!-- SOLID CITY · part 2136 -->
box(W, 1.2, 0.4, steel, 0, roofY+0.6, D/2-0.2); solid(0, D/2-0.2, W, 0.4, roofY, roofY+1.2);
    box(0.4, 1.2, D, steel, -W/2+0.2, roofY+0.6, 0); solid(-W/2+0.2, 0, 0.4, D, roofY, roofY+1.2);
    box(0.4, 1.2, D, steel, W/2-0.2, roofY+0.6, 0); solid(W/2-0.2, 0, 0.4, D, roofY, roofY+1.2);
    [[-15,-15],[15,-15],[-15,15],[15,15]].forEach(function(p){
      box(3, 1.5, 3, deskM, p[0], roofY+0.75, p[1]); solid(p[0], p[1], 3, 3, roofY, roofY+1.5);
    });

  } else {
    // ============ CYLINDER PODIUM (spire) ============
    var segs = 16, rad = W/2, lobbyH2 = 14, f2y2 = floors[1], obsY = floors[2];
    for (i = 0; i < segs; i++) {
      var a = (i/segs)*Math.PI*2;
      var isDoor = (i === 3 || i === 4);
      var x1 = Math.cos(a)*rad, z1 = Math.sin(a)*rad;
      var x2 = Math.cos(a+Math.PI*2/segs)*rad, z2 = Math.sin(a+Math.PI*2/segs)*rad;
      var mx = (x1+x2)/2, mz = (z1+z2)/2;
      var segW = Math.hypot(x2-x1, z2-z1)+0.5;
      if (isDoor) {
        var wall = box(segW, H-lobbyH2, 0.8, glassA, mx, lobbyH2+(H-lobbyH2)/2, mz, -a);
      } else {
        box(segW, H, 0.8, glassA, mx, H/2, mz, -a);
        solid(mx, mz, segW, 1, 0, H);
      }
    }
    // Lobby: 8 gold columns
    for (i = 0; i < 8; i++) {
      var ca = (i/8)*Math.PI*2;
      var cx = Math.cos(ca)*18, cz = Math.sin(ca)*18;
      var col = new T.Mesh(new T.CylinderGeometry(1.1,1.3,lobbyH2,8), gold);
      col.position.set(cx, lobbyH2/2, cz); g.add(col);
      solid(cx, cz, 2.6, 2.6, 0, lobbyH2);
    }
    box(12, 1.2, 3, deskM, 0, 0.6, -10); solid(0, -10, 12, 3, 0, 1.2);
    for (i = 0; i < 4; i++) {
      var ba = (i/4)*Math.PI*2 + 0.4;
      var bx = Math.cos(ba)*24, bz = Math.sin(ba)*24;
      box(4, 0.8, 1.5, deskM, bx, 0.4, bz); solid(bx, bz, 4, 1.5, 0, 0.8);
    }
    var ceil = new T.Mesh(new T.CylinderGeometry(rad, rad, 1, 16), ceilM);
    ceil.position.y = lobbyH2+0.5; g.add(ceil);
    var plq2 = new T.Mesh(new T.PlaneGeometry(10,2.5),
      new T.MeshBasicMaterial({ map: o.textPlate('THE SPIRE LOBBY'), side: T.DoubleSide }));
    plq2.position.set(0, 7, rad-2); plq2.rotation.y = Math.PI; g.add(plq2);

    // Elevator (2 shafts)
    [-10, 10].forEach(function(sx){
      box(7, H, 0.6, wallM, sx, H/2, -20); solid(sx, -20, 7, 0.6, 0, H);
      box(0.6, H, 7, wallM, sx-3.5, H/2, -16.5); solid(sx-3.5, -16.5, 0.6, 7, 0, H);
      box(0.6, H, 7, wallM, sx+3.5, H/2, -16.5); solid(sx+3.5, -16.5, 0.6, 7, 0, H);
      var cab2 = box(6, 3.5, 6, steel, sx, 1.75, -16.5);
      cab2.name = 'elevcab';
      elevators.push({ cab: cab2, x: o.x+sx, z: o.z-16.5, floors: floors, idx: 0, moving: false, t: 0, from: 0, to: 0 });
    });

    // FLOOR 2 (circular, 6 rooms)
    var slab = new T.Mesh(new T.CylinderGeometry(rad-1, rad-1, 1, 16), floorM);
    slab.position.y = f2y2-0.5; g.add(slab);
    standFloors.push({ x0: o.x-rad, x1: o.x+rad, z0: o.z-rad, z1: o.z+rad, y: f2y2, cyl: true, cx: o.x, cz: o.z, r: rad-1 });
    for (j = 0; j < 6; j++) {
      var ra = (j/6)*Math.PI*2;
      var rx = Math.cos(ra)*22, rz = Math.sin(ra)*22;
      box(8, 3, 0.4, partM, rx, f2y2+1.5, rz, -ra);
      solid(rx, rz, 8, 1, f2y2, f2y2+3);
      box(3, 1, 1.5, deskM, Math.cos(ra)*26, f2y2+0.5, Math.sin(ra)*26);
    }

    // OBSERVATORY (glass curtain, 360°)
    var oslab = new T.Mesh(new T.CylinderGeometry(rad-1, rad-1, 1, 16), floorM);
    oslab.position.y = obsY-0.5; g.add(oslab);
    standFloors.push({ x0: o.x-rad, x1: o.x+rad, z0: o.z-rad, z1: o.z+rad, y: obsY, cyl: true, cx: o.x, cz: o.z, r: rad-1 });
    var curtain = new T.Mesh(new T.CylinderGeometry(rad-0.5, rad-0.5, 8, 16, 1, true),
      new T.MeshLambertMaterial({ color: 0x9fc4e8, transparent: true, opacity: 0.35, side: T.DoubleSide }));
    curtain.position.y = obsY+4; g.add(curtain);
    box(6, 1, 6, gold, 0, obsY+0.5, 0); solid(0, 0, 6, 6, obsY, obsY+1);
    var plq3 = new T.Mesh(new T.PlaneGeometry(10,2.5),
      new T.MeshBasicMaterial({ map: o.textPlate('THE OBSERVATORY'), side: T.DoubleSide }));
    plq3.position.set(0, obsY+6, 0); g.add(plq3);
  }

  // ---- PORTHOLES: mounted by caller on upper facades ----
  g.userData.portholes = portholes;
  g.userData.portTex = portTex;
  root.add(g);
  return g;
}
// Add glowing porthole discs to a facade (call after podium/tower built)
function addPortholes(T, parent, portTex, list) {
  // list: [{x,y,z,ry,r}]
  var mat = new T.MeshBasicMaterial({ map: portTex, transparent: true, side: T.DoubleSide });
  list.forEach(function(p){
    var m = new T.Mesh(new T.CircleGeometry(p.r||3.5, 24), mat);
    m.position.set(p.x, p.y, p.z); m.rotation.y = p.ry||0;
    m.name = 'porthole'; parent.add(m);
  });
}

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.