<!-- RUBY-BACKUP · part 309 -->

    output += '        Count: 1\n';
    output += '    }\n';
    output += '}\n';
    output += 'Objects:  {\n';
    var vOffset = 0;
    var meshIdx = 0;
    target.updateMatrixWorld(true);
    target.traverse(function(m){
      if(m.isMesh && m.geometry){
        var geo = m.geometry;
        var pos = geo.attributes.position;
        var idx = geo.index;
        output += '    Model: "Model::mesh' + meshIdx + '", "Mesh" {\n';
        output += '        Version: 232\n';
        /* vertices */
        output += '        Vertices: ';
        var v = new THREE.Vector3();
        var verts = [];
        for(var i=0; i<pos.count; i++){
          v.set(pos.getX(i), pos.getY(i), pos.getZ(i));
          m.localToWorld(v);
          verts.push(v.x.toFixed(4) + ',' + v.y.toFixed(4) + ',' + v.z.toFixed(4));
        }
        output += verts.join(',') + '\n';
        /* polygon indices */
        output += '        PolygonVertexIndex: ';
        var polys = [];
        if(idx){
          for(var f=0; f<idx.count; f+=3){
            polys.push(idx.getX(f), idx.getX(f+1), -idx.getX(f+2)-1);
          }
        }else{
          for(var f2=0; f2<pos.count; f2+=3){
            polys.push(f2, f2+1, -f2-2-1);
          }
        }
        output += polys.join(',') + '\n';
        output += '        GeometryVersion: 124\n';
        output += '    }\n';
        meshIdx++;
      }
    });
    output += '}\n';
    var blob = new Blob([output], {type:'text/plain'});
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url;
    a.download = 'creator-model.fbx';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    codeStatus('exported FBX — share it');
  }catch(e){ codeStatus('FBX export failed'); }
}
function exportOBJ(){
  try{
    var target = window._highlighted || window._selectedBlock;
    if(!target){ codeStatus('tap a block first, then export'); return; }
    var output = '# Exported from the Creator\n';
    output += '# For the ancestors\n';
    var vOffset = 0;
    target.updateMatrixWorld(true);
    target.traverse(function(m){
      if(m.isMesh && m.geometry){
        var geo = m.geometry;
        var pos = geo.attributes.position;
        var norm = geo.attributes.normal;
        var idx = geo.index;
        // vertices (world space)
        var v = new THREE.Vector3();
        for(var i=0; i<pos.count; i++){
          v.set(pos.getX(i), pos.getY(i), pos.getZ(i));
          m.localToWorld(v);
          output += 'v ' + v.x.toFixed(4) + ' ' + v.y.toFixed(4) + ' ' + v.z.toFixed(4) + '\n';
        }
        // normals
        if(norm){
          for(var n=0; n<norm.count; n++){
            output += 'vn ' + norm.getX(n).toFixed(4) + ' ' + norm.getY(n).toFixed(4) + ' ' + norm.getZ(n).toFixed(4) + '\n';
          }
        }
        // faces
        if(idx){
          for(var f=0; f<idx.count; f+=3){
            var a = idx.getX(f)+1+vOffset, b = idx.getX(f+1)+1+vOffset, d = idx.getX(f+2)+1+vOffset;
            output += 'f ' + a + '//' + a + ' ' + b + '//' + b + ' ' + d + '//' + d + '\n';
          }
        }else{
          for(var f2=0; f2<pos.count; f2+=3){
            var a2 = f2+1+vOffset, b2 = f2+1+1+vOffset, d2 = f2+1+2+vOffset;
            output += 'f ' + a2 + '//' + a2 + ' ' + b2 + '//' + b2 + ' ' + d2 + '//' + d2 + '\n';
          }
        }
        vOffset += pos.count;
      }
    });
    // download
    var blob = new Blob([output], {type:'text/plain'});
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url;
    a.download = 'creator-model.obj';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    codeStatus('exported OBJ — share it');
  }catch(e){ codeStatus('export failed'); }
}
/* ORGANIC SCULPT MODE — Playa 2.0 spec, blessed */
/* Smooth / Sharpen / Bend brushes, vertex displacement along normals */
var _sculptOpen = false;
var _sculptBrush = 'smooth';
var _sculptIntensity = 0.3;
function toggleSculptMode(){
  _sculptOpen = !_sculptOpen;
  var sm = document.getElementById('sculptPanel');
  if(_sculptOpen){
    var target = window._highlighted || window._selectedBlock;
    if(!target){ codeStatus('tap a block first, then sculpt'); _sculptOpen = false; return; }
    if(!sm){
      sm = document.createElement('div');
      sm.id = 'sculptPanel';
      sm.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:15000;background:rgba(10,8,4,0.92);border:2px solid #ffd700;border-radius:16px;padding:12px;display:flex;flex-direction:column;gap:10px;max-width:94vw;';
      sm.innerHTML = '<div style="color:#ffd700;font-family:monospace;font-size:13px;text-align:center;">SCULPT — digital clay</div>'
        + '<div style="display:flex;gap:8px;justify-content:center;">'
        + '<button data-brush="smooth" style="sculptbtn">🌊 Smooth</b