<!-- RUBY-BACKUP · part 562 -->
changed = true; }
    }
    st.order = st.order.filter(function(n){ return !!st.snaps[n]; });
    if(changed) saveStore(st);
    return st;
  }

  /* ---------- relative age, anchored to today ---------- */
  function ageStr(t){
    var now = Date.now(), d = 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'); }
    var td = new Date(t), nd = new Date(now);
    if(td.toDateString() === nd.toDateString()){
      var h = td.getHours(), ap = h >= 12 ? 'PM' : 'AM', h12 = h % 12 || 12;
      var mm = ('0' + td.getMinutes()).slice(-2);
      return 'today at ' + h12 + ':' + mm + ' ' + ap;
    }
    var hrs = Math.floor(d/3600000);
    return hrs + (hrs===1 ? ' hour ago' : ' hours ago');
  }

  function timeStr(t){
    var d = new Date(t);
    var h = d.getHours(), ap = h >= 12 ? 'PM' : 'AM', h12 = h % 12 || 12;
    var mm = ('0' + d.getMinutes()).slice(-2);
    return 'today at ' + h12 + ':' + mm + ' ' + ap;
  }

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

  /* ---------- capture: dynamic state ONLY. never the built world. ---------- */
  function captureState(){
    var st = {v:1};
    /* nodes: modes + settings, portals, network, spans — position-keyed, never stones */
    try{ st.nodes = (typeof window.__dumpNodes === 'function') ? window.__dumpNodes() : null; }
    catch(e){ st.nodes = null; }
    /* terrain lift overlays (the dynamic deformation, not the terrain itself) */
    try{ st.lifts = (typeof window.__getLifts === 'function') ? window.__getLifts() : []; }
    catch(e){ st.lifts = []; }
    /* grand machines: kind + position (session constructs, rebuilt on restore) */
    st.machines = [];
    try{
      var ms = (typeof window.__getMachines === 'function') ? window.__getMachines() : [];
      for(var i=0;i<ms.length;i++){
        try{
          var p = new THREE.Vector3();
          if(ms[i].group) ms[i].group.getWorldPosition(p);
          st.machines.push({kind: ms[i].kind, x: Math.round(p.x*10)/10, z: Math.round(p.z*10)/10});
        }catch(e){}
      }
    }catch(e){}
    /* pulleys: positions (session constructs) */
    st.pulleys = [];
    try{
      var ps = (typeof window.__getPulleys === 'function') ? window.__getPulleys() : [];
      for(var j=0;j<ps.length;j++){
        try{
          var pp = ps[j].pos || (ps[j].group && ps[j].group.position);
          if(pp) st.pulleys.push({x: Math.round(pp.x*10)/10, y: Math.round(pp.y*10)/10, z: Math.round(pp.z*10)/10});
        }catch(e){}
      }
    }catch(e){}
    return st;
  }

  /* ---------- clear all dynamic state. the world underneath is untouched. ---------- */
  function withQuiet(fn){
    var hadW = false, origW = null, hadB = false, origB = null;
    try{ if(typeof window.codeStatus === 'function'){ hadW = true; origW = window.codeStatus; window.codeStatus = function(){}; } }catch(e){}
    try{ if(typeof codeStatus === 'function' && !hadW){ hadB = true; origB = codeStatus; codeStatus = function(){}; } }catch(e){}
    try{ fn(); }
    finally{
      try{ if(hadW) window.codeStatus = origW; }catch(e){}
      try{ if(hadB) codeStatus = origB; }catch(e){}
    }
  }

  function clearDynamic(){
    withQuiet(function(){
      /* network first, so no sync fights the teardown */
      try{ if(typeof window.disconnectAll === 'function') window.disconnectAll(); }catch(e){}
      /* every node back to plain solid — modes stripped, platforms rest */
      try{
        var sns = window.__solidNodes || {};
        for(var nid in sns){
          try{ if(typeof window.clearMode === 'function') window.clearMode(nid); }catch(e){}
        }
      }catch(e){}
      /* spans cut */
      try{ if(typeof window.__unspanAll === 'function') window.__unspanAll(); }catch(e){}
      /* ropes + pulleys cleared */
      try{ if(typeof window.__clearRigs === 'function') window.__clearRigs(); }catch(e){}
      /* grand machines cleared */
      try{ if(typeof window.__clearMachines === 'function') window.__clearMachines(); }catch(e){}
      /* lift overlays cleared (the terrain itself is never touched) */
      try{ if(typeof window.__setLifts === 'function') window.__setLifts([]); }catch(e){}
    });
  }

  /* ---------- restore: systems return to the moment. stones are never
     created, moved, or altered. missing stones are skipped silently. ---------- */
  function restoreState(state){
    if(!state) return 0;
    var n = 0;
    withQuiet(function(){
      /* nodes + modes + portals + network — resolves by position, skips the gone */
      try{
        if(state.nodes && typeof window.__restoreNodes === 'function')
          n = window.__restoreNodes(state.nodes) || 0;
      }catch(e){}
      /* spans */
      try{
        if(state.nodes && typeof window.__restoreSpans === 'function')
          window._