<!-- RUBY-BACKUP · part 40 -->
int(7,0);
    var g=new T.Group();
    var drumR=0.35, axleY=2.3;
    /* frame posts */
    var p1=new T.Mesh(new T.BoxGeometry(0.3,axleY+0.4,0.3),mats().wood); p1.position.set(-0.95,(axleY+0.4)/2,0); g.add(p1);
    var p2=p1.clone(); p2.position.x=0.95; g.add(p2);
    /* drum on axle along X */
    var drumG=new T.Group(); drumG.position.set(0,axleY,0); g.add(drumG);
    var drum=new T.Mesh(new T.CylinderGeometry(drumR,drumR,1.5,20),mats().gold);
    drum.rotation.z=Math.PI/2; drumG.add(drum);
    /* flanges */
    var fl1=new T.Mesh(new T.CylinderGeometry(0.5,0.5,0.08,20),mats().red);
    fl1.rotation.z=Math.PI/2; fl1.position.x=-0.79; drumG.add(fl1);
    var fl2=fl1.clone(); fl2.position.x=0.79; drumG.add(fl2);
    /* crank on +x end: arm + knob */
    var crankG=new T.Group(); crankG.position.set(0.95,axleY,0); g.add(crankG);
    var arm=new T.Mesh(new T.BoxGeometry(0.12,0.85,0.12),mats().steel);
    arm.position.y=-0.32; crankG.add(arm);
    var knob=new T.Mesh(new T.SphereGeometry(0.14,12,12),mats().gold);
    knob.position.y=-0.72; crankG.add(knob);
    var khit=hitSphere(0.5); khit.position.copy(knob.position); crankG.add(khit);
    var M=regMachine('winch',g);
    M.drumR=drumR; M.wound=0; M.maxWound=4; M.theta=0;
    M.drumG=drumG; M.crankG=crankG;
    /* load */
    M.load=new T.Mesh(new T.BoxGeometry(0.9,0.9,0.9),mats().crate); g.add(M.load);
    M.loadY0=base.y+0.7;
    var lbl=textSprite('WINCH',0.85); lbl.position.set(0,axleY+1.3,0); g.add(lbl);
    var formula=textSprite('ΔROPE = Δθ × r',0.7); formula.position.set(0,axleY+0.75,0); g.add(formula); M.formula=formula;
    M.rope=ropeSeg(); g.add(M.rope);
    function layout(){
      var loadY=M.loadY0+M.wound;
      M.load.position.set(0,loadY,0);
      /* rope: drum top tangent -> load top */
      aimCyl(M.rope,new T.Vector3(0,axleY+drumR,0),new T.Vector3(0,loadY+0.45,0));
      var turns=M.wound/(2*Math.PI*drumR);
      try{
        g.remove(M.formula);
        M.formula=textSprite('TURNS '+turns.toFixed(2)+' · ROPE '+M.wound.toFixed(2)+'m',0.65);
        M.formula.position.set(0,axleY+0.75,0); g.add(M.formula);
      }catch(e){}
    }
    M.layout=layout;
    /* crank drag: screen-space circle around drum center */
    var a0=0;
    regHit(khit,{
      onDown:function(x,y){
        a0=screenAngle(worldPosOf(drumG),x,y);
      },
      onMove:function(x,y){
        var a1=screenAngle(worldPosOf(drumG),x,y);
        var d=wrapPi(a1-a0); a0=a1;
        /* counterclockwise on screen winds IN */
        var dw=d*M.drumR;
        var nw=Math.max(0,Math.min(M.maxWound,M.wound+dw));
        var applied=nw-M.wound;
        M.wound=nw;
        M.theta+=applied/M.drumR;
        drumG.rotation.x=M.theta;
        crankG.rotation.x=M.theta;
        layout();
      }
    });
    g.position.copy(base);
    scene.add(g);
    layout();
    riseIn(g,base.y,2.5,0.6);
    status('winch — drag the crank knob in a circle. the drum winds true.');
  }catch(e){ status('winch failed: '+e.message); }
}

/* ============================================================================
   GEAR LINK — rack and pinion. Turn the gear, the rope travels.
   ΔROPE = Δθ × r_axle. Back-drivable: pull the rope, the gear turns.
   `gearlink` builds it. `ungear` removes it.
   ============================================================================ */
function buildGearlink(){
  try{
    if(typeof scene==='undefined') return;
    mats();
    var base=forwardPoint(7,0);
    var g=new T.Group();
    var gy=base.y, gearY=gy+2.7, axleR=0.16, gearR=0.62;
    /* post */
    var post=new T.Mesh(new T.BoxGeometry(0.3,gearY-gy+0.4,0.3),mats().steel);
    post.position.set(-1.1,(gearY+gy)/2-0.1,0); g.add(post);
    var armB=new T.Mesh(new T.BoxGeometry(1.3,0.2,0.2),mats().steel);
    armB.position.set(-0.55,gearY,0); g.add(armB);
    /* gear */
    var gearG=new T.Group(); gearG.position.set(0,gearY,0); g.add(gearG);
    var disc=new T.Mesh(new T.CylinderGeometry(gearR,gearR,0.16,28),mats().gold);
    disc.rotation.x=Math.PI/2; gearG.add(disc);
    for(var ti=0;ti<12;ti++){
      var tooth=new T.Mesh(new T.BoxGeometry(0.16,0.16,0.16),mats().gold);
      var a=ti/12*Math.PI*2;
      tooth.position.set(Math.cos(a)*(gearR+0.06),Math.sin(a)*(gearR+0.06),0);
      tooth.rotation.z=a; gearG.add(tooth);
    }
    var axle=new T.Mesh(new T.CylinderGeometry(axleR,axleR,0.5,12),mats().red);
    axle.rotation.x=Math.PI/2; gearG.add(axle);
    var ghit=hitSphere(0.95); gearG.add(ghit);
    var M=regMachine('gearlink',g);
    M.theta=0; M.axleR=axleR; M.gearG=gearG;
    /* rope: vertical, tangent to axle at x=+axleR, load hangs below */
    M.load=new T.Mesh(new T.BoxGeometry(0.8,0.8,0.8),mats().crate); g.add(M.load);
    M.ropeTop=gearY+1.6; M.loadY=gy+0.8;
    var lbl=textSprite('GEAR LINK 12T',0.85); lbl.position.set(0,gearY+1.15,0); g.add(lbl);
    var formula=textSprite('1 TURN = 2π × '+axleR,0.7); formula.position.set(0,gearY+0.62,0); g.add(formula); M.formula=f