<!-- RUBY-BACKUP · part 38 -->
hit.onMove(x,y,dx,dy);
    return true;
  }catch(e){ return false; }
}
function gUp(id){
  try{
    if(!gDrag||gDrag.id!==id) return;
    if(!gDrag.moved&&gDrag.hit.onTap) gDrag.hit.onTap();
    if(gDrag.hit.onUp) gDrag.hit.onUp();
    gDrag=null;
  }catch(e){ gDrag=null; }
}

/* ============================================================================
   COMPOUND — chained sheaves. PULL n = LIFT 1. Exact ratios.
   `compound` builds the 2:1. Tap the gold + to add a stage (to 4:1).
   `uncompound` removes it.
   ============================================================================ */
function buildCompound(){
  try{
    if(typeof scene==='undefined') return;
    mats();
    var base=forwardPoint(7,0);
    var g=new T.Group();
    var H=4.4, gy=base.y;
    /* frame: two posts + top bar */
    var p1=new T.Mesh(new T.BoxGeometry(0.3,H,0.3),mats().red); p1.position.set(-1.6,H/2,0); g.add(p1);
    var p2=p1.clone(); p2.position.x=1.6; g.add(p2);
    var bar=new T.Mesh(new T.BoxGeometry(3.6,0.25,0.3),mats().red); bar.position.set(0,H,0); g.add(bar);
    var M=regMachine('compound',g);
    M.n=2; M.pull=0; M.maxPull=3;
    M.loadY0=gy+0.9;
    /* load crate + movable bar */
    M.load=new T.Mesh(new T.BoxGeometry(0.9,0.9,0.9),mats().crate); g.add(M.load);
    M.movBar=new T.Group(); g.add(M.movBar);
    var lbl=textSprite('COMPOUND 2:1',0.85); lbl.position.set(0,H+0.9,0); g.add(lbl); M.lbl=lbl;
    var formula=textSprite('PULL 2 = LIFT 1',0.7); formula.position.set(0,H+0.35,0); g.add(formula); M.formula=formula;
    /* handle */
    M.handle=goldRing(0.24); M.handle.position.set(2.3,gy+1.6,0); g.add(M.handle);
    var hhit=hitSphere(0.62); hhit.position.copy(M.handle.position); g.add(hhit); M.hhit=hhit;
    /* + stage button */
    var plus=goldRing(0.2); plus.position.set(-2.3,gy+1.6,0); g.add(plus);
    var plbl=textSprite('+',0.5); plbl.position.set(-2.3,gy+2.15,0); g.add(plbl);
    var phit=hitSphere(0.55); phit.position.copy(plus.position); g.add(phit);
    regHit(phit,{onTap:function(){ addCompoundStage(M); }});
    /* rope segments pool */
    M.ropes=[];
    function rebuild(){
      var n=M.n;
      /* clear old sheaves/ropes */
      for(var i=M.movBar.children.length-1;i>=0;i--) M.movBar.remove(M.movBar.children[i]);
      if(M.fixBar) g.remove(M.fixBar);
      for(var r=0;r<M.ropes.length;r++) g.remove(M.ropes[r]);
      M.ropes=[];
      M.fixBar=new T.Group(); g.add(M.fixBar);
      M.fixX=[]; M.movX=[];
      for(var s=0;s<n;s++){
        var fx=-1.2+s*(2.4/(n-1));
        M.fixX.push(fx);
        var sh=new T.Mesh(new T.TorusGeometry(0.22,0.07,10,22),mats().gold);
        sh.position.set(fx,H-0.35,0); M.fixBar.add(sh);
        var mx=fx;
        M.movX.push(mx);
        var msh=new T.Mesh(new T.TorusGeometry(0.18,0.06,10,20),mats().gold);
        msh.position.set(mx,0.75,0); M.movBar.add(msh);
      }
      for(var q=0;q<2*n+1;q++){ var seg=ropeSeg(); g.add(seg); M.ropes.push(seg); }
      /* anchor nub at top-left */
      var nub=new T.Mesh(new T.SphereGeometry(0.09,8,8),mats().gold);
      nub.position.set(M.fixX[0]-0.35,H-0.35,0); g.add(nub); M.nub=nub;
      layoutCompound(M);
    }
    function redrawLabel(M,n){
      /* swap label text by rebuilding sprite */
      try{
        g.remove(M.lbl);
        M.lbl=textSprite('COMPOUND '+n+':1',0.85); M.lbl.position.set(0,H+0.9,0); g.add(M.lbl);
        g.remove(M.formula);
        M.formula=textSprite('PULL '+n+' = LIFT 1',0.7); M.formula.position.set(0,H+0.35,0); g.add(M.formula);
      }catch(e){}
    }
    M.rebuild=rebuild;
    /* handle drag: vertical pull */
    var pull0=0, y0=0;
    regHit(hhit,{
      onDown:function(x,y){
        var wp=worldPosOf(M.handle); var wy=vPlaneY(wp,x,y);
        if(wy!==null){ y0=wy; pull0=M.pull; }
      },
      onMove:function(x,y){
        var wp=worldPosOf(M.handle); var wy=vPlaneY(wp,x,y);
        if(wy===null) return;
        /* screen-down pull: world y decreases -> pull increases */
        M.pull=Math.max(0,Math.min(M.maxPull, pull0+(y0-wy)));
        layoutCompound(M);
      }
    });
    g.position.copy(base);
    scene.add(g);
    rebuild();
    riseIn(g,base.y,2.5,0.6);
    M.cleanup=function(){};
    status('compound '+M.n+':1 — pull the gold ring DOWN, the load rises 1/'+M.n);
  }catch(e){ status('compound failed: '+e.message); }
}
function addCompoundStage(M){
  if(M.n>=4){ status('4:1 is the max — worthy enough'); return; }
  M.n++; M.rebuild();
  status('compound '+M.n+':1 — PULL '+M.n+' = LIFT 1');
}
function layoutCompound(M){
  try{
    var n=M.n, H=4.4;
    var lift=M.pull/n;
    var loadY=M.loadY0+lift;
    M.load.position.set(0,loadY,0);
    M.movBar.position.set(0,loadY+0.55,0);
    /* handle follows pull */
    M.handle.position.set(2.3,(M.loadY0+1.2)-M.pull*0.9,0);
    M.hhit.position.copy(M.handle.position);
    /* rope path: nub -> mov0 -> fix0 -> mov1 -> fix1 ... -> handle */
    var pts=[];
    pts.push(new T.Vector3(M.fixX[0]-0.35,H-