<!-- SOLID CITY · part 2094 -->
r built=[];
  var types=[0,1,2,3];
  for(i=0;i<spots.length;i++){
    var h=150+R()*250; // 150-400m
    var tp=types[i%4];
    // taller near center: scale by proximity
    var dc=Math.hypot(spots[i][0],spots[i][1]);
    if(dc<120)h=Math.min(400,h+60);
    built.push(tower(spots[i][0],spots[i][1],h,tp));
  }
  // ---- INTERIOR PODIUMS (Phase 1): 2 towers + spire get REAL interiors ----
  var elevators=[], standFloors=[];
  var podiumOpts={ R:R, glassA:glassA, glassB:glassB, gold:gold, steel:steel, textPlate:function(name){ return textPlate(T, name); } };
  // Pick: first slab (type1) and first crown (type3) for box podiums
  var pickA=null, pickB=null;
  for(var pi=0;pi<built.length;pi++){
    if(!pickA&&built[pi].tp===1)pickA=built[pi];
    if(!pickB&&built[pi].tp===3)pickB=built[pi];
  }
  [pickA,pickB].forEach(function(tb,idx){
    if(!tb)return;
    tb.g.position.y=28; // lift tower onto podium
    // fix footprint: was [x,z,w,d,0,h,0], now starts at 60
    for(var fi=0;fi<foot.length;fi++){
      if(Math.abs(foot[fi][0]-tb.x)<1&&Math.abs(foot[fi][1]-tb.z)<1&&foot[fi][4]===0){
        foot[fi][4]=28; break;
      }
    }
    buildInteriorPodium(T,root,foot,elevators,standFloors,Object.assign({},podiumOpts,{
      x:tb.x,z:tb.z,w:50,d:50,h:28,floors:[0,14,26],
      name:idx===0?'TOWER ONE LOBBY':'TOWER TWO LOBBY'
    }));
  });
  // skybridges: connect 4 pairs at ~60% height
  var bridges=0;
  for(i=0;i<built.length&&bridges<4;i++){
    for(var j2=i+1;j2<built.length;j2++){
      var dd=Math.hypot(built[i].x-built[j2].x,built[i].z-built[j2].z);
      if(dd>55&&dd<95){
        var by=Math.min(built[i].h,built[j2].h)*0.62;
        var mx=(built[i].x+built[j2].x)/2, mz=(built[i].z+built[j2].z)/2;
        var bl=Math.hypot(built[i].x-built[j2].x,built[i].z-built[j2].z);
        var br=new T.Mesh(new T.BoxGeometry(bl,5,7),glassB);
        br.position.set(mx,by,mz);
        br.rotation.y=-Math.atan2(built[j2].z-built[i].z,built[j2].x-built[i].x);
        root.add(br);
        bridges++;
        break;
      }
    }
  }

  // ---- THE HYPER SPIRE (460m) at plaza north edge ----
  var spire=new T.Group();spire.position.set(0,0,-58);
  var sh=460;
  var s1=new T.Mesh(new T.CylinderGeometry(14,26,sh*0.7,12),glassA);s1.position.y=sh*0.35;spire.add(s1);
  var s2=new T.Mesh(new T.CylinderGeometry(8,14,sh*0.22,12),glassB);s2.position.set(0,sh*0.7+sh*0.11,0);spire.add(s2);
  var s3=new T.Mesh(new T.CylinderGeometry(0.8,6,sh*0.08+30,8),gold);s3.position.set(0,sh*0.92+sh*0.04+10,0);spire.add(s3);
  // gold crown rings
  [0.55,0.7,0.82].forEach(function(f){
    var r=new T.Mesh(new T.TorusGeometry(20*(1-f*0.4)+8,1.8,8,32),goldTrim);
    r.rotation.x=Math.PI/2;r.position.y=sh*f;spire.add(r);
  });
  // beacon
  var beacon=new T.Mesh(new T.SphereGeometry(3.2,12,10),new T.MeshBasicMaterial({color:0xffe8a0}));
  beacon.position.y=sh+32;beacon.name='beacon';spire.add(beacon);
  var beam=new T.Mesh(new T.CylinderGeometry(1.2,2.2,120,8,1,true),
    new T.MeshBasicMaterial({color:0xffe8a0,transparent:true,opacity:0.28,side:T.DoubleSide,depthWrite:false}));
  beam.position.y=sh+92;beam.name='beam';spire.add(beam);
  root.add(spire);
  foot.push([0,-58,56,56,36,sh+34+36,0]);
  // Spire interior podium: 70m dia cylinder, 95m tall, full interior + observatory
  spire.position.y=36; // lift spire onto podium (now 496m total — bigger, as the King loves)
  buildInteriorPodium(T,root,foot,elevators,standFloors,Object.assign({},podiumOpts,{
    x:0,z:-58,w:70,d:70,h:36,cyl:true,floors:[0,18,32],
    name:'THE SPIRE LOBBY'
  }));
  var smark=quietMarker(T,'THE HYPER SPIRE');smark.position.set(24,0,-58);root.add(smark);


  // ---- PORTHOLES: the upper facades become portal arrays ----
  // (King: "the rest will be port holes aka portals")
  (function(){
    var pm = new T.MeshBasicMaterial({ map: null, transparent: true, side: T.DoubleSide });
    // Build a shared porthole texture once
    var pc = document.createElement('canvas'); pc.width = pc.height = 128;
    var px = pc.getContext('2d');
    var pgr = px.createRadialGradient(64,64,4,64,64,64);
    pgr.addColorStop(0,'#fff8e8'); pgr.addColorStop(0.25,'#f2c86a');
    pgr.addColorStop(0.5,'#9a6ae8'); pgr.addColorStop(0.75,'#4a6ae8'); pgr.addColorStop(1,'#1a1a3a');
    px.fillStyle = pgr; px.beginPath(); px.arc(64,64,64,0,7); px.fill();
    px.strokeStyle = '#f2c86a'; px.lineWidth = 3;
    for (var pr = 12; pr < 64; pr += 13) { px.beginPath(); px.arc(64,64,pr,0,7); px.stroke(); }
    var ptex = new T.CanvasTexture(pc); ptex.encoding = T.sRGBEncoding;
    var pmat = new T.MeshBasicMaterial({ map: ptex, transparent: true, side: T.DoubleSide });
    window._hyperPortholes = [];
    // Portholes on the 2 podium towers (above podium, every 25m)
    [pickA, pickB].forEach(function(tb){
      if(!tb) return;
      var baseY = 28; // podium top
      for (var py = baseY+18; py < baseY+tb.h; py += 26) {
        for (var f = 0; f < 4; f++) {
          var ang = f