<!-- RUBY-BACKUP · part 151 -->
r sh = buildShoe();
  sh.position.set(x, groundY(x, z), z);
  sh.rotation.y = ry||0;
  scene.add(sh);
  if(typeof monumentShoes==='undefined' || !monumentShoes) monumentShoes = [];
  monumentShoes.push(sh);
  persistShoes();
  return sh;
}
function loadShoes(){
  var ks = [];
  try{ ks = JSON.parse(localStorage.getItem('ruby-shoes-v1')||'[]'); }catch(e){}
  ks.forEach(function(k){
    var sh = spawnShoe(k.x, k.z, k.r);
    if(sh && typeof k.y==='number' && isFinite(k.y)) sh.position.y = k.y;
  });
}
function showBrain(){
  var st2 = document.getElementById('codetext-status');
  if(!st2) return;
  st2.innerHTML = '<div style="color:#ffd700;letter-spacing:2px;margin-bottom:4px;">BRAIN — every code</div>' +
    BRAIN.map(function(b){ return '<div style="padding:2px 0;"><b style="color:#ffd700;">'+escapeHtml(b[0])+'</b> — '+escapeHtml(b[1])+'</div>'; }).join('');
  st2.style.whiteSpace = 'normal'; st2.style.opacity = '1'; st2.style.pointerEvents = 'auto';
  if(codeT) clearTimeout(codeT);
}
function showDid(){
  if(!didLog.length){ codeStatus('nothing yet — build something'); return; }
  var st2 = document.getElementById('codetext-status');
  if(!st2) return;
  st2.innerHTML = didLog.map(function(e,i){
    return '<div onclick="didPick('+i+')" style="padding:2px 0;cursor:pointer;">'+(i+1)+'. '+escapeHtml(e.line)+' \u2014 '+escapeHtml(e.res)+'</div>';
  }).join('');
  st2.style.whiteSpace = 'normal'; st2.style.opacity = '1'; st2.style.pointerEvents = 'auto';
  if(codeT) clearTimeout(codeT);
}
var didAnchorLine = null, awaitingDidAnchor = false;
function didPick(i){
  var e = didLog[i];
  if(!e) return;
  didAnchorLine = e.line; awaitingDidAnchor = true;
  codeStatus('tap the anchor stone for: '+e.line);
}
function didAnchorTo(grp){
  awaitingDidAnchor = false;
  cursor.x = grp.position.x; cursor.z = grp.position.z; saveCursor(); paintCursor();
  var l = didAnchorLine; didAnchorLine = null;
  runLine(l);
}
/* send your build to your friends: `send myfence` turns the save into a code
   (copied to the bar, ready to copy). a friend types `take <code>` and the
   build joins their library. no server — the code IS the build. */
function shareEncode(name){
  var sv = SAVES[name];
  if(!sv) return null;
  var b64 = btoa(unescape(encodeURIComponent(JSON.stringify({n:name, s:sv.stones}))));
  return 'RUBY1.' + b64.replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');
}
function shareDecode(code){
  code = String(code||'').trim();
  if(code.indexOf('RUBY1.')!==0) return null;
  try{
    var b64 = code.slice(6).replace(/-/g,'+').replace(/_/g,'/');
    var p = JSON.parse(decodeURIComponent(escape(atob(b64))));
    if(!p || typeof p.n!=='string' || !/^[a-z][a-z0-9]*$/.test(p.n)) return null;
    if(!Array.isArray(p.s) || !p.s.length || p.s.length>500) return null;
    var known = {cube:1,pyramid:1,sphere:1,cyl:1,torus:1,cone:1,octa:1,tetra:1,icosa:1,dodeca:1,tri:1,pdown:1,poff:1,poffdown:1};
    for(var i=0;i<p.s.length;i++){ var st=p.s[i];
      if(!isFinite(st.dx)||!isFinite(st.dz)||!isFinite(st.y)||!isFinite(st.r)) return null;
      var bs = String(st.s).split('~')[0];
      if(!known[bs] || (st.s!==bs && st.s!==bs+'~ghost')) return null;
      if((st.v!==0&&st.v!==1)) return null;
      if(!(st.r>=1&&st.r<=200)) return null;
    }
    return p;
  }catch(e){ return null; }
}
function saveOps(name, ec){
  var sv = SAVES[name].stones, o = [];
  for(var i=0;i<sv.length;i++){ var c=sv[i];
    o.push({shape:c.s+(c.v?'~ghost':''), size:c.r, x:ec.x+c.dx, z:ec.z+c.dz, y:c.y||0, proc:name});
  }
  return o;
}
/* oops-my-bad: surgical undo for huge intricate builds. arm with the words,
   tap one anchor stone — every stone connected to it is offered:
   `cut` removes them, `keep` releases them. */
var awaitingAnchor = false;
var selection = [];
var selHelpers = new T.Group(); scene.add(selHelpers);
function clearSelection(){
  selection = [];
  while(selHelpers.children.length) selHelpers.remove(selHelpers.children[0]);
}
function stoneTouches(a, b){
  var au=a.userData, bu=b.userData;
  if(Math.max(Math.abs(a.position.x-b.position.x), Math.abs(a.position.z-b.position.z)) > 2.01) return false;
  var ay=au.y||0, ah=(au.rubies||1)*(au.k1||1);
  var by=bu.y||0, bh=(bu.rubies||1)*(bu.k1||1);
  return ay <= by+bh+0.01 && by <= ay+ah+0.01;
}
function floodFrom(anchor){
  var seen=[anchor], queue=[anchor];
  while(queue.length){
    var a=queue.pop();
    for(var i=0;i<stones.children.length;i++){
      var b=stones.children[i];
      if(seen.indexOf(b)>=0) continue;
      if(stoneTouches(a,b)){ seen.push(b); queue.push(b); }
    }
  }
  return seen;
}
function selectConnected(anchor){
  if(!anchor){ codeStatus('no anchor'); return; }
  clearSelection();
  selection = floodFrom(anchor);
  for(var i=0;i<selection.length;i++) selHelpers.add(new T.BoxHelper(selection[i], 0xffd700));
  var au = anchor.userData, abase = au.shape, aghost = !!au.ghost, nm = 0;
  for(var j=0;j<selection.length;j++){ var su = s