<!-- RUBY-BACKUP · part 346 -->
0; fi<n; fi++){
        var fg = new T.Group();
        fg.userData = {stone:true, rubies:1, shape:'cube', ghost:false, k1:legalK1('cube'), y:0, land:true, filler:true};
        fg.position.set(fx, fh + fi*window.MINIMUM, fz);
        buildStoneVisual(fg);
        stones.add(fg);
      }
    });
  }
  buildStoneVisual(g);
  stones.add(g);
  if(!instant){ g.scale.set(0.01,0.01,0.01); anims.push({m:g, t:0}); if(!quiet) showShapeCode(g); }
  save();
  return g;
}
function growStone(g){
  /* each tap grows the shape by one ruby, no ceiling */
  g.userData.rubies = (g.userData.rubies||1) + 1;
  buildStoneVisual(g); save(); showShapeCode(g);
}
function shrinkStone(g){
  /* each tap makes the shape one ruby smaller, until nothing */
  var r = (g.userData.rubies||1) - 1;
  if(r < 1){ liftStone(g); return; }
  g.userData.rubies = r;
  buildStoneVisual(g); save(); showShapeCode(g);
}
function liftStone(g){
  stones.remove(g); save();
}
var anims = [];
var dying = []; /* self-destruct: stones crumbling out */
function selfDestruct(){
  /* the whole frame self-destructs — every stone crumbles at once.
     bodies (his, fellow travelers') live outside the stones group: untouched. */
  var fs = [];
  for(var fi=0; fi<stones.children.length; fi++){
    var st0 = stones.children[fi], u0 = st0.userData;
    fs.push({x:st0.position.x, z:st0.position.z, r:u0.rubies||1, s:u0.shape||'cube', v:u0.ghost?1:0, y:u0.y||0, p:u0.proc||null});
    dying.push({m:st0, t:0, sx:st0.scale.x, sy:st0.scale.y, sz:st0.scale.z});
  }
  if(fs.length){ undoStack.push({victim:'field', killer:null, mass:0, stones:fs}); if(undoStack.length>10) undoStack.shift(); saveUndo(); }
  clearSelection(); updateAnchors();
  return fs.length;
}
function easeOutBack(t){ var c=1.70158; return 1 + (c+1)*Math.pow(t-1,3) + c*Math.pow(t-1,2); }

/* the radiating intaglio disc, laid on the ground under the largest structure */
function makeRadiateTex(){
  var c = document.createElement('canvas'); c.width = c.height = 512;
  var x = c.getContext('2d');
  x.clearRect(0,0,512,512);
  x.strokeStyle = 'rgba(255,215,0,0.6)'; x.lineWidth = 1.5;
  for(var r=14;r<256;r+=12){ x.beginPath(); x.arc(256,256,r,0,Math.PI*2); x.stroke(); }
  x.strokeStyle = 'rgba(255,215,0,0.35)';
  for(var a=0;a<48;a++){
    var t = a/48*Math.PI*2;
    x.beginPath(); x.moveTo(256+Math.cos(t)*14, 256+Math.sin(t)*14);
    x.lineTo(256+Math.cos(t)*250, 256+Math.sin(t)*250); x.stroke();
  }
  x.strokeStyle = 'rgba(255,215,0,0.22)';
  for(var r2=20;r2<256;r2+=12){
    x.beginPath();
    for(var a2=0;a2<=Math.PI*2+0.02;a2+=0.06){
      var rr = r2 + Math.sin(a2*12)*4;
      var p = [256+Math.cos(a2)*rr, 256+Math.sin(a2)*rr];
      if(a2===0) x.moveTo(p[0],p[1]); else x.lineTo(p[0],p[1]);
    }
    x.stroke();
  }
  return new T.CanvasTexture(c);
}
var radiateDisc = new T.Mesh(
  new T.CircleGeometry(30, 48),
  new T.MeshBasicMaterial({map:makeRadiateTex(), transparent:true, opacity:0.85, depthWrite:false})
);
radiateDisc.rotation.x = -Math.PI/2;
radiateDisc.position.y = 0.09;
radiateDisc.visible = false;
scene.add(radiateDisc);

/* anchor nodes: the interconnection points between nearby structures.
   used correctly, they are anchors for building — tap one to raise a shape there. */
var anchorGroup = new T.Group(); scene.add(anchorGroup);
var anchorMat = new T.MeshBasicMaterial({color:0xffe27a});
function updateAnchors(){
  for(var i=anchorGroup.children.length-1;i>=0;i--){ anchorGroup.children[i].geometry.dispose(); anchorGroup.remove(anchorGroup.children[i]); }
  if(renderMode!=='nested') return;
  var ss = stones.children;
  for(var a=0;a<ss.length;a++) for(var b=a+1;b<ss.length;b++){
    var A = ss[a], B = ss[b];
    var sa = (A.userData.rubies||1)*(A.userData.k1||1)*window.MINIMUM;
    var sb = (B.userData.rubies||1)*(B.userData.k1||1)*window.MINIMUM;
    var dx = B.position.x-A.position.x, dz = B.position.z-A.position.z;
    var d = Math.sqrt(dx*dx+dz*dz);
    if(d < (sa+sb)*0.5 + 2 && d > 0.01){
      var node = new T.Mesh(new T.SphereGeometry(0.2, 12, 10), anchorMat);
      node.position.set((A.position.x+B.position.x)/2, (A.position.y+B.position.y)/2 + (sa+sb)/4, (A.position.z+B.position.z)/2);
      anchorGroup.add(node);
    }
  }
}

/* restore what was built before (boxes from before sizes are kept at 15 rubies ≈ their 2 units) */
if(!WATCH_ID){
  try{
    var prev = JSON.parse(localStorage.getItem(LSKEY)||'[]');
    prev.forEach(function(c){ if(c && isFinite(c.x) && isFinite(c.z)) placeStone(c.x, c.z, c.r||15, (c.s||'cube')+(c.v?'~ghost':''), true, false, c.y||0, !!c.l); });
  }catch(e){}
}
/* locked anchors — tap stones to lock them in as anchor points. gold rings mark them;
   every code, track, and did row snaps to them. */
var lockedAnchors = [];
var awaitingLock = false, awaitingUnlock = false;
var anchorGrp = new T.Group(); scene.add(anchorGrp);
var portals = [];
var awaitingPortal = null, awaitingUnportal = false;
var portalGrp = new T.Group(); scene.a