<!-- RUBY-BACKUP · part 102 -->
==='w'?'e':d==='s'?'n':'s'; }
  dirs.forEach(function(dd){
    var nx = x+dd[0], nz = z+dd[1], k = seamKey(x, z, dd[2]);
    if(stoneAt(nx, nz, y)){ removeSeam(k); return; }   /* fixed */
    if(seamMap[k] || Object.keys(seamMap).length > 300) return;
    var geo = (dd[2]==='e' || dd[2]==='w')
      ? new T.BoxGeometry(0.03, side*0.85, side)
      : new T.BoxGeometry(side, side*0.85, 0.03);
    var m = new T.Mesh(geo, red);
    m.position.set(x + dd[0]*half, y*window.MINIMUM + side*0.42, z + dd[1]*half);
    seamGrp.add(m); seamMap[k] = m;
  });
  /* this stone may fix its neighbors' seams facing it */
  dirs.forEach(function(dd){ removeSeam(seamKey(x+dd[0], z+dd[1], opp(dd[2]))); });
}
/* ---- shared map: one stones for the room, every traveler builds it ---- */
var lastStoneT = 0;
function shareStones(list){
  if(!WATCH_ID || !list.length) return;
  var arr = list.map(function(g){
    var u = g.userData, p = g.position;
    return {x:Math.round(p.x), z:Math.round(p.z), r:u.rubies||1, y:u.y||0,
      s:String(u.shape||'cube')+(u.ghost?'~ghost':'')};
  });
  fetch('/api/ruby/stones/'+encodeURIComponent(WATCH_ID), {method:'POST', headers:{'Content-Type':'application/json'},
    body:JSON.stringify({client:CLIENT_ID, name:CHAT_NAME, stones:arr})}).catch(function(){});
}
function pullStones(){
  if(!WATCH_ID) return;
  return fetch('/api/ruby/stones/'+encodeURIComponent(WATCH_ID)+'?since='+lastStoneT).then(function(r){ return r.json(); }).then(function(d){
    if(!d || !d.ok || !d.stones) return;
    d.stones.forEach(function(m){
      lastStoneT = Math.max(lastStoneT, m.t);
      if(m.client === CLIENT_ID) return;
      /* topo enforcement — nothing below the land */
      var my = m.y||0;
      try{
        var ty = topoY(m.x, m.z);
        if(ty!==null && my < Math.floor(ty+0.5)) my = Math.floor(ty+0.5);
      }catch(e){}
      if(stoneAt(m.x, m.z, my)) return;
      var g = placeStone(m.x, m.z, m.r, m.s, true, true, my, !!m.l);
      if(g){ g.userData.shared = true; g.userData.by = m.by||''; addSeamsFor(g); }
    });
  }).catch(function(){});
}
function pullChat(){
  if(!WATCH_ID) return;
  fetch('/api/ruby/chat/'+encodeURIComponent(WATCH_ID)+'?since='+lastChatT).then(function(r){ return r.json(); }).then(function(d){
    if(!d || !d.ok) return;
    var first = (lastChatT===0);
    d.msgs.forEach(function(m){
      lastChatT = Math.max(lastChatT, m.t);
      if(!first) floatWord(m);
    });
  }).catch(function(){});
}
function sendChat(){
  var inp = document.getElementById('chatinput');
  if(!inp) return;
  var text = inp.value.trim();
  if(!text || !WATCH_ID) return;
  inp.value = '';
  try{ inp.blur(); }catch(e){}
  fetch('/api/ruby/chat/'+encodeURIComponent(WATCH_ID), {method:'POST', headers:{'Content-Type':'application/json'},
    body:JSON.stringify({client:CLIENT_ID, name:CHAT_NAME, text:text})}).then(function(){ pullChat(); }).catch(function(){});
}
var _a1=new T.Vector3(), _a2=new T.Vector3(), _a3=new T.Vector3();
function buildCircleAvatar(color){
  var g = new T.Group();
  var m = new T.Mesh(A_GEO.ring, new T.MeshBasicMaterial({color:color||0xffd700, transparent:true, opacity:0.95, side:T.DoubleSide}));
  m.rotation.x = -Math.PI/2; m.position.y = 0.02; g.add(m);
  var d = new T.Mesh(A_GEO.dot, new T.MeshBasicMaterial({color:color||0xffd700, transparent:true, opacity:0.5, side:T.DoubleSide}));
  d.rotation.x = -Math.PI/2; d.position.y = 0.02; g.add(d);
  return g;
}

/* ROUNDED — no shape corners, everything smooth like the reference shoes */
function roundedBox(w, h, d, r){
  r = Math.min(r||0.01, Math.min(w,h,d)/2.1);
  var s = new T.Shape();
  var x = -w/2, y = -h/2;
  s.moveTo(x+r, y);
  s.lineTo(x+w-r, y); s.quadraticCurveTo(x+w, y, x+w, y+r);
  s.lineTo(x+w, y+h-r); s.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
  s.lineTo(x+r, y+h); s.quadraticCurveTo(x, y+h, x, y+h-r);
  s.lineTo(x, y+r); s.quadraticCurveTo(x, y, x+r, y);
  var g = new T.ExtrudeGeometry(s, {depth:d-2*r, bevelEnabled:true, bevelThickness:r, bevelSize:r, bevelSegments:3});
  g.translate(0, 0, -(d-2*r)/2);
  return g;
}

/* PROVOLTA as the player's body — warrior.glb */

/* WHITE TABLE — built together, pearl lesson from PROVOLTA */

/* INTRICATE WHITE TABLE — designed together, piece by piece */
function buildIntricateTable(x, z){
  if(typeof scene==='undefined') return;
  var g = new THREE.Group();
  /* materials — pearl white family */
  var pearl = new THREE.MeshStandardMaterial({color: 0xf5f3ee, metalness: 0.35, roughness: 0.22});
  var pearlDark = new THREE.MeshStandardMaterial({color: 0xe8e4da, metalness: 0.3, roughness: 0.3});
  var goldTrim = new THREE.MeshStandardMaterial({color: 0xd4af37, metalness: 0.9, roughness: 0.2});
  var shadow = new THREE.MeshStandardMaterial({color: 0xd0ccc0, metalness: 0.2, roughness: 0.4});

  /* === TABLETOP — layered with inlay === */
  /* main slab */
  var topMain = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.12, 2.2), pearl);
  topMain.position.y = 1.15