<!-- RUBY-BACKUP · part 392 -->
isplay='none';
var lookTarget = new T.Vector3(0, 0.6, 0);
var clock = new T.Clock();
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: COMMAND ENGINE — "a line of code to punch in and it will create the thing"
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: SHAPE CODES   — letter map            (EDIT: letters, new shapes)
   MARK: CURSOR        — relative-build origin (EDIT: start, marker)
   MARK: GRAMMAR       — the tight language    (EDIT: verbs, tokens)
   MARK: PROCEDURES    — the library           (EDIT: add/rename, keep HALF+flip)
   MARK: MIRROR        — flip                  (EDIT: mirror axis)
   MARK: LAW OF GROUND — step/displace/coexist (EDIT: the mass test)
   MARK: HARDENING     — caps & validation     (EDIT: the caps)
   MARK: COMMAND BAR   — > button + input      (EDIT: wired below)
   MARK: MAINFRAME     — the authority         (EDIT: the surface the world calls)
   Contract: parseLine(line, ec) → {ok, ops[]|error} · runOps(ops, mass)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: SHAPE CODES — one letter per shape; UPPERCASE = ghost twin
   EDIT: change letters, add shapes. Single letters only; 'x' is remove, 'b' is seed.
   the family: g triangle · u upside-down pyramid · f offset pyramid · w offset upside-down.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
var CODE_SHAPES = {c:'cube',p:'pyramid',s:'sphere',l:'cyl',t:'torus',v:'cone',o:'octa',r:'tetra',i:'icosa',d:'dodeca',g:'tri',u:'pdown',f:'poff',w:'poffdown'};
function codeToShapeKey(code){
  if(code==='b' || code==='B') return curShapeKey;   /* b = seed of the current shape */
  var low = code.toLowerCase();
  if(!CODE_SHAPES[low]) return null;
  return CODE_SHAPES[low] + (code!==low ? '~ghost' : '');
}
function snap(v){ return Math.round(v/2)*2; }        /* the 2-unit stone grid */

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: CURSOR — where relative commands build from
   EDIT: start position, marker look, persistence.
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
var cursor = {x:0, z:0};
function dpadMove(dx, dz){ cursor.x = Math.max(-49, Math.min(49, cursor.x+dx)); cursor.z = Math.max(-49, Math.min(49, cursor.z+dz)); if(typeof updateCursorDot==='function') updateCursorDot(); }
var dpadTimer = null;
function dpadStart(dx, dz){ dpadMove(dx, dz); if(dpadTimer) clearInterval(dpadTimer); dpadTimer = setInterval(function(){ dpadMove(dx, dz); }, 90); /* hold to run, with speed */ }
function dpadStop(){ if(dpadTimer){ clearInterval(dpadTimer); dpadTimer = null; } }
document.addEventListener('DOMContentLoaded', function(){
  /* top solid bar — send codes */
  var ss = document.getElementById('solidsend'), si = document.getElementById('solidinput');
  if(ss && si){
    /* the King's code box — he builds on our world */
    si.removeAttribute('readonly');
    si.placeholder = 'Cheat Codes…';
    ss.style.display = '';
    function kingSend(){ var v = si.value.trim(); if(!v) return; si.value=''; try{ Mainframe.run(v); }catch(e){} }
    ss.addEventListener('click', kingSend);
    si.addEventListener('keydown', function(e){ if(e.key==='Enter') kingSend(); });
  }
  function bindDpad(id, dx, dz){ var b=document.getElementById(id); if(!b) return;
    b.addEventListener('touchstart', function(e){ e.preventDefault(); dpadStart(dx,dz); }, {passive:false});
    b.addEventListener('touchend', function(e){ e.preventDefault(); dpadStop(); }, {passive:false});
    b.addEventListener('mousedown', function(){ dpadStart(dx,dz); });
    b.addEventListener('mouseup', function(){ dpadStop(); });
    b.addEventListener('mouseleave', function(){ dpadStop(); });
  }
  bindDpad('dpad-up', 0,-1); bindDpad('dpad-down', 0,1); bindDpad('dpad-left', -1,0); bindDpad('dpad-right', 1,0);
});
try{ var _cc = JSON.parse(localStorage.getItem('ruby-cursor-v1')||'null'); if(_cc && isFinite(_cc.x) && isFinite(_cc.z)) cursor = _cc; }catch(e){}
function saveCursor(){ try{ localStorage.setItem('ruby-cursor-v1', JSON.stringify(cursor)); }catch(e){} }
var cursorDot = new T.Mesh(new T.SphereGeometry(0.12, 10, 8), new T.MeshBasicMaterial({color:0xffe27a}));
cursorDot.visible = false; scene.add(cursorDot);
function paintCursor(){ var cy = (typeof groundY==='function') ? groundY(cursor.x, cursor.z)+0.15 : 0.15; cursorDot.position.set(cursor.x, cy, cursor.z); }
paintCursor();

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MARK: GRAMMAR — the tight language
   Atoms:
     <code> [size] <x> <z> [y]   absolute. size defaults to 1 (the seed).
                                 y = height in shells above ground. e.g. c 4 10 10 3
     <code>[size] <n><dir>      relative from cursor. dir: r/l/u/d.      e.g. b2r, l8 2r
     <code>[size]^              stack on the stone at cursor.            e.g. b^
     <code>                     seed of that