<!-- RUBY-BACKUP · part 342 -->
 }
    }, {passive:true});
    canvas.addEventListener('touchend', function(e){
      if(e.touches.length<2) lastPinchDist = 0;
    }, {passive:true});
  }
  findCanvas();
})();


===== FILE: public/ruby-backup/ruby.js.backup-v810 =====
var SOLID_SURFACES = [];
(function(){
'use strict';
/* BARE RUBY — the ruby, the grid, tap to place one stone. No menus at all. */
var PLATEAU = {x:8, z:8, r:2.5, h:0.9}; /* reality 1 — our ground, born first */
window.addEventListener('error',function(e){
  try{
    var b=document.getElementById('boot');
    if(b){ b.style.display='flex'; b.textContent='RUBY STUMBLED: '+String((e&&e.message)||'unknown').slice(0,140); }
  }catch(ee){}
});
var T = window.THREE;
if(!T){ document.getElementById('boot').textContent='NO LIGHT — THREE MISSING'; return; }
try{
  var _c=document.createElement('canvas');
  if(!(_c.getContext('webgl')||_c.getContext('experimental-webgl')))
    throw new Error('no webgl in this browser');
}catch(e){
  document.getElementById('boot').textContent='THIS BROWSER HAS NO WEBGL';
  return;
}

/* ---- Minimum: the King's eye, 0.13. Nothing is created smaller, by default. ---- */
window.MINIMUM = 0.13;
window.MINIMUM_OFF = window.MINIMUM_OFF || false;
window.applyMinimum = function(obj){
  if(window.MINIMUM_OFF || !obj) return obj;
  var box = new T.Box3().setFromObject(obj);
  var size = new T.Vector3(); box.getSize(size);
  var smallest = Math.min(size.x, size.y, size.z);
  if(smallest > 0 && smallest < window.MINIMUM){
    var k = window.MINIMUM / smallest;
    obj.scale.multiplyScalar(k);
  }
  return obj;
};
var shapeK1 = {};   /* legal scale of each shape's 1-ruby base — filled by legalK1 */

var CELL = 2;                 /* placement still snaps to the 2-unit grid */
var LSKEY = 'ruby-builds-v1';
/* watch / shared room: ?watch=<id> streams the field to the box; ?watch=<id>&view=1
   only watches. with just ?watch=<id> it is a shared room — we play together there:
   both sides poll, both sides post, the newest field wins. */
var _wq = {};
try{ location.search.slice(1).split('&').forEach(function(p){ var kv=p.split('='); _wq[decodeURIComponent(kv[0])]=decodeURIComponent(kv[1]||''); }); }catch(e){}
var WATCH_ID = /^[a-z0-9-]{1,32}$/.test(_wq.watch||'') ? _wq.watch : '';
var VIEW_ONLY = !!WATCH_ID && _wq.view==='1';
var SHARED = !!WATCH_ID && !VIEW_ONLY;
var watchT0 = 0, watchT = null;
function watchCells(){
  return stones.children.map(function(s){ var u=s.userData; return {x:s.position.x, z:s.position.z, r:u.rubies||15, s:u.shape||'cube', v:u.ghost?1:0, y:u.y||0, l:u.land?1:0}; });
}
function broadcastWatch(){
  if(!SHARED) return;
  if(watchT) clearTimeout(watchT);
  watchT = setTimeout(function(){
    try{
      fetch('/api/ruby/watch/'+encodeURIComponent(WATCH_ID), {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({stones:watchCells()})}).catch(function(){});
    }catch(e){}
  }, 5000);
}
function pullWatch(){
  if(!WATCH_ID) return;
  fetch('/api/ruby/watch/'+encodeURIComponent(WATCH_ID)).then(function(r){ return r.json(); }).then(function(d){
    if(d && d.ok && d.t && d.t!==watchT0){
      watchT0 = d.t;
      while(stones.children.length) stones.remove(stones.children[0]);
      (d.stones||[]).forEach(function(c){ placeStone(c.x, c.z, c.r, c.s+(c.v?'~ghost':''), true, true, c.y||0, !!c.l); });
      clearSelection(); updateAnchors();
    }
  }).catch(function(){});
}
/* presence — if I am an avatar, I am a circle: every visitor in the room is a
   colored ring on the ground at their cursor, so you can see where I am. */
var CLIENT_ID = 'c-' + Math.random().toString(36).slice(2, 10);
var lastSolidCode = '';
var CLIENT_COLOR = ['ffd700','7df9ff','ff6b9d','9dff6b','c39bff'][Math.floor(Math.random()*5)];
function postPresence(){
  if(!WATCH_ID) return;
  try{
    fetch('/api/ruby/presence/'+encodeURIComponent(WATCH_ID), {method:'POST', headers:{'Content-Type':'application/json'},
      body:JSON.stringify({client:CLIENT_ID, avatar:AVATAR, wear:myWearName||'',
        x:WALK?mePos.x:((typeof cursor!=='undefined'&&cursor)?cursor.x:0),
        z:WALK?mePos.z:((typeof cursor!=='undefined'&&cursor)?cursor.z:0),
        y:WALK?mePos.y:null,
        color:CLIENT_COLOR})}).catch(function(){});
  }catch(e){}
}
function pullPresence(){
  if(!WATCH_ID) return;
  fetch('/api/ruby/presence/'+encodeURIComponent(WATCH_ID)).then(function(r){ return r.json(); }).then(function(d){
    if(!d || !d.ok) return;
    var seen = {};
    Object.keys(d.clients).forEach(function(cid){
      if(cid===CLIENT_ID) return;
      seen[cid] = true;
      var c = d.clients[cid];
      var ra = remoteAvatars[cid];
      var kind = (c.avatar==='king') ? 'king' : 'circle';
      if(!ra){
        ra = {grp: kind==='king' ? buildKingAvatar() : buildCircleAvatar(parseInt(c.color,16)||0xffd700),
              kind:kind, last:new T.Vector3(c.x,0,c.z), wearName:null, wearGrp:null, phase:Math.random()*6.28};
        presenceGrp.add(