<!-- RUBY-BACKUP · part 60 -->
rebuild from scratch.
   The King: "But you can capture areas to keep and erase and rebuild
   from scratch."

   This is NOT time travel — it is a chosen fresh start. The memory
   birth-limit does not apply: this is forward building from chosen
   pieces, not backward rewind.

   Codes:
     capture area <name> [here] — save stones + node modes + spans in radius
     areas                      — list captured areas
     forget area <name>         — delete a captured area
     erase                      — wipe dynamic systems (stones stay sacred)
     erase yes                  — confirm
     erase all                  — warn: also removes non-sacred stones
     erase all yes              — confirm the nuclear option
     rebuild from <name>        — stones rise, systems return
     sacred                     — flag nearest stone sacred (spared by erase all)

   Law honored: "Everything built up to now can not be reimagined by
   memory." Rebuild never alters existing stones — it only raises what is
   missing. Erase all never touches sacred stones.

   Key: ruby-areas-v1 — {name: {t, cx, cz, radius, stones:[], nodeDump:{}}}
   ============================================================================ */
(function(){
  if(window.__phoenix) return;
  window.__phoenix = true;

  var AKEY = 'ruby-areas-v1';
  var RADIUS = 30;

  /* the 6 significant blocks — inherently sacred (mirror of autoload defaults) */
  var SIG_POS = [[-40,0],[40,0],[0,-40],[0,35],[-25,40],[0,0]];

  function status(m){ try{ if(typeof codeStatus==='function') codeStatus(m); }catch(e){} }

  function playerPos(){
    try{
      if(typeof WALK !== 'undefined' && WALK && typeof mePos !== 'undefined' && mePos)
        return {x: mePos.x, z: mePos.z};
      if(typeof cursor !== 'undefined' && cursor) return {x: cursor.x || 0, z: cursor.z || 0};
    }catch(e){}
    return {x:0, z:0};
  }

  function cleanName(raw){
    var n = String(raw == null ? '' : raw).trim().slice(0, 40);
    return n.replace(/[^a-zA-Z0-9 _-]/g, '').trim();
  }

  function loadAreas(){
    var a = {};
    try{ var raw = localStorage.getItem(AKEY); if(raw) a = JSON.parse(raw) || {}; }catch(e){ a = {}; }
    return a;
  }
  function saveAreas(a){ try{ localStorage.setItem(AKEY, JSON.stringify(a)); }catch(e){} }

  function ageStr(t){
    var d = Date.now() - t;
    if(d < 0) d = 0;
    if(d < 60000) return 'just now';
    if(d < 3600000){ var m = Math.floor(d/60000); return m + (m===1?' minute ago':' minutes ago'); }
    return Math.floor(d/3600000) + 'h ago';
  }

  /* ---------- sacred ---------- */
  function isSacred(g){
    try{
      if(g.userData && g.userData.sacred) return true;
      var p = g.position;
      for(var i=0;i<SIG_POS.length;i++){
        if(Math.hypot(p.x - SIG_POS[i][0], p.z - SIG_POS[i][1]) < 4) return true;
      }
    }catch(e){}
    return false;
  }

  window.markSacred = function(){
    try{
      var pp = playerPos(), best = null, bd = 1e9;
      var ch = (typeof stones !== 'undefined' && stones.children) ? stones.children : [];
      for(var i=0;i<ch.length;i++){
        var g = ch[i];
        if(!g.userData || !g.userData.stone || g.userData.filler) continue;
        var d = Math.hypot(g.position.x - pp.x, g.position.z - pp.z);
        if(d < bd && d < 8){ bd = d; best = g; }
      }
      if(!best) return 'no stone near you';
      best.userData.sacred = !best.userData.sacred;
      var msg = best.userData.sacred ? 'stone marked SACRED — erase all will spare it'
                                    : 'sacred mark lifted';
      status(msg);
      return msg;
    }catch(e){ return 'sacred failed'; }
  };

  /* ---------- capture ---------- */
  function captureStones(cx, cz, radius){
    var out = [];
    try{
      var ch = (typeof stones !== 'undefined' && stones.children) ? stones.children : [];
      for(var i=0;i<ch.length;i++){
        var g = ch[i], u = g.userData;
        if(!u || !u.stone || u.filler) continue;
        if(Math.hypot(g.position.x - cx, g.position.z - cz) > radius) continue;
        out.push({
          x: Math.round(g.position.x*10)/10, z: Math.round(g.position.z*10)/10,
          r: u.rubies || 1,
          s: (u.shape || 'cube') + (u.ghost ? '~ghost' : ''),
          y: u.y || 0, land: u.land !== false ? 1 : 0,
          sacred: isSacred(g) ? 1 : 0
        });
      }
    }catch(e){}
    return out;
  }

  function areaNodeDump(cx, cz, radius){
    var empty = {v:1, t:Date.now(), nodes:[], portals:[], network:null, spans:[]};
    var full = null;
    try{ full = (typeof window.__dumpNodes === 'function') ? window.__dumpNodes() : null; }catch(e){}
    if(!full) return empty;
    function inR(x, z){ return Math.hypot(x - cx, z - cz) <= radius; }
    var nodes = ((full.nodes || []).filter(function(e){ return inR(e.x, e.z); }));
    var pos = {};
    nodes.forEach(function(e){ pos[Math.round(e.x*10)/10 + ',' + Math.round(e.z*10)/10] = true; });
    var portals = (full.portals || [