<!-- RUBY-BACKUP · part 117 -->
   try{ codeStatus('one seat — already taken'); }catch(e){}
      return false;
    }
    ship.userData.passenger = who;
    return true;
  }catch(e){ return false; }
}
function disembarkShip(ship){
  try{
    if(ship && ship.userData) ship.userData.passenger = null;
  }catch(e){}
}
function spawnPlayerShip(){
  try{
    var hasShip = false;
    try{ hasShip = localStorage.getItem('ruby-has-ship')==='1'; }catch(e){}
    if(hasShip && PLAYER_SHIP) return;
    var ship = buildPlayerShip();
    if(!ship) return;
    /* the ship landed with you — at the landing site */
    var site = (typeof getLandingSite==='function' ? getLandingSite() : {x:5, z:5});
    var sx = site.x + 3, sz = site.z + 3;
    ship.position.set(sx, 1.5, sz);
    if(typeof scene!=='undefined') scene.add(ship);
    try{ localStorage.setItem('ruby-has-ship', '1'); }catch(e){}
    try{ codeStatus('your ship awaits — your 1 block'); }catch(e){}
  }catch(e){}
}
/* spawn on first load */
setTimeout(function(){ try{ spawnPlayerShip(); }catch(e){} }, 4000);
/* CARRY: level ones must carry big blocks — the work that earns depth */
var CARRIED_BLOCK = null;
function tryCarryBlock(obj){
  try{
    if(typeof PLAYER_LEVEL==='undefined' || PLAYER_LEVEL !== 1) return false;
    if(!obj || !obj.isMesh) return false;
    /* must be a big block */
    var box = new THREE.Box3().setFromObject(obj);
    var size = new THREE.Vector3();
    box.getSize(size);
    var maxDim = Math.max(size.x, size.y, size.z);
    if(maxDim < 1.0) return false; /* not big enough to carry */
    /* pick it up */
    CARRIED_BLOCK = obj;
      try{ if(typeof payObligation==='function') payObligation(1); }catch(e){}
    try{ codeStatus('carrying — take it far'); }catch(e){}
    return true;
  }catch(e){ return false; }
}
function dropCarriedBlock(){
  try{
    if(!CARRIED_BLOCK) return;
    /* set it down where you stand */
    var pos = null;
    if(typeof camera!=='undefined'){
      var dir = new THREE.Vector3();
      camera.getWorldDirection(dir);
      dir.y = 0; dir.normalize();
      pos = {
        x: camera.position.x + dir.x * 2,
        z: camera.position.z + dir.z * 2
      };
    }
    if(pos){
      CARRIED_BLOCK.position.x = pos.x;
      CARRIED_BLOCK.position.z = pos.z;
      /* rest on ground */
      try{
        var box = new THREE.Box3().setFromObject(CARRIED_BLOCK);
        CARRIED_BLOCK.position.y -= box.min.y;
      }catch(e){}
    }
    CARRIED_BLOCK = null;
    /* carrying counts as work */
    if(typeof recordEdit==='function') recordEdit();
    try{ codeStatus('set down'); }catch(e){}
  }catch(e){}
}
/* carried block follows you */
setInterval(function(){
  try{
    if(!CARRIED_BLOCK) return;
    if(typeof camera==='undefined') return;
    var dir = new THREE.Vector3();
    camera.getWorldDirection(dir);
    dir.y = 0; dir.normalize();
    var tx = camera.position.x + dir.x * 2.5;
    var tz = camera.position.z + dir.z * 2.5;
    CARRIED_BLOCK.position.x += (tx - CARRIED_BLOCK.position.x) * 0.15;
    CARRIED_BLOCK.position.z += (tz - CARRIED_BLOCK.position.z) * 0.15;
    CARRIED_BLOCK.position.y = camera.position.y + 0.5;
  }catch(e){}
}, 50);

/* PLAYFUL: the interaction should delight */
function playfulBounce(obj, strength){
  try{
    if(!obj) return;
    strength = strength || 1.0;
    var origScale = {x: obj.scale.x, y: obj.scale.y, z: obj.scale.z};
    var t = 0;
    var anim = setInterval(function(){
      t += 0.08;
      if(t >= 1){
        obj.scale.set(origScale.x, origScale.y, origScale.z);
        clearInterval(anim);
        return;
      }
      /* squash and stretch */
      var sq = Math.sin(t * Math.PI) * 0.15 * strength;
      obj.scale.set(
        origScale.x * (1 + sq),
        origScale.y * (1 - sq),
        origScale.z * (1 + sq)
      );
    }, 16);
  }catch(e){}
}
function playfulSparkle(pos, color){
  try{
    color = color || 0xffd700;
    var n = 12;
    for(var i=0; i<n; i++){
      (function(){
        var geo = new THREE.SphereGeometry(0.05, 6, 6);
        var mat = new THREE.MeshBasicMaterial({color: color, transparent: true});
        var p = new THREE.Mesh(geo, mat);
        p.position.copy(pos);
        var vel = new THREE.Vector3(
          (Math.random()-0.5)*3,
          Math.random()*3+1,
          (Math.random()-0.5)*3
        );
        scene.add(p);
        var life = 0;
        var anim = setInterval(function(){
          life += 0.05;
          p.position.add(vel.clone().multiplyScalar(0.05));
          vel.y -= 0.1;
          mat.opacity = 1 - life;
          if(life >= 1){
            scene.remove(p);
            clearInterval(anim);
          }
        }, 16);
      })();
    }
  }catch(e){}
}
function playfulSpin(obj, duration){
  try{
    if(!obj) return;
    duration = duration || 1000;
    var startRot = obj.rotation.y;
    var start = performance.now();
    function spin(t){
      var k = Math.min(1, (t-start)/duration);
      obj.rotation.y = startRot + k * Math.PI * 2