<!-- SOLID CITY · part 2099 -->
, 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.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;
      var pole2=new T.Mesh(lampG,lampM);pole2.position.set(x,4.5,lz);root.add(pole2);
      var bulb2=new T.Mesh(new T.SphereGeometry(0.7,8,6),bulbM);bulb2.position.set(x,9.2,lz);root.add(bulb2);
      foot.push([x,lz,1,1,0,9,0]);
    });
  }
  // planters
  var planterM=new T.MeshLambertMaterial({color:0x3a3226});
  var leafM=new T.MeshLambertMaterial({color:0x2e6a34});
  for(i=0;i<16;i++){
    var px=-180+R()*360, pz=-160+R()*320;
    if(Math.abs(px)<30&&Math.abs(pz)<30)continue;
    if(px<-150)continue; // not on promenade
    var pl=new T.Mesh(new T.BoxGeometry(4,2,4),planterM);pl.position.set(px,1,pz);root.add(pl);
    var lf=new T.Mesh(new T.SphereGeometry(2.2,8,6),leafM);lf.position.set(px,3.4,pz);root.add(lf);
    foot.push([px,pz,4,4,0,2,0]);
  }

  // ---- THE 24 MEGATOWERS ----
  // jittered grid positions, avoiding plaza (r<45) and promenade (x<-150)
  var spots=[];
  var gx,gz,tries=0;
  while(spots.length<24&&tries<400){
    tries++;
    gx=-190+R()*380; gz=-170+R()*340;
    if(Math.hypot(gx,gz)<55)continue;          // plaza
    if(gx<-150)continue;                        // promenade
    var ok=true;
    for(var s=0;s<spots.length;s++){ if(Math.hypot(gx-spots[s][0],gz-spots[s][1])<52){ok=false;break;} }
    if(ok)spots.push([gx,gz]);
  }
  function tower(x,z,h,type){
    var g=new T.Group();g.position.set(x,0,z);
    var w=18+R()*22, d=18+R()*22;
    var mat=R()<0.5?glassA:glassB;
    if(type===0){ // needle: tapered
      var nd=new T.Mesh(new T.CylinderGeometry(w*0.28,w*0.55,h,8),mat);
      nd.position.y=h/2;g.add(nd);
      var tip=new T.Mesh(new T.CylinderGeometry(0.8,2.5,26,6),gold);
      tip.position.y=h+13;g.add(tip);
      foot.push([x,z,w*1.2,w*1.2,0,h+26,0]);
    }else if(type===1){ // slab with golden-ratio setbacks
      var w1=w,d1=d,h1=h*0.55;
      var b1=new T.Mesh(new T.BoxGeometry(w1,h1,d1),mat);b1.position.y=h1/2;g.add(b1);
      var w2=w1/PHI,d2=d1/PHI,h2=h*0.3;
 