<!-- RUBY-BACKUP · part 278 -->
buf.push({dx:dx, dz:dz, r:st.r, s:st.s, y:st.y||0, c:st.c||''});
        }
      }
    }catch(e){}
    setCopyBuf(buf);
    try{
      var roomId = (typeof WATCH_ID!=='undefined' ? WATCH_ID : (location.search.match(/watch=([^&]+)/)||[])[1] || 'whatever');
      fetch('/api/ruby/copybuf/'+roomId, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({buf:buf})}).catch(function(){});
    }catch(e){}
    codeStatus('copied '+buf.length+' stones'); return; }
  if(tl.indexOf('paste')===0){
    var pp=tl.split(/\s+/); var pdx=parseFloat(pp[1])||0, pdz=parseFloat(pp[2])||0;
    var buf=getCopyBuf();
    if(!buf.length){
      try{
        var roomId = (typeof WATCH_ID!=='undefined' ? WATCH_ID : (location.search.match(/watch=([^&]+)/)||[])[1] || 'whatever');
        var xhr=new XMLHttpRequest(); xhr.open('GET','/api/ruby/copybuf/'+roomId,false); xhr.send();
        var d=JSON.parse(xhr.responseText||'{}'); buf=d.buf||[]; setCopyBuf(buf);
      }catch(e){}
    }
    if(!buf.length){ codeStatus('nothing copied'); return; }
    try{
      for(var i=0; i<buf.length; i++){
        var b=buf[i];
        placeStone(cursor.x+b.dx+pdx, cursor.z+b.dz+pdz, b.r, b.s, true, true, b.y, true);
      }
    }catch(e){}
    codeStatus('pasted '+buf.length+' stones'); return; }
  /* IMAGE TO 3D (heightmap from image brightness) */
  function renderImage3D(img, isize, ithresh){
    try{
      var cv=document.createElement('canvas'); cv.width=isize; cv.height=isize;
      var cx2=cv.getContext('2d'); cx2.drawImage(img,0,0,isize,isize);
      var d=cx2.getImageData(0,0,isize,isize).data;
      var ox=cursor.x-Math.floor(isize/2), oz=cursor.z-Math.floor(isize/2);
      var n=0;
      for(var yy=0; yy<isize; yy++){
        for(var xx=0; xx<isize; xx++){
          var i=(yy*isize+xx)*4;
          var br=(d[i]+d[i+1]+d[i+2])/3/255;
          if(br < ithresh) continue;
          var h=Math.round(br*8)+1;
          var hex=((1<<24)+(d[i]<<16)+(d[i+1]<<8)+d[i+2]).toString(16).slice(1);
          try{ placeStone(ox+xx, oz+yy, h, 'cube', true, true, 0, true, hex); }
          catch(e){ placeStone(ox+xx, oz+yy, h, 'cube', true, true, 0, true); }
          n++;
        }
      }
      codeStatus('image3d: '+n+' stones');
    }catch(e){ codeStatus('image3d failed: '+e.message); }
  }
  if(tl.indexOf('image3d')===0){
    var ip=tl.split(/\s+/); var iurl=ip[1]; var isize=parseInt(ip[2])||20; var ith=parseFloat(ip[3]); if(!(ith>=0)) ith=0.1;
    if(!iurl){ codeStatus('usage: image3d <url> [size] [threshold]'); return; }
    if(isize>40) isize=40;
    codeStatus('loading image...');
    var img=new Image(); img.crossOrigin='anonymous';
    img.onload=function(){ renderImage3D(img, isize, ith); };
    img.onerror=function(){ codeStatus('image3d: could not load image'); };
    img.src=iurl;
    return; }
  if(tl==='upload3d'){
    var fi=document.getElementById('img3d-upload');
    if(!fi){
      fi=document.createElement('input'); fi.type='file'; fi.accept='image/*'; fi.id='img3d-upload';
      fi.style.display='none';
      fi.onchange=function(){
        var f=fi.files[0]; if(!f) return;
        var rd=new FileReader();
        rd.onload=function(){
          var img=new Image();
          img.onload=function(){ renderImage3D(img, 24, 0.1); };
          img.src=rd.result;
        };
        rd.readAsDataURL(f);
      };
      document.body.appendChild(fi);
    }
    fi.click();
    codeStatus('choose an image');
    return; }
  /* SKINS & COLOR PATTERNS */
  var SKIN_KEY='ruby-skin';
  function getSkin(){ try{ return localStorage.getItem(SKIN_KEY)||''; }catch(e){ return ''; } }
  function setSkin(s){ try{ localStorage.setItem(SKIN_KEY,s); }catch(e){} }
  var SKINS={'oak':'8b5a2b', 'pine':'c8a060', 'walnut':'5c3a21', 'cherry':'8c2f1b', 'mahogany':'6e2a1a', 'birch':'d8c8a0', 'cedar':'a06030', 'ebony':'1a1a1a', 'teak':'7a5a30', 'bamboo':'b8a050', 'driftwood':'9a8a70', 'redwood':'8a3a20', 'ash':'8a8a8a', 'hickory':'7a5a3a', 'maple':'d8b070', 'elm':'6a4a2a', 'fir':'5a4a30', 'spruce':'4a5a3a', 'larch':'8a6a40', 'yew':'3a2a1a', 'granite':'7a7a7a', 'limestone':'d8d0b8', 'sandstone':'e0b880', 'basalt':'2a2a2a', 'marble':'f5f5f0', 'slate':'3a3f4a', 'quartz':'e8e8e8', 'obsidian':'0a0a0a', 'alabaster':'f0e8d8', 'travertine':'d8c8a8', 'gneiss':'8a7a6a', 'shale':'5a5a5a', 'chalk':'f8f8f0', 'flint':'4a4a4a', 'jasper':'a03a2a', 'agate':'8a6a8a', 'onyx':'1a1a1a', 'turquoise':'40c0c0', 'lapis':'2020a0', 'jade':'2a8a5a', 'gold':'ffd700', 'silver':'c0c0c0', 'copper':'b87333', 'bronze':'8c6a3a', 'iron':'5a5a5a', 'steel':'8a8a9a', 'brass':'d4a017', 'pewter':'8a8a8a', 'nickel':'a0a0a0', 'zinc':'9a9a8a', 'titanium':'b0b0c0', 'platinum':'e0e0e0', 'cobalt':'2a4aa0', 'chrome':'d8d8e0', 'rust':'8a3a1a', 'verdigris':'4aa08a', 'electrum':'d8c86a', 'mithril':'c0e0e0', 'adamant':'3a3a4a', 'orichalcum':'c08a3a', 'velvet':'6a1a3a', 'silk':'e8d8e8', 'linen':'e0d8c0', 'cotton':'f0f0f0', 'wool':'c8b8a8', 'denim':'2a4a7a', 'leat