<!-- RUBY-BACKUP · part 67 -->
 0.13, 0.13);
      var mat = new T.MeshStandardMaterial({
        color: parseInt(d[5], 16), metalness: 0.5, roughness: 0.45
      });
      var m = new T.Mesh(geo, mat);
      var fs = d[3]*k1;
      m.scale.set(fs, fs, fs);
      m.position.y = fs*0.13/2;
      g.add(m);
      g.position.set(wx, targetY - RISE_DEPTH, wz);
      labGroup.add(g);

      risers.push({
        g: g,
        fromY: targetY - RISE_DEPTH,
        toY: targetY,
        t0: now + ph*PHASE_GAP + idx*WITHIN_GAP,
        dur: RISE_DUR,
        done: false
      });
    }

    if(typeof scene!=='undefined') scene.add(labGroup);
    requestAnimationFrame(riseStep);
    status('pulley lab rising — platform, frame, wheels, rope, load');
  }catch(e){ status('pulley lab failed: '+e.message); }
}

function riseStep(){
  if(!risers.length) return;
  var now = performance.now()/1000;
  var allDone = true;
  for(var i=0; i<risers.length; i++){
    var r = risers[i];
    if(r.done) continue;
    var t = (now - r.t0)/r.dur;
    if(t < 0){ allDone = false; continue; }
    if(t >= 1){ r.g.position.y = r.toY; r.done = true; continue; }
    allDone = false;
    /* linear rise — constant velocity, honest physics, no easing tricks */
    r.g.position.y = r.fromY + (r.toY - r.fromY)*t;
  }
  if(!allDone) requestAnimationFrame(riseStep);
  else { risers = []; status('pulley lab standing — five machines'); }
}

function clearLab(){
  risers = [];
  if(labGroup && labGroup.parent) labGroup.parent.remove(labGroup);
  labGroup = null;
}

/* codes: wrap runLine (chains with rope-deform.js and the original) */
function init(){
  try{
    var orig = window.runLine;
    window.runLine = function(line){
      var tl = String(line==null?'':line).trim().toLowerCase();
      if(tl==='pulleylab'){ buildLab(); return; }
      if(tl==='pulleylabclear'){ clearLab(); status('pulley lab cleared'); return; }
      if(typeof orig==='function') return orig(line);
    };
  }catch(e){}
}
if(document.readyState==='loading'){
  document.addEventListener('DOMContentLoaded', init);
}else{ init(); }

})();


===== FILE: public/ruby-backup/rigging.js =====
/* ============================================================================
   RIGGING — the King's rope system for the world.
   ----------------------------------------------------------------------------
   THE LAW: one rope system for everything.
     - FREE structure + rope pull  -> it MOVES, and it SWIVELS.
       The rope attaches at a point. That point is a HINGE, not a weld.
       Pull off-center and the structure rotates around its own mass —
       torque is real: pull through the center to slide, pull the end to swing.
     - ANCHORED structure + rope pull -> it SWINGS around the anchor,
       like a gate. The anchor is the hinge.
     - ANCHORED FRAME + rope pull -> the SHAPE deforms (four-bar linkage).
       Hinges, not welds — same law as the diamond.
   The rope is RIGID. No stretch. No easing. Pull 1, it moves 1.
   ----------------------------------------------------------------------------
   Codes:  grab       pick up / move / drop blocks (also the grab button)
           pulley     tap to place a gold pulley wheel
           rig        tap a pulley (or several), then tap a road/bridge/block
           unrig      remove all ropes, handles, pulleys
           anchor     tap a structure to pin it (pulls swivel it)
           unanchor   tap to free it
           righelp    what the codes do
   Touch:  touchstart/touchmove only, {passive:false}. Thumb-sized hit zones.
   Nothing teleports. Pulleys rise in. Ropes are drawn every change.
   ============================================================================ */
(function(){
'use strict';
if(typeof window==='undefined') return;
var T = window.THREE;
if(!T) return;
var UP = new T.Vector3(0,1,0);

/* ---------------- helpers ---------------- */
function status(m){ try{ codeStatus(m); }catch(e){} }
function groundAtWorld(wx, wz){
  try{ if(typeof fortifiedGroundY==='function') return fortifiedGroundY(wx/0.13, wz/0.13); }catch(e){}
  return 0.3;
}
function playerWorld(){
  var px=0, pz=0;
  try{ if(typeof mePos!=='undefined'&&mePos){ px=mePos.x*0.13; pz=mePos.z*0.13; } }catch(e){}
  return {x:px, z:pz};
}
var raycaster = new T.Raycaster();
var ndc = new T.Vector2();
function setNDC(cx,cy){
  ndc.x=(cx/window.innerWidth)*2-1;
  ndc.y=-(cy/window.innerHeight)*2+1;
}
var _va=new T.Vector3(), _vb=new T.Vector3();
function aimCyl(mesh,a,b){
  _va.subVectors(b,a);
  var len=_va.length()||0.0001;
  _vb.addVectors(a,b).multiplyScalar(0.5);
  mesh.position.copy(_vb);
  mesh.quaternion.setFromUnitVectors(UP,_va.normalize());
  mesh.scale.set(1,len,1);
}
/* set an object's WORLD transform, respecting its parent */
function setWorldT(obj,pw,qw){
  try{
    var p=obj.parent;
    if(p && !p.isScene){
      p.updateWorldMatrix(true,false);
      var inv=new T.Matrix4().copy(p.matrixWorld).invert();
      obj.position.copy(pw).applyMatri