<!-- RUBY-BACKUP · part 569 -->
window.__doSolidHook()) return; }catch(e){}
        if(tries < 60) setTimeout(attempt, 500);
      }
      setTimeout(attempt, 800);
    })();
  }catch(e){}

})();


===== FILE: public/ruby-backup/spider.js =====
/* ============================================================================
   SPIDER — the constellation code.
   The King: "The spider code. It creates a constellation between all points
   of the same structure and you can choose the amount of connections it
   latches on to by the distance of a small area."

   Tap a structure and the spider spins: gold threads latching every pair of
   points within your chosen distance — the structure's skeleton, revealed.
   Smaller distance = sparser web. Larger = denser constellation.

   Codes: spider | spider <distance> | unspider | webs
   ============================================================================ */
(function(){
  if(window.__spider) return;
  window.__spider = true;

  var latchDist = 6;        /* the small area — user-chosen */
  var spiderArmed = false;
  var warpArmed = false;    /* warp: tap a thread, ride it */
  var webs = [];            /* {group, mats, name, dist, conns, pts, pairs} */
  var webSeq = 0;
  var CLUSTER_GAP = 10;     /* flood-fill gap: meshes this close are one structure */
  var MAX_POINTS = 240;     /* constellation point cap */
  var MAX_THREADS = 6000;   /* thread cap */

  function status(m){ try{ if(typeof codeStatus==='function') codeStatus(m); }catch(e){} }

  /* ---------- find the structure: flood-fill over stamped node meshes ---------- */
  function structureOf(seedObj){
    var map = window.__nodeMap || {};
    var ids = Object.keys(map);
    var seedPos = new THREE.Vector3();
    try{ seedObj.getWorldPosition(seedPos); }catch(e){ return [seedObj]; }
    var pos = {}, near = [];
    for(var i=0;i<ids.length;i++){
      var rec = map[ids[i]];
      if(!rec || !rec.obj) continue;
      try{ if(!rec.obj.parent) continue; }catch(e){ continue; }
      var p = new THREE.Vector3();
      try{ rec.obj.getWorldPosition(p); }catch(e){ continue; }
      if(p.distanceToSquared(seedPos) > 3600) continue; /* 60u broad radius */
      pos[ids[i]] = p;
      near.push(ids[i]);
    }
    if(!near.length) return [seedObj];
    /* nearest node mesh to the tap = flood seed */
    var best = null, bd = Infinity;
    for(var j=0;j<near.length;j++){
      var d = pos[near[j]].distanceToSquared(seedPos);
      if(d < bd){ bd = d; best = near[j]; }
    }
    var inSet = {}, queue = [best];
    inSet[best] = 1;
    while(queue.length){
      var cur = queue.pop();
      for(var k=0;k<near.length;k++){
        var nid = near[k];
        if(inSet[nid]) continue;
        if(pos[cur].distanceTo(pos[nid]) <= CLUSTER_GAP){ inSet[nid] = 1; queue.push(nid); }
      }
    }
    var meshes = [];
    for(var n in inSet){ if(map[n] && map[n].obj) meshes.push(map[n].obj); }
    return meshes.length ? meshes : [seedObj];
  }

  /* ---------- constellation points: bounding-box corners, deduped ---------- */
  function constellationPoints(meshes){
    var pts = [], seen = {};
    for(var i=0;i<meshes.length && pts.length < MAX_POINTS;i++){
      var box = new THREE.Box3().setFromObject(meshes[i]);
      if(box.isEmpty()) continue;
      var cx = [[box.min.x,box.min.y,box.min.z],[box.min.x,box.min.y,box.max.z],
                [box.min.x,box.max.y,box.min.z],[box.min.x,box.max.y,box.max.z],
                [box.max.x,box.min.y,box.min.z],[box.max.x,box.min.y,box.max.z],
                [box.max.x,box.max.y,box.min.z],[box.max.x,box.max.y,box.max.z]];
      for(var c=0;c<8 && pts.length < MAX_POINTS;c++){
        var key = Math.round(cx[c][0]*2)+','+Math.round(cx[c][1]*2)+','+Math.round(cx[c][2]*2);
        if(seen[key]) continue;
        seen[key] = 1;
        pts.push(new THREE.Vector3(cx[c][0], cx[c][1], cx[c][2]));
      }
    }
    return pts;
  }

  /* ---------- spin the web ---------- */
  function spinWeb(meshes){
    var pts = constellationPoints(meshes);
    if(pts.length < 2){ status('too few points to spin'); return null; }
    var verts = [], conns = 0, pairs = [];
    var d2max = latchDist * latchDist;
    for(var i=0;i<pts.length;i++){
      for(var j=i+1;j<pts.length;j++){
        if(pts[i].distanceToSquared(pts[j]) <= d2max){
          verts.push(pts[i].x, pts[i].y, pts[i].z, pts[j].x, pts[j].y, pts[j].z);
          pairs.push([pts[i].clone(), pts[j].clone()]);
          if(++conns >= MAX_THREADS) break;
        }
      }
      if(conns >= MAX_THREADS) break;
    }
    if(!conns){ status('no points within '+latchDist+'u — try spider <bigger>'); return null; }
    var g = new THREE.BufferGeometry();
    g.setAttribute('position', new THREE.Float32BufferAttribute(verts, 3));
    var lm = new THREE.LineBasicMaterial({color:0xffd700, transparent:true, opacity:0,
      blending:THREE.AdditiveBlending, depthWrite:false});
    var lines = new THREE.LineSegments(g, lm);
    var pv = [];
    for(