<!-- RUBY-BACKUP · part 559 -->
t;
        /* quadratic bezier: from -> mid -> to */
        var a = fromPos.clone().lerp(mid, e);
        var b = mid.clone().lerp(toPos, e);
        var p = a.lerp(b, e);
        objGroup.position.copy(p);
        objGroup.rotation.y += 0.12;
        var s = startScale * (1 - e*0.85);
        objGroup.scale.setScalar(Math.max(0.05, s));
        if(t < 1){ requestAnimationFrame(step); return; }
        try{ if(window.scene) window.scene.remove(objGroup); }catch(e2){}
        inventory[kind] = (inventory[kind]||0) + 1;
        saveInv();
        if(done) done();
      }catch(e){}
    })();
  }

  /* ---------- disassemble a seed machine in reverse ---------- */
  function disassembleMachine(salv){
    if(typeof window.__seedParts !== 'function'){ status('no machine systems found'); return; }
    var info = window.__seedParts();
    var total = info.pulleys.length + info.gears.length + info.ropes + (info.web?1:0);
    if(!total){ status('nothing to salvage — no machine stands'); return; }
    status('salvaging '+total+' parts — web first, then ropes, gears, pulleys.');
    var toPos = salv.pos.clone(); toPos.y += 1;

    /* We take parts via __seedTakePart which removes them from the machine.
       But for the fly-home we want the VISUAL group. Take a different approach:
       get positions, then fly a ghost cargo. Simpler and reliable: */
    var queue = [];
    if(info.web) queue.push({kind:'web'});
    for(var r=0;r<info.ropes;r++) queue.push({kind:'rope'});
    /* gears: take highest index first so splice doesn't shift */
    for(var gi=info.gears.length-1; gi>=0; gi--) queue.push({kind:'gear', idx:gi});
    for(var pi=info.pulleys.length-1; pi>=0; pi--) queue.push({kind:'pulley', idx:pi});

    var step = 0;
    (function next(){
      if(step >= queue.length){
        status('salvaged — inventory holds '+invSummary());
        return;
      }
      var item = queue[step++];
      try{
        var fromPos = window.__seedTakePart(item.kind, item.idx);
        if(!fromPos) fromPos = toPos.clone();
        /* cargo ghost: small iron octahedron carrying the part's essence */
        mats();
        var cargo = new THREE.Mesh(new THREE.OctahedronGeometry(0.35), ironMat.clone());
        cargo.position.copy(fromPos);
        if(fromPos.y < 0.5) cargo.position.y = 2;
        if(window.scene) window.scene.add(cargo);
        flyHome(cargo, cargo.position.clone(), toPos, item.kind, function(){
          setTimeout(next, 120);
        });
      }catch(e){ setTimeout(next, 120); }
    })();
  }

  function invSummary(){
    var p=[];
    if(inventory.pulley) p.push(inventory.pulley+' pulley');
    if(inventory.gear) p.push(inventory.gear+' gear');
    if(inventory.rope) p.push(inventory.rope+' rope');
    if(inventory.web) p.push(inventory.web+' web');
    return p.length ? p.join(', ') : 'nothing';
  }

  /* ---------- place a part back into the world ---------- */
  function riseIn(obj, fromY, toY, dur){
    var t0 = Date.now();
    obj.position.y = fromY;
    (function step(){
      try{
        var t = Math.min(1, (Date.now()-t0)/dur);
        var e = 1 - Math.pow(1-t, 3);
        obj.position.y = fromY + (toY-fromY)*e;
        if(t < 1) requestAnimationFrame(step);
      }catch(e){}
    })();
  }

  function placePulley(x, z){
    mats();
    var gy = groundY(x, z);
    var g = new THREE.Group();
    var post = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.24, 3.2, 10), ironMat);
    post.position.y = 1.6; g.add(post);
    var wheel = new THREE.Group();
    wheel.add(new THREE.Mesh(new THREE.TorusGeometry(0.55, 0.14, 10, 24), goldMat));
    var hub = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.4, 10), goldMat);
    hub.rotation.x = Math.PI/2; wheel.add(hub);
    wheel.position.y = 3.4; g.add(wheel);
    g.position.set(x, gy-4, z);
    if(window.scene) window.scene.add(g);
    riseIn(g, gy-4, gy, 900);
    /* it spins, slowly, remembering the machine */
    (function spin(){ try{ if(!g.parent) return; wheel.rotation.z += 0.02; requestAnimationFrame(spin); }catch(e){} })();
    status('pulley placed — salvaged, standing again.');
  }

  function placeGear(x, z){
    mats();
    var gy = groundY(x, z);
    var g = new THREE.Group();
    var post = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.2, 1.6, 10), ironMat);
    post.position.y = 0.8; g.add(post);
    var gear = new THREE.Group();
    gear.add(new THREE.Mesh(new THREE.CylinderGeometry(1.1, 1.1, 0.35, 24), goldMat));
    for(var t=0;t<12;t++){
      var tooth = new THREE.Mesh(new THREE.BoxGeometry(0.28, 0.35, 0.32), goldMat);
      var a = (t/12)*Math.PI*2;
      tooth.position.set(Math.cos(a)*1.22, 0, Math.sin(a)*1.22);
      tooth.rotation.y = -a;
      gear.add(tooth);
    }
    gear.position.y = 1.9; g.add(gear);
    g.position.set(x, gy-4, z);
    if(window.scene) window.scene.add(g);
    riseIn(g, gy-4, gy, 1100);
    (function spin(){ try{ if(!g.parent) return; gear.rotation.y += 0.015; request