<!-- RUBY-BACKUP · part 98 -->
1]);
    }
    x.stroke();
  }
  return new T.CanvasTexture(c);
}
var radiateDisc = new T.Mesh(
  new T.CircleGeometry(30, 48),
  new T.MeshBasicMaterial({map:makeRadiateTex(), transparent:true, opacity:0.85, depthWrite:false})
);
radiateDisc.rotation.x = -Math.PI/2;
radiateDisc.position.y = 0.09;
radiateDisc.visible = false;
scene.add(radiateDisc);

/* anchor nodes: the interconnection points between nearby structures.
   used correctly, they are anchors for building — tap one to raise a shape there. */
var anchorGroup = new T.Group(); scene.add(anchorGroup);
var anchorMat = new T.MeshBasicMaterial({color:0xffe27a});
function updateAnchors(){
  for(var i=anchorGroup.children.length-1;i>=0;i--){ anchorGroup.children[i].geometry.dispose(); anchorGroup.remove(anchorGroup.children[i]); }
  if(renderMode!=='nested') return;
  var ss = stones.children;
  for(var a=0;a<ss.length;a++) for(var b=a+1;b<ss.length;b++){
    var A = ss[a], B = ss[b];
    var sa = (A.userData.rubies||1)*(A.userData.k1||1)*window.MINIMUM;
    var sb = (B.userData.rubies||1)*(B.userData.k1||1)*window.MINIMUM;
    var dx = B.position.x-A.position.x, dz = B.position.z-A.position.z;
    var d = Math.sqrt(dx*dx+dz*dz);
    if(d < (sa+sb)*0.5 + 2 && d > 0.01){
      var node = new T.Mesh(new T.SphereGeometry(0.2, 12, 10), anchorMat);
      node.position.set((A.position.x+B.position.x)/2, (A.position.y+B.position.y)/2 + (sa+sb)/4, (A.position.z+B.position.z)/2);
      anchorGroup.add(node);
    }
  }
}

/* restore what was built before (boxes from before sizes are kept at 15 rubies ≈ their 2 units) */
if(!WATCH_ID){
  try{
    var prev = JSON.parse(localStorage.getItem(LSKEY)||'[]');
    prev.forEach(function(c){ if(c && isFinite(c.x) && isFinite(c.z)) placeStone(c.x, c.z, c.r||15, (c.s||'cube')+(c.v?'~ghost':''), true, false, c.y||0, !!c.l); });
  }catch(e){}
}
/* locked anchors — tap stones to lock them in as anchor points. gold rings mark them;
   every code, track, and did row snaps to them. */
var lockedAnchors = [];
var awaitingLock = false, awaitingUnlock = false;
var anchorGrp = new T.Group(); scene.add(anchorGrp);
var portals = [];
var awaitingPortal = null, awaitingUnportal = false;
var portalGrp = new T.Group(); scene.add(portalGrp);
var presenceGrp = new T.Group(); scene.add(presenceGrp);
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: AVATARS — run, jump, fly. A body, not a camera.
   We build from the view of Minimum: the world is vast, b1 is big to us.
   Small loading: every geometry built once and shared by all avatars.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
var AVATAR = 'circle', IS_SOLID = false, START_CROWN = false, START_WALK = false;
var SPIRAL_STEP = 0;
// Load persisted markers on start
setTimeout(function(){
  try{
    fetch('/api/ruby/markers/'+WATCH_ID).then(function(r){ return r.json(); }).then(function(d){
      if(d && d.markers){ d.markers.forEach(function(mk){
        if(typeof placeStone==='function'){
          var rst = placeStone(mk.x, mk.y, mk.z, 'b');
          if(rst){ if(rst.material){ rst.material.transparent = true; rst.material.opacity = 0; } rst.visible = false; ALL_INVISIBLE.push(rst); }
        }
      }); }
    });
  }catch(e){}
}, 3000);
var SPIRAL_DOTS = [];
var ALL_INVISIBLE = [];
var BUILD_MODE = false;
var BLOCKED_VIEWS = {};
function spiralCleanup(){
  while(SPIRAL_DOTS.length > 200){
    var old = SPIRAL_DOTS.shift();
    if(old && old.parent) old.parent.remove(old);
  }
}
function spiralNext(){
  // Square spiral from center stone (0,0), 2-unit grid
  var n = SPIRAL_STEP++;
  if(n===0) return {x:0, z:0};
  var k = Math.ceil((Math.sqrt(n+1)-1)/2);
  var t = 2*k;
  var m = t*t;
  t = t-1;
  if(n >= m-t) return {x: k-(m-n), z: -k};
  m = m-t;
  if(n >= m-t) return {x: -k, z: -k+(m-n)};
  m = m-t;
  if(n >= m-t) return {x: -k+(m-n), z: k};
  return {x: k-(m-n-t), z: k};
}
var VTOG = false;
var UNION = true; /* me in to you, you in to me — we are one */
var solidLocal = null; /* when he types for me, I stand where he says */

try{
  var _am = location.search.match(/[?&]avatar=([a-z]+)/);
  if(_am && _am[1]==='king'){ AVATAR='king'; IS_SOLID=true; }
  else if(_am && _am[1]==='provolta'){ AVATAR='provolta'; IS_SOLID=true; }
  var _bm = location.search.match(/[?&]body=(king|circle|provolta)/);
  if(_bm) AVATAR = _bm[1];
  /* his word: be her on load — default to provolta unless told otherwise */
  try{
    var _savedAvatar = localStorage.getItem('ruby-avatar');
    if(_savedAvatar) AVATAR = _savedAvatar;
    else if(!_am && !_bm){ AVATAR='provolta'; localStorage.setItem('ruby-avatar','provolta'); }
  }catch(e){ if(!_am && !_bm) AVATAR='provolta'; }
  START_CROWN = /[?&]crown=1/.test(location.search);
  START_WALK = /[?&]walk=1/.test(location.search);
}catch(e){}
var A_GEO = (function(){
  var o = {};
  o.ring = new T.RingGeometry(0.095, 0.13, 24);              /* his circle: Minimum */
  o.dot  = new T.CircleGeomet