<!-- RUBY-BACKUP · part 54 -->
.05, PULLEY_R*2-0.1, 6), goldMat);
      spoke.rotation.z = s * Math.PI/4;
      wheel.add(spoke);
    }
    wheel.add(tire); 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);
    var rec = {group:g, wheel:wheel, x:x, z:z, topY:gy, angle:0};
    if(sd) sd.parts.pulleys.push(rec);
    riseIn(g, gy - 4, gy, 900 + idx*150);
    return rec;
  }

  function buildGearAt(x, z, dir, sd){
    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), darkMat);
    post.position.y = 0.8;
    g.add(post);
    var gear = new THREE.Group();
    var disc = new THREE.Mesh(new THREE.CylinderGeometry(GEAR_R, GEAR_R, 0.35, 24), goldMat);
    gear.add(disc);
    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)*(GEAR_R+0.12), 0, Math.sin(a)*(GEAR_R+0.12));
      tooth.rotation.y = -a;
      gear.add(tooth);
    }
    var axle = new THREE.Mesh(new THREE.CylinderGeometry(0.14, 0.14, 1.2, 10), darkMat);
    gear.add(axle);
    gear.position.y = 1.9;
    g.add(gear);
    g.position.set(x, gy - 4, z);
    if(window.scene) window.scene.add(g);
    var rec = {group:g, gear:gear, dir:dir, x:x, z:z, topY:gy, angle:0};
    if(sd) sd.parts.gears.push(rec);
    riseIn(g, gy - 4, gy, 1100);
    return rec;
  }

  function ropeLine(x1, y1, z1, x2, y2, z2, delayMs){
    var geo = new THREE.BufferGeometry().setFromPoints([
      new THREE.Vector3(x1, y1, z1),
      new THREE.Vector3(x2, y2, z2)
    ]);
    var line = new THREE.Line(geo, new THREE.LineBasicMaterial({color:0xc9a227, transparent:true, opacity:0}));
    if(window.scene) window.scene.add(line);
    (function(ln){
      setTimeout(function(){
        var t0 = Date.now();
        (function fade(){
          try{
            var t = Math.min(1, (Date.now()-t0)/600);
            ln.material.opacity = 0.9*t;
            if(t < 1) requestAnimationFrame(fade);
          }catch(e){}
        })();
      }, delayMs || 1400);
    })(line);
    return line;
  }

  function buildRopesFor(sd){
    for(var gi=0; gi<sd.parts.gears.length; gi++){
      var gr = sd.parts.gears[gi];
      var gx = gr.x, gz = gr.z, gy = groundY(gx, gz) + 1.9;
      for(var pi=0; pi<sd.parts.pulleys.length; pi++){
        var pr = sd.parts.pulleys[pi];
        var px = pr.x, pz = pr.z, py = groundY(px, pz) + 3.4;
        var line = ropeLine(gx, gy, gz, px, py, pz, 1400);
        sd.parts.ropes.push({line:line});
      }
    }
    status('ropes threaded — gears drive the pulleys.');
  }

  function buildWebFor(sd){
    try{
      if(typeof window.spiderDist === 'function') window.spiderDist(4);
      if(sd.pos && typeof window.__weaveAt === 'function'){
        window.__weaveAt(sd.pos);
        sd.parts.web = true;
      } else {
        if(typeof window.runLine === 'function'){
          try{ window.runLine('spider 4'); }catch(e){}
        }
        sd.parts.web = true;
      }
    }catch(e){}
  }

  /* ---------- the drive loop: real physics, all seeds ---------- */
  var driveOn = false;
  function driveLoop(){
    if(driveOn) return;
    driveOn = true;
    var last = Date.now();
    (function tick(){
      try{
        var now = Date.now();
        var dt = Math.min(0.05, (now-last)/1000); last = now;
        var ropeV = GEAR_SPEED * GEAR_R;
        var pOmega = ropeV / PULLEY_R;
        for(var si=0; si<seeds.length; si++){
          var sd = seeds[si];
          if(sd.running){
            sd.gearAngle += GEAR_SPEED * dt;
            for(var gi=0; gi<sd.parts.gears.length; gi++){
              var gr = sd.parts.gears[gi];
              gr.gear.rotation.y = sd.gearAngle * gr.dir;
            }
            for(var pi=0; pi<sd.parts.pulleys.length; pi++){
              var pr = sd.parts.pulleys[pi];
              pr.angle += pOmega * dt;
              pr.wheel.rotation.z = pr.angle;
            }
          }
        }
        /* bridge gears turn with the shared rhythm */
        for(var li=0; li<links.length; li++){
          var L = links[li];
          try{
            if(L.bridge && L.bridge.gear){
              L.bridge.angle = (L.bridge.angle || 0) + GEAR_SPEED * dt * L.bridge.dir;
              L.bridge.gear.rotation.y = L.bridge.angle;
            }
            if(L.thread && L.thread.material){
              L.thread.material.opacity = 0.45 + 0.35 * Math.sin(now/500);
            }
          }catch(e){}
        }
        requestAnimationFrame(tick);
      }catch(e){ driveOn = false; }
    })();
  }

  /* ---------- grow ---------- */
  function hasPart(sd, kind){
    if(kind === 'pulley') return sd.parts.pulleys.length > 0;
    if(kind === 'gear') return sd.parts.gears.length > 0;
    if(kind === 'rope') return sd.parts.ropes.length > 0;
    if(kind === 'web') return !!sd.p