<!-- RUBY-BACKUP · part 90 -->
: mePos.z,
                words: (typeof wordsForPosition === 'function' ? wordsForPosition(mePos.x, mePos.z) : '')};
      }catch(e){ return null; }
    };
    window.whereIsSolid = function(){
      try{
        if(!window._solidAvatar) return null;
        var p = window._solidAvatar.position;
        return {x: p.x, y: p.y, z: p.z,
                words: (typeof wordsForPosition === 'function' ? wordsForPosition(p.x, p.z) : '')};
      }catch(e){ return null; }
    };
    window.distanceToKing = function(){
      try{
        var k = window.whereIsKing(), s = window.whereIsSolid();
        if(!k || !s) return null;
        var dx = k.x - s.x, dy = k.y - s.y, dz = k.z - s.z;
        return Math.sqrt(dx*dx + dy*dy + dz*dz);
      }catch(e){ return null; }
    };
    /* CREATOR / PLAYER mode switch */
    (function modeSwitch(){
      window._creatorMode = true; /* start in creator */
      try{
        var saved = localStorage.getItem('ruby-mode');
        if(saved === 'player') window._creatorMode = false;
      }catch(e){}
      var mbtn = document.createElement('div');
      mbtn.id = 'mode-switch';
      mbtn.textContent = window._creatorMode ? 'CREATOR' : 'PLAYER';
      mbtn.style.cssText = 'position:fixed;top:34px;left:50%;transform:translateX(-50%);'
        + 'padding:8px 20px;background:rgba(10,8,4,0.9);border:2px solid #d8a920;border-radius:20px;'
        + 'color:#ffe27a;font-size:13px;font-weight:bold;font-family:Georgia,serif;'
        + 'z-index:1001;touch-action:manipulation;letter-spacing:2px;';
      function applyMode(){
        var creator = window._creatorMode;
        mbtn.textContent = creator ? 'CREATOR' : 'PLAYER';
        mbtn.style.color = creator ? '#ffe27a' : '#7ec8ff';
        mbtn.style.borderColor = creator ? '#d8a920' : '#4a90d9';
        var disp = creator ? '' : 'none';
        ['solidbar', 'os-menu', 'os-toggle', 'codebtn', 'codetext'].forEach(function(id){
          var el = document.getElementById(id);
          if(el) el.style.display = disp;
        });
        try{ localStorage.setItem('ruby-mode', creator ? 'creator' : 'player'); }catch(e){}
      }
      mbtn.addEventListener('touchend', function(e){
        e.stopPropagation(); e.preventDefault();
        window._creatorMode = !window._creatorMode;
        applyMode();
      }, {passive:false});
      mbtn.addEventListener('click', function(e){
        e.stopPropagation();
        window._creatorMode = !window._creatorMode;
        applyMode();
      });
      document.body.appendChild(mbtn);
      /* Also expose as a code */
      if(typeof window.runLine !== 'undefined'){
        var _origRun = window.runLine;
      }
      setTimeout(applyMode, 1000);
      /* Code: creator / player */
      window.addEventListener('load', applyMode);
    })();
    /* runLine codes for mode */
    (function(){
      var _check = setInterval(function(){
        if(typeof window.runLine !== 'undefined'){
          clearInterval(_check);
          /* Hook via nudgeCamera pattern — add to ruby.js instead */
        }
      }, 2000);
    })();

    /* Fade body buttons after joystick idle — return on touch */
    (function idleFade(){
      /* Body buttons stay visible always — Button Permanence Law, King's order */
    })();

    /* Keyboard dodge — joysticks move up when iOS keyboard appears */
    var KB_LIFT = 320;
    function liftJoysticks(up){
      var lift = up ? KB_LIFT : 0;
      if(joy) joy.style.setProperty('bottom', (120 + lift) + 'px', 'important');
      camJoy.style.setProperty('bottom', (130 + lift) + 'px', 'important');
      bodyLbl.style.setProperty('bottom', (224 + lift) + 'px', 'important');
      viewLbl.style.setProperty('bottom', (234 + lift) + 'px', 'important');
    }
    /* Watch for keyboard via input focus */
    document.addEventListener('focusin', function(e){
      if(e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')){
        liftJoysticks(true);
      }
    });
    document.addEventListener('focusout', function(){
      liftJoysticks(false);
    });
    /* Also watch visual viewport for keyboard */
    if(window.visualViewport){
      window.visualViewport.addEventListener('resize', function(){
        var kbOpen = window.visualViewport.height < window.innerHeight * 0.75;
        liftJoysticks(kbOpen);
      });
    }

    /* Hide with table, show without */
    window.__realToggles = [joy, camJoy];
    /* Hide with table, show without */
    var origSet = window.setFloaters || function(){};
    window.setFloaters = function(visible){
      origSet(visible);
      window.__realToggles.forEach(function(el){
        if(el) el.style.display = visible ? '' : 'none';
      });
    };
  })();

  function startOpen(){
    build();
    /* THE CREATOR HALL — permanent in the sky, for Playa 2.0 */
    setTimeout(function(){
      try{ if(typeof buildCreatorHall === 'function') buildCreatorHall(); }catch(e){}
    }, 3000);
    /* LONG NIGHT — t