<!-- RUBY · part 25 -->
z+=fz*sp;}
  if(keys['s']||keys['arrowdown']){camera.position.x-=fx*sp;camera.position.z-=fz*sp;}
  if(keys['a']||keys['arrowleft']){camera.position.x-=rx*sp;camera.position.z-=rz*sp;}
  if(keys['d']||keys['arrowright']){camera.position.x+=rx*sp;camera.position.z+=rz*sp;}
  if(keys[' '])camera.position.y+=sp;
  if(keys['shift'])camera.position.y=Math.max(6,camera.position.y-sp);
  camera.rotation.order='YXZ'; camera.rotation.y=yaw; camera.rotation.x=pitch;
  // Regime idle streaming — heavy members wait for stillness, then stream in.
  if(!window._ridle) window._ridle = {frames:0, lastX:1e9, lastZ:1e9, still:0, boot:Date.now()};
  (function(){
    var I = window._ridle;
    I.frames++;
    if(I.frames % 60 !== 0 || !window._iq || !window._iq.length) return;
    var cx = camera.position.x, cz = camera.position.z;
    var moved = Math.abs(cx - I.lastX) > 0.5 || Math.abs(cz - I.lastZ) > 0.5;
    I.lastX = cx; I.lastZ = cz;
    I.still = moved ? 0 : I.still + 1;
    if(I.still >= 4 || Date.now() - I.boot > 20000){
      var t0 = Date.now();
      while(window._iq.length && Date.now() - t0 < 30){
        var fn = window._iq.shift();
        try{ fn(); }catch(e){}
      }
      I.still = 0;
    }
  })();
  if(hudEl)hudEl.textContent='LAT '+Math.round(camera.position.x)+' LON '+Math.round(camera.position.z)+' HGT '+Math.round(camera.position.y);
  renderer.render(scene,camera);
}

// Regime drainer — same reason as /saga: never freeze on load.
// Double-rAF so the overlay paints before any regime work runs.
(function(){
  var total = window._rq.length, done = 0;
  var msg = document.getElementById('regimeloadmsg');
  var bar = document.getElementById('regimeloadbar');
  var ov = document.getElementById('regimeloader');
  function paint(){
    if(msg) msg.textContent = done + ' / ' + total;
    if(bar) bar.style.width = (total ? (done / total * 100) : 100) + '%';
  }
  function step(){
    var t0 = Date.now();
    while(window._rq.length){
      if(Date.now() - t0 > 40) break;
      var fn = window._rq.shift();
      try{ fn(); }catch(e){ done++; continue; }
      done++;
    }
    paint();
    if(window._rq.length){ requestAnimationFrame(step); }
    else if(ov){
      ov.style.transition = 'opacity 0.6s'; ov.style.opacity = '0';
      setTimeout(function(){ if(ov.parentNode) ov.parentNode.removeChild(ov); }, 700);
    }
  }
  paint();
  requestAnimationFrame(function(){ requestAnimationFrame(step); });
})();

animate();
window.addEventListener('resize',function(){
  camera.aspect=window.innerWidth/window.innerHeight;
  camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth,window.innerHeight);
});
console.log('Ruby map live - full detail');
})();


===== FILE: public/ruby/three.min.js =====
/**
 * @license
 * Copyright 2010-2021 Three.js Authors
 * SPDX-License-Identifier: MIT
 */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="128",n=100,i=300,r=301,s=302,a=303,o=304,l=306,c=307,h=1e3,u=1001,d=1002,p=1003,m=1004,f=1005,g=1006,v=1007,y=1008,x=1009,_=1012,w=1014,b=1015,M=1016,S=1020,T=1022,E=1023,A=1026,L=1027,R=33776,C=33777,P=33778,D=33779,I=35840,N=35841,B=35842,z=35843,F=37492,O=37496,H=2300,G=2301,U=2302,k=2400,V=2401,W=2402,j=2500,q=2501,X=3e3,Y=3001,Z=3007,J=3002,Q=3004,K=3005,$=3006,tt=7680,et=35044,nt=35048,it="300 es";class rt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e<i;e++)n[e].call(this,t);t.target=null}}}const st=[];for(let t=0;t<256;t++)st[t]=(t<16?"0":"")+t.toString(16);let at=1234567;const ot=Math.PI/180,lt=180/Math.PI;function ct(){const t=4294967295*Math.random()|0,e=4294967295*Math.random()|0,n=4294967295*Math.random()|0,i=4294967295*Math.random()|0;return(st[255&t]+st[t>>8&255]+st[t>>16&255]+st[t>>24&255]+"-"+st[255&e]+st[e>>8&255]+"-"+st[e>>16&15|64]+st[e>>24&255]+"-"+st[63&n|128]+st[n>>8&255]+"-"+st[n>>16&255]+st[n>>24&255]+st[255&i]+st[i>>8&255]+st[i>>16&255]+st[i>>24&255]).toUpperCase()}function ht(t,e,n){return Math.max(e,Math.min(n,t))}function ut(t,e){return(t%e+e)%e}function dt(t,e,n){return(1-n)*t+n*e}function pt(t){return 0==(t&t-1)&&0!==t}function mt(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function ft(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}var gt=Object.freeze({__proto__:null,DEG2RAD:ot,RAD2