<!-- RUBY-BACKUP · part 69 -->
x,cy);
    raycaster.setFromCamera(ndc,camera);
    var pt=new T.Vector3();
    var plane=new T.Plane(new T.Vector3(0,1,0), -carried.planeY);
    if(!raycaster.ray.intersectPlane(plane,pt)) return;
    var body=targetBody(carried.t);
    setWorldT(body, new T.Vector3(pt.x,carried.planeY,pt.z), worldQuatOf(body));
    if(carried.t.kind==='pulley'&&carried.t.pulley)
      carried.t.pulley.pos.copy(worldPosOf(body));
    refreshAllRopes();
  }catch(e){}
}
function dropCarried(){
  if(!carried) return;
  try{
    var body=targetBody(carried.t);
    var wp=worldPosOf(body);
    var gy=groundAtWorld(wp.x,wp.z);
    setWorldT(body, new T.Vector3(wp.x, gy+carried.halfH+0.05, wp.z), worldQuatOf(body));
    if(carried.t.kind==='pulley'&&carried.t.pulley)
      carried.t.pulley.pos.copy(worldPosOf(body));
    status('dropped');
  }catch(e){}
  carried=null;
  refreshAllRopes();
}

/* ---------------- PULLEYS — fixed gold wheels ---------------- */
var pulleys=[];
var pulleyArmed=false;
function armPulley(){
  closeMenu();
  pulleyArmed=!pulleyArmed;
  status(pulleyArmed?'tap where the pulley goes':'pulley placement off');
}
function placePulley(pointW){
  try{
    mats();
    if(typeof scene==='undefined') return null;
    var g=new T.Group();
    var wheel=new T.Mesh(new T.TorusGeometry(0.34,0.09,12,28), MAT.gold);
    g.add(wheel);
    var pin=new T.Mesh(new T.CylinderGeometry(0.06,0.06,0.6,10), MAT.steel);
    pin.rotation.x=Math.PI/2; g.add(pin);
    var lbl=textSprite('PULLEY',0.7);
    if(lbl){ lbl.position.y=0.85; g.add(lbl); }
    g.position.copy(pointW);
    scene.add(g);
    var P={group:g, wheel:wheel, pos:pointW.clone()};
    g.userData.rigPulley=true;
    g.userData.pulleyRef=P;
    // rise in — nothing teleports
    var toY=pointW.y, depth=2.5, t0=performance.now()/1000, dur=0.5;
    g.position.y=toY-depth;
    (function step(){
      var t=(performance.now()/1000-t0)/dur;
      if(t>=1){ g.position.y=toY; return; }
      g.position.y=toY-depth+depth*t;
      requestAnimationFrame(step);
    })();
    pulleys.push(P);
    status('pulley placed — rig it with the rig code');
    return P;
  }catch(e){ status('pulley failed: '+e.message); return null; }
}
function pulleyHit(cx,cy){
  try{
    if(!pulleys.length||typeof camera==='undefined') return null;
    setNDC(cx,cy);
    raycaster.setFromCamera(ndc,camera);
    var gs=[]; for(var i=0;i<pulleys.length;i++) gs.push(pulleys[i].group);
    var hits=raycaster.intersectObjects(gs,true);
    if(!hits.length) return null;
    var o=hits[0].object;
    while(o){
      for(var j=0;j<pulleys.length;j++) if(pulleys[j].group===o) return pulleys[j];
      o=o.parent;
    }
  }catch(e){}
  return null;
}

/* ---------------- ANCHORS — the hinge ----------------
   One anchor per structure. Anchored structures SWIVEL around it. */
var anchors=[];
var anchorArmed=false, unanchorArmed=false;
function armAnchor(){
  closeMenu();
  anchorArmed=!anchorArmed; unanchorArmed=false;
  status(anchorArmed?'tap the road, bridge, or block to anchor it':'anchor off');
}
function addAnchor(t, pointW){
  try{
    mats();
    if(typeof scene==='undefined') return;
    removeAnchor(t);
    var g=new T.Group();
    var gy=groundAtWorld(pointW.x,pointW.z);
    var h=Math.max(0.6, pointW.y-gy+0.4);
    var post=new T.Mesh(new T.CylinderGeometry(0.09,0.12,1,10), MAT.steel);
    post.scale.y=h;
    post.position.set(pointW.x, gy+h/2-0.2, pointW.z);
    var ring=new T.Mesh(new T.TorusGeometry(0.22,0.05,10,20), MAT.red);
    ring.position.copy(pointW);
    var lbl=textSprite('ANCHOR',0.7);
    if(lbl) lbl.position.set(pointW.x, pointW.y+0.7, pointW.z);
    g.add(post); g.add(ring); if(lbl) g.add(lbl);
    scene.add(g);
    anchors.push({key:targetKey(t), target:t, pointW:pointW.clone(), stake:g});
    if(t.kind==='frame'&&t.frame) t.frame.anchored=true;
    status('anchored — pull it and it swivels');
  }catch(e){ status('anchor failed: '+e.message); }
}
function findAnchor(t){
  var k=targetKey(t);
  for(var i=0;i<anchors.length;i++) if(anchors[i].key===k) return anchors[i];
  return null;
}
function getAnchor(t){
  var a=findAnchor(t);
  if(a) return a;
  // rope-deform's own anchored frame counts as anchored (virtual hinge at its center)
  if(t.kind==='frame'&&t.frame&&t.frame.anchored)
    return {key:targetKey(t), target:t, pointW:worldPosOf(t.frame.group), virtual:true};
  return null;
}
function removeAnchor(t){
  var k=targetKey(t), removed=false;
  for(var i=anchors.length-1;i>=0;i--){
    if(anchors[i].key===k){
      var st=anchors[i].stake;
      if(st&&st.parent) st.parent.remove(st);
      anchors.splice(i,1);
      removed=true;
    }
  }
  if(t.kind==='frame'&&t.frame&&!findAnchor(t)) t.frame.anchored=false;
  if(removed) status('anchor pulled — it is free');
  return removed;
}
function doUnanchor(){
  if(anchors.length===1){ removeAnchor(anchors[0].target); return; }
  unanchorArmed=!unanchorArmed; anchorArmed=false;
  status(unanchorArmed?'