<!-- RUBY-BACKUP · part 365 -->
/* holds 1 only — him or her */
    ship.userData.passenger = null;
    ship.userData.usable = true; /* usable from the map — travel from day one */
    ship.userData.sellable = false; /* unsellable — it is yours, your 1 block */
    PLAYER_SHIP = ship;
    return ship;
  }catch(e){ return null; }
}

/* SHIP LAW: 1 passenger only — him or her, never both, never more */

/* SHIP LAW: it sits on the deck unusable unsellable — until earned */
function canUseShip(ship){
  try{
    if(!ship || !ship.userData) return false;
    return ship.userData.usable === true;
  }catch(e){ return false; }
}
function tryUseShip(ship){
  if(!canUseShip(ship)){
    try{ codeStatus('the ship sits — unusable. Earn it.'); }catch(e){}
    return false;
  }
  return true;
}
function tryBoardShip(ship, who){
  try{
    if(!ship || !ship.userData) return false;
    if(ship.userData.passenger){
      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