<!-- RUBY-BACKUP · part 71 -->
n') persistLifts();
      try{ if(typeof refreshLandMesh === 'function'){ for(var j=0;j<lifts.length;j++) refreshLandMesh(lifts[j].x, lifts[j].z, lifts[j].r); } }catch(e){}
      return true;
    }catch(e){ return false; }
  };
  window.__liftAt = (typeof liftAt === 'function') ? liftAt : null;
}catch(e){}

/* ---------------- THE PULL — swivel physics ----------------
   Free: the attach point is dragged toward the pulley along the rope.
   The body follows: part translation, part rotation about its own mass.
   Torque = r x F: pull through the center -> slides; pull the end -> swings.
   Anchored: the body yaws around the anchor's vertical line (gate swing),
   solved from the rope-length constraint by Newton steps.
   Anchored frame: the pull drives the nearest corner (four-bar linkage). */
function driveRig(rig){
  try{
    if(typeof scene==='undefined') return;
    scene.updateMatrixWorld();
    var Aw=rig.body.localToWorld(rig.attachLocal.clone());
    var Pn=rig.pulleys[rig.pulleys.length-1].pos;
    var targetSeg=Math.max(0.5, rig.Lt-rig.h-rig.fixedSegs);
    rig.targetSeg=targetSeg;
    var anchor=getAnchor(rig.target);
    if(rig.target.kind==='frame'&&anchor) deformPull(rig,Aw,Pn,targetSeg);
    else if(anchor) pendulumPull(rig,anchor,Aw,Pn,targetSeg);
    else freePull(rig,Aw,Pn,targetSeg);
    for(var i=0;i<rig.pulleys.length;i++) rig.pulleys[i].wheel.rotation.z+=0.06;
    refreshRig(rig);
  }catch(e){}
}
function freePull(rig,Aw,Pn,targetSeg){
  var body=rig.body;
  var C=worldPosOf(body), Q=worldQuatOf(body);
  var u=Aw.clone().sub(Pn); var d=u.length()||1; u.divideScalar(d);
  var Anew=Pn.clone().add(u.multiplyScalar(targetSeg));
  var D=Anew.sub(Aw);
  if(D.lengthSq()<1e-10) return;
  var Cnew=C.clone().addScaledVector(D,0.55);       /* mostly travels */
  var rem=D.clone().multiplyScalar(0.45);           /* rest becomes swing */
  var r=Aw.clone().sub(C);
  var rl=r.length(), reml=rem.length();
  var Qnew=Q.clone();
  if(rl>1e-4&&reml>1e-6){
    var axis=new T.Vector3().crossVectors(r,rem);
    var al=axis.length();
    if(al>1e-8){
      var ang=Math.min(0.6,(reml/rl)*(al/(rl*reml)));
      Qnew=new T.Quaternion().setFromAxisAngle(axis.divideScalar(al),ang).multiply(Q);
    }
  }
  setWorldT(body,Cnew,Qnew);
}
function pendulumPull(rig,anchor,Aw,Pn,targetSeg){
  var A=anchor.pointW;
  if(!rig.snap||rig.snap.anchor!==anchor){
    rig.snap={anchor:anchor, pos0:worldPosOf(rig.body), quat0:worldQuatOf(rig.body),
              off0:Aw.clone().sub(A)};
    rig.theta=0;
  }
  var s=rig.snap, th=rig.theta;
  function errAt(t){
    var off=s.off0.clone().applyAxisAngle(UP,t);
    return A.clone().add(off).distanceTo(Pn)-targetSeg;
  }
  for(var i=0;i<5;i++){
    var e0=errAt(th);
    if(Math.abs(e0)<0.008) break;
    var d=(errAt(th+0.04)-errAt(th-0.04))/0.08;
    if(Math.abs(d)<1e-5) break;
    th-=e0/d;
    if(th>2.6)th=2.6; if(th<-2.6)th=-2.6;
  }
  rig.theta=th;
  var q=new T.Quaternion().setFromAxisAngle(UP,th).multiply(s.quat0);
  var p=A.clone().add(s.pos0.clone().sub(A).applyAxisAngle(UP,th));
  setWorldT(rig.body,p,q);
}
function deformPull(rig,Aw,Pn,targetSeg){
  var frame=rig.target.frame;
  var u=Aw.clone().sub(Pn); var d=u.length()||1; u.divideScalar(d);
  var desired=Pn.clone().add(u.multiplyScalar(targetSeg));
  var Dw=desired.sub(Aw);
  if(Dw.lengthSq()<1e-10) return;
  var g=frame.group;
  g.updateWorldMatrix(true,false);
  var lNow=g.worldToLocal(Aw.clone());
  var lTgt=g.worldToLocal(Aw.clone().add(Dw));
  var dx=lTgt.x-lNow.x, dy=lTgt.y-lNow.y;
  var best='A', bd=1e18;
  ['A','B','C','D'].forEach(function(k){
    var p=frame.pos[k];
    var dd=(p[0]-lNow.x)*(p[0]-lNow.x)+(p[1]-lNow.y)*(p[1]-lNow.y);
    if(dd<bd){ bd=dd; best=k; }
  });
  frame.driven[best]=true;
  frame.pos[best][0]+=dx; frame.pos[best][1]+=dy;
  try{ frame.solve(); frame.updateMeshes(); }catch(e){}
  rig._deformCorner=best;
}

/* ---------------- handle pulling ---------------- */
var activePull=null; // {rig,id}
function handleHit(cx,cy){
  try{
    if(!rigs.length||typeof camera==='undefined') return null;
    setNDC(cx,cy);
    raycaster.setFromCamera(ndc,camera);
    var hs=[]; for(var i=0;i<rigs.length;i++) hs.push(rigs[i].handle.hit);
    var hits=raycaster.intersectObjects(hs,false);
    return hits.length?hits[0].object.userData.rigPart.rig:null;
  }catch(e){ return null; }
}
/* closest approach of thumb-ray to the pull line P1+axis*s -> s */
function pullS(rig,cx,cy){
  try{
    setNDC(cx,cy);
    raycaster.setFromCamera(ndc,camera);
    var ro=raycaster.ray.origin, rd=raycaster.ray.direction;
    var P1=rig.pulleys[0].pos, a=rig.axis;
    var w0=P1.clone().sub(ro);
    var b=rd.dot(a), d=rd.dot(w0), e=a.dot(w0);
    var den=1-b*b;
    if(Math.abs(den)<1e-6) return null;
    return (b*d-e)/den;
  }catch(e){ return null; }
}
function dragHandle(rig,cx,cy){
  var s=pullS(rig,cx,cy);
  if(s===null||!isFinite(s)) return;
  rig.h=Math.max(0.6,Math.min(7,s));
  driveRig(ri