<!-- RUBY-BACKUP · part 152 -->
election[j].userData; if(su.shape===abase && !!su.ghost===aghost) nm++; }
  codeStatus(selection.length+' connected · '+nm+' '+abase+(aghost?' ghost':'')+' — `swap <code>` restyles · `cut` removes · `keep` releases');
}
function swapSelection(code){
  if(!selection.length){ codeStatus('nothing selected'); return; }
  var key = codeToShapeKey(code);
  if(!key){ codeStatus('refused · unknown code'); return; }
  var nbase = shapeBase(key), nghost = shapeIsGhost(key);
  var au = selection[0].userData, abase = au.shape, aghost = !!au.ghost, n = 0;
  for(var i=0;i<selection.length;i++){
    var s2 = selection[i], u = s2.userData;
    if(u.shape!==abase || !!u.ghost!==aghost) continue;
    u.shape = nbase; u.ghost = nghost; u.k1 = legalK1(nbase);
    buildStoneVisual(s2);
    n++;
  }
  save(); updateAnchors();
  codeStatus(n+' → '+nbase+(nghost?' ghost':'')+' — `swap <code>` again · `keep` releases');
}
function cutSelection(){
  if(!selection.length){ codeStatus('nothing selected'); return; }
  var st=[];
  for(var i=0;i<selection.length;i++){
    var s=selection[i], u=s.userData;
    st.push({x:s.position.x, z:s.position.z, r:u.rubies||1, s:u.shape||'cube', v:u.ghost?1:0, y:u.y||0, p:u.proc||null});
  }
  for(var j=0;j<selection.length;j++) stones.remove(selection[j]);
  var n=st.length;
  clearSelection();
  undoStack.push({victim:'selection', killer:null, mass:0, stones:st});
  if(undoStack.length>10) undoStack.shift();
  saveUndo(); save(); updateAnchors();
  codeStatus(n+' cut');
}
function runOps(ops, mass){
  var raised=0, stepped=0, cleared=0, newGs=[];
  var runProc = null, bi;
  for(bi=0;bi<ops.length;bi++) if(ops[bi].proc){ runProc = ops[bi].proc; break; }
  if(runProc){
    /* a new project claims its site: every previous project inside the new
       footprint is rendered obsolete, at the stone — the old code deletes,
       atomic, whatever its mass. newness decides, not mass. */
    var x0=1e9,x1=-1e9,z0=1e9,z1=-1e9;
    for(bi=0;bi<ops.length;bi++){ var bo=ops[bi];
      if(bo.x<x0)x0=bo.x; if(bo.x>x1)x1=bo.x; if(bo.z<z0)z0=bo.z; if(bo.z>z1)z1=bo.z; }
    var doomed = {};
    for(var s=0;s<stones.children.length;s++){
      var st=stones.children[s], sp=st.userData.proc;
      if(sp && sp!==runProc && st.position.x>=x0 && st.position.x<=x1 && st.position.z>=z0 && st.position.z<=z1) doomed[sp]=1;
    }
    for(var dp in doomed){ pushUndo(dp, runProc); cleared += deleteProcedure(dp); }
  }
  for(var i=0;i<ops.length;i++){
    var o=ops[i], oy=o.y||0;
    if(o.x===0 && o.z===0){ stepped++; continue; }        /* the ruby is sacred, at any height */
    if(o.remove){
      var t = stoneAt(o.x, o.z, oy);
      if(t){ stones.remove(t); }
      continue;
    }
    var e = stoneAt(o.x, o.z, oy);                         /* the law of space: 3D cells */
    if(e){
      var eu = e.userData;
      if(o.proc && eu.proc && eu.proc===o.proc){ stepped++; continue; }  /* own work: step over */
      if(o.proc){
        /* a new project renders the last project obsolete, at the stone:
           the old code deletes, atomic — newness decides, not mass. */
        if(eu.proc){ pushUndo(eu.proc, o.proc); cleared += deleteProcedure(eu.proc); }
        else { stones.remove(e); cleared++; }
      }else{
        /* hand work keeps the old mass rule — a tap never nukes a project */
        if(mass > stoneMass(e)){
          if(eu.proc){ pushUndo(eu.proc, null); cleared += deleteProcedure(eu.proc); }
          else { stones.remove(e); cleared++; }
        }else{ stepped++; continue; }
      }
    }
    var g = placeStone(o.x, o.z, o.size, o.shape, false, true, oy);
    if(g){ g.userData.proc = o.proc||null; g.userData.procMass = mass; raised++; newGs.push(g); }
  }
  save(); updateAnchors();
  if(newGs.length){ newGs.forEach(addSeamsFor); shareStones(newGs); }
  return {raised:raised, stepped:stepped, cleared:cleared};
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: HARDENING — so it never crashes
   validate first, build second · caps · bounds · finite numbers · fail soft.
   EDIT: the caps.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
var HARDEN = {MAX_SIZE:200, MAX_REPEAT:64, MAX_OPS:500, MAX_DEPTH:8, BOUND:50};
function validateOps(ops){
  if(ops.length > HARDEN.MAX_OPS) return 'too many stones';
  for(var i=0;i<ops.length;i++){
    var o=ops[i];
    if(!(isFinite(o.x) && isFinite(o.z))) return 'bad position';
    if(Math.abs(o.x)>HARDEN.BOUND || Math.abs(o.z)>HARDEN.BOUND) return 'beyond the map';
    if(o.remove) continue;
    if(!(o.size>=1 && o.size<=HARDEN.MAX_SIZE)) return 'size out of range';
    if(!((o.y||0)>=0 && (o.y||0)<=HARDEN.MAX_SIZE)) return 'height out of range';
    if(!o.shape) return 'no shape';
  }
  return null;
}

/* THE CREATOR HALL — Playa 2.0's Fix Station built into the world */
function buildCreatorHall(){
  try{
    var cx = 45, cz = 30; /* off zero lines, near city */
    var gy = 1500; /* HIGH IN THE SKY — 