<!-- RUBY-BACKUP · part 345 -->
okeStyle = 'rgba(255,235,170,0.4)';
  for(var r=8;r<360;r+=9){
    x.beginPath();
    for(var a=0;a<=Math.PI*2+0.01;a+=0.05){
      var rr = r + Math.sin(a*9)*3, p = [256+Math.cos(a)*rr, 256+Math.sin(a)*rr];
      if(a===0) x.moveTo(p[0],p[1]); else x.lineTo(p[0],p[1]);
    }
    x.stroke();
  }
  var t = new T.CanvasTexture(c);
  t.wrapS = t.wrapT = T.RepeatWrapping;
  return t;
}
var familyMat = new T.MeshStandardMaterial({map:makeFamilyTex(), roughness:0.35, metalness:0.85, emissive:0x3a2a08, emissiveIntensity:0.25});
var FAMILY = {pyramid:1, tri:1, pdown:1, poff:1, poffdown:1};

/* render modes: solid gold, nested intaglio shells, camera centered on largest, intaglio radiating from largest */
var RENDER_MODES = ['solid','nested','center','radiate'];
var RENDER_LABELS = {solid:'Solid', nested:'Nested', center:'Center', radiate:'Radiate'};
var RENDER_ICONS = {
  solid: '<svg viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" fill="#ffd700"/></svg>',
  nested: '<svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" fill="none" stroke="#ffd700" stroke-width="2"/><rect x="8.5" y="8.5" width="7" height="7" fill="none" stroke="#ffd700" stroke-width="1.5"/><rect x="11" y="11" width="2" height="2" fill="#ffd700"/></svg>',
  center: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8" fill="none" stroke="#ffd700" stroke-width="2"/><circle cx="12" cy="12" r="2" fill="#ffd700"/></svg>',
  radiate: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="2.5" fill="none" stroke="#ffd700" stroke-width="2"/><circle cx="12" cy="12" r="6.5" fill="none" stroke="#ffd700" stroke-width="1.5"/><path d="M12 1 L12 5 M12 19 L12 23 M1 12 L5 12 M19 12 L23 12" stroke="#ffd700" stroke-width="1.5"/></svg>'
};
var renderMode = 'solid';
try{ var _rm = localStorage.getItem('ruby-render-mode'); if(RENDER_MODES.indexOf(_rm)>=0) renderMode = _rm; }catch(e){}

var stones = new T.Group(); scene.add(stones);
var shapeBaseGeo = {};
function baseGeo(shape){
  if(!shapeBaseGeo[shape]) shapeBaseGeo[shape] = shapeGeo(shape, window.MINIMUM);
  return shapeBaseGeo[shape];
}
/* each stone is a group at its ground cell; its look is rebuilt for the render mode */
function buildStoneVisual(g){
  for(var i=g.children.length-1;i>=0;i--) g.remove(g.children[i]);
  var ud = g.userData, base = ud.shape||'cube', k1 = ud.k1||1, r = ud.rubies||1;
  var ghostly = !!ud.ghost;
  if(renderMode==='nested' && !ghostly){
    for(var i=1;i<=r;i++){
      var sh = new T.Mesh(baseGeo(base), intaglioMat);
      var sc = i*k1;
      sh.scale.set(sc,sc,sc);
      sh.position.y = sc*window.MINIMUM/2;
      g.add(sh);
    }
  }else{
    /* the ghost twin is always its see-through intaglio self, in every mode */
    var m = new T.Mesh(baseGeo(base), (ghostly || LINEWORK_ON) ? intaglioMat : (FAMILY[base] ? familyMat : stoneMat));
    var fs = r*k1;
    m.scale.set(fs,fs,fs);
    m.position.y = fs*window.MINIMUM/2;
    g.add(m);
  }
}
function stoneGroupOf(o){ while(o){ if(o.userData && o.userData.stone) return o; o = o.parent; } return null; }
function stoneAt(cx, cz, yRubies){
  yRubies = yRubies||0;
  for(var i=0;i<stones.children.length;i++){
    var s = stones.children[i];
    if(Math.abs(s.position.x-cx)<0.01 && Math.abs(s.position.z-cz)<0.01 && Math.abs((s.userData.y||0)-yRubies)<0.01) return s;
  }
  return null;
}
function largestStone(){
  var best = null, br = 0;
  stones.children.forEach(function(g){ var r = g.userData.rubies||0; if(r>br){ br = r; best = g; } });
  return best;
}
function save(){
  if(SHARED){ broadcastWatch(); }
  else if(!VIEW_ONLY){
    try{ localStorage.setItem(LSKEY, JSON.stringify(watchCells())); }catch(e){}
  }
  updateAnchors();
}
function placeStone(cx, cz, rubies, shapeKey, instant, quiet, yRubies, onLand){
  /* branching: auto-enter creator mode on manual builds */
  if(!quiet && typeof creatorTouch==='function') creatorTouch();
  yRubies = yRubies||0;
  /* branching: if occupied, stack upward instead of failing */
  var tryY = yRubies;
  while(stoneAt(cx, cz, tryY) && tryY < yRubies+20){
    tryY++;
  }
  if(stoneAt(cx, cz, tryY)) return null;
  yRubies = tryY;
  rubies = Math.max(1, Math.round(rubies||1));
  var base = shapeBase(shapeKey), ghostly = shapeIsGhost(shapeKey);
  var g = new T.Group();
  var land = (onLand!==false);
  var baseY = land ? fortifiedGroundY(cx, cz) : 0;
  g.userData = {stone:true, rubies:rubies, shape:base, ghost:ghostly, k1:legalK1(base), y:yRubies, land:land};
  var stoneBaseY = baseY + yRubies*window.MINIMUM;
  g.position.set(cx, stoneBaseY, cz);
  /* slope foundation — small blocks fill the gap where the land falls away */
  if(land && !ghostly){
    var foot = rubies * (g.userData.k1||1) * window.MINIMUM;
    [[-1,-1],[1,-1],[-1,1],[1,1]].forEach(function(cn){
      var fx = cx + cn[0]*foot*0.35, fz = cz + cn[1]*foot*0.35;
      var fh = groundY(fx, fz);
      var gap = stoneBaseY - fh, n = Math.min(6, Math.floor(gap/window.MINIMUM)-1);
      for(var fi=