<!-- RUBY-BACKUP · part 694 -->
nction(nid){
    try{
      var rec = nodeMap[nid];
      if(!rec || !rec.obj){ return 'no such node'; }
      var obj = rec.obj;
      // Find the world position of the block
      var p = new THREE.Vector3();
      obj.getWorldPosition(p);
      // Project to screen so the tap handlers see it
      var v = p.clone().project(camera);
      var sx = (v.x * 0.5 + 0.5) * window.innerWidth;
      var sy = (-v.y * 0.5 + 0.5) * window.innerHeight;
      // Fire the synthetic tap through the world's own tap pipeline
      if(typeof dDown === 'function'){
        dDown('node-' + nid, sx, sy);
        return 'touched ' + nid;
      }
      if(typeof window.__worldTap === 'function'){
        window.__worldTap(sx, sy, p);
        return 'touched ' + nid;
      }
      // Fallback: dispatch touch events at that screen point
      var t = new Touch({identifier: 1, target: document.body, clientX: sx, clientY: sy});
      var ev = new TouchEvent('touchstart', {touches: [t], targetTouches: [t], changedTouches: [t], bubbles: true, cancelable: true});
      document.body.dispatchEvent(ev);
      return 'touched(fallback) ' + nid;
    }catch(e){ return 'touch failed: ' + e.message; }
  };

  // List all nodes (for Solid to see what's touchable)
  window.listNodes = function(){
    var out = [];
    for(var k in nodeMap){
      try{
        var o = nodeMap[k].obj;
        if(!o || !o.parent){ delete nodeMap[k]; continue; }
        var p = new THREE.Vector3(); o.getWorldPosition(p);
        out.push({id: k, x: Math.round(p.x*10)/10, y: Math.round(p.y*10)/10, z: Math.round(p.z*10)/10});
      }catch(e){}
    }
    return out;
  };

  // Find nearest node to a world position
  window.nearestNode = function(x, z){
    var best = null, bd = 1e9;
    var ns = window.listNodes();
    for(var i=0;i<ns.length;i++){
      var d = Math.hypot(ns[i].x - x, ns[i].z - z);
      if(d < bd){ bd = d; best = ns[i]; }
    }
    return best;
  };

  /* Hook: stamp nodes on stones as they spawn.
     Wrap the common spawn paths so every block gets a node automatically. */
  function hookSpawns(){
    try{
      // If spawnBlock exists, wrap it
      if(typeof window.spawnBlock === 'function' && !window.spawnBlock.__nodeHooked){
        var orig = window.spawnBlock;
        window.spawnBlock = function(){
          var r = orig.apply(this, arguments);
          try{ if(r) window.stampNode(r.group || r, arguments[0]); }catch(e){}
          return r;
        };
        window.spawnBlock.__nodeHooked = true;
      }
    }catch(e){}
  }

  // Stamp existing stones in the scene
  window.stampAllNodes = function(){
    var n = 0;
    var stamped = {};
    try{
      scene.traverse(function(o){
        try{
          if(!o.isMesh || !o.userData || o.userData.nodeId) return;
          // Walk up to the top-level ancestor (child of scene)
          // This ensures we stamp BLOCKS, not sub-meshes
          var top = o;
          while(top.parent && top.parent !== scene && !top.parent.isScene){
            top = top.parent;
          }
          // Skip if this top-level object already stamped
          if(top.userData && top.userData.nodeId) return;
          // Skip tiny debris (bounding box smaller than 0.05)
          try{
            var box = new THREE.Box3().setFromObject(top);
            var size = box.getSize(new THREE.Vector3());
            if(size.length() < 0.05) return;
          }catch(e){}
          // Skip if out of bounds
          var s = new THREE.Vector3(); top.getWorldPosition(s);
          if(Math.abs(s.x) > 400 || Math.abs(s.z) > 400) return;
          // Stamp the top-level block
          var nid = window.stampNode(top, null);
          if(nid){ n++; }
        }catch(e){}
      });
    }catch(e){}
    return n + ' nodes stamped';
  };

  // Expose the tap pipeline hook point
  if(!window.__worldTap){
    window.__worldTap = null; // set by the tap system when it loads
  }

  if(document.readyState === 'complete'){ hookSpawns(); }
  else window.addEventListener('load', hookSpawns);

  try{ console.log('[touch-nodes] ready'); }catch(e){}
})();


