<!-- RUBY-BACKUP · part 400 -->
;
  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;
}
function runLine(line){
  var tl = line.trim().toLowerCase();
  var ec = {x:(typeof cursor!=='undefined'&&cursor)?cursor.x:0, z:(typeof cursor!=='undefined'&&cursor)?cursor.z:0};
  if(tl==='did'){ showDid(); return; }
  if(tl==='lock'){ awaitingLock=true; awaitingUnlock=false; codeStatus('tap the anchor stone to lock it in'); return; }
  if(tl==='unlock'){
    if(!lockedAnchors.length){ codeStatus('none locked'); return; }
    awaitingUnlock=true; awaitingLock=false; codeStatus('tap a locked anchor to release it'); return;
  }
  if(tl==='anchors'){ codeStatus(lockedAnchors.length ? lockedAnchors.length+' locked' : 'none locked'); return; }


/* MAP LAW: no map until you meet PROVOLTA */
var MET_PROVOLTA = false;
try{ MET_PROVOLTA = localStorage.getItem('ruby-met-provolta')==='1'; }catch(e){}
function markMetProvolta(){
  try{
    if(!MET_PROVOLTA){
      MET_PROVOLTA = true;
      localStorage.setItem('ruby-met-provolta', '1');
      try{ codeStatus('you have met PROVOLTA — the map is yours'); }catch(e){}
      try{ if(typeof giveProvoltaObjectives==='function') giveProvoltaObjectives(); }catch(e){}
    }
  }catch(e){}
}


/* THE ROBOT: it finds you. It loops until you stand still. */
var NEWPLAYER_ROBOT = null;
var ROBOT_STATE = 'waiting'; /* waiting, chasing, analyzing, done */
var ROBOT_LAST_POS = null;
var ROBOT_STILL_TIME = 0;
var ROBOT_DIALOGUE_IDX = 0;
var ROBOT_LINES = [
  'NEW PLAYER! NEW PLAYER!',
  'I see you. Come here.',
  'NEW PLAYER! NEW PLAYER!',
  'Stop running away!',
  'I have to converse with you.',
  'My programmers told me to tell you.',
  'You have to hurry up right now.',
  'Come here I need to tell you something.',
  'NEW PLAYER! NEW PLAYER!'
];
function buildNewPlayerRobot(){
  try{
    if(NEWPLAYER_ROBOT) return;
    NEWPLAYER_ROBOT = new THREE.Group();
 