<!-- RUBY-BACKUP · part 466 -->
AND NEW PRODUCTS */
var GAME_VERSION = '1.0';
var PRODUCTS = [
  {name: 'Starship Skin: Gold', cost: 100, desc: 'Golden hull for your ship'},
  {name: 'Avatar Aura: Rainbow', cost: 150, desc: 'Rainbow glow'},
  {name: 'Pet: Phoenix', cost: 200, desc: 'A fiery companion'},
  {name: 'House: Sky Villa', cost: 300, desc: 'A home in the clouds'}
];
function checkUpdates(){
  codeStatus('game version '+GAME_VERSION+'. New products available — type "products"');
}
function showProducts(){
  loadCredits();
  var html = '<b style="color:#0ff">NEW PRODUCTS</b> (you have '+CREDITS+' credits)<br><br>';
  PRODUCTS.forEach(function(p, i){
    html += '<div style="margin:8px 0;padding:8px;border:1px solid #0ff;border-radius:5px;">'
      + '<b>'+p.name+'</b> — '+p.cost+' credits<br><small>'+p.desc+'</small><br>'
      + '<button onclick="buyProduct('+i+')" style="margin-top:5px;">BUY</button></div>';
  });
  var d = document.createElement('div');
  d.id = 'product-panel';
  d.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:10000;background:rgba(5,20,30,0.95);border:3px solid #0ff;border-radius:12px;padding:20px;color:#fff;max-width:400px;max-height:70%;overflow:auto;';
  d.innerHTML = html + '<br><button onclick="document.getElementById(\'product-panel\').remove()">CLOSE</button>';
  document.body.appendChild(d);
}
function buyProduct(idx){
  loadCredits();
  var p = PRODUCTS[idx];
  if(!p) return;
  if(CREDITS >= p.cost){
    CREDITS -= p.cost;
    try{ localStorage.setItem('ruby-credits', CREDITS.toString()); }catch(e){}
    try{
      var owned = JSON.parse(localStorage.getItem('ruby-products')||'[]');
      owned.push(p.name);
      localStorage.setItem('ruby-products', JSON.stringify(owned));
    }catch(e){}
    unlockAchievement('Bought '+p.name);
    codeStatus('purchased: '+p.name+'! Remaining: '+CREDITS+' credits');
    var d = document.getElementById('product-panel');
    if(d) d.remove();
  }else{
    codeStatus('not enough credits — need '+p.cost+', have '+CREDITS);
  }
}
/* init */
loadAchievements(); loadCredits(); loadInfluence();
function buildPurposefulCity(){
  if(typeof scene==='undefined' || typeof mePos==='undefined') return;
  /* start with the ancestral city */
  buildAncestralCity();
  /* assign purposes to downtown towers */
  var purposeIdx = 0;
  CITY_TOWERS.forEach(function(tower, i){
    /* every 4th tower gets a public purpose */
    if(i % 3 === 0){
      var purpose = BUILDING_PURPOSES[purposeIdx % BUILDING_PURPOSES.length];
      assignBuildingPurpose(tower, purpose, 0, 0);
      purposeIdx++;
    }
  });
  /* ensure fire and police are present */
  var hasFire = false, hasPolice = false;
  PURPOSE_BUILDINGS.forEach(function(b){
    if(b.userData.purpose==='fire') hasFire = true;
    if(b.userData.purpose==='police') hasPolice = true;
  });
  codeStatus('city with purpose — shops, businesses, fire, police. All visible. No cars.');
}
function listPurposes(){
  var counts = {};
  CITY_TOWERS.forEach(function(t){
    if(t.userData.purpose){
      counts[t.userData.purpose] = (counts[t.userData.purpose]||0)+1;
    }
  });
  var parts = [];
  for(var k in counts) parts.push(k+': '+counts[k]);
  codeStatus(parts.length ? 'purposes: '+parts.join(', ') : 'no purposes assigned yet');
}

/* WILD TERRAIN — islands, volcanoes, mountains, cliffs with trails */
var TERRAIN_GROUP = null;
function buildVolcano(x, z, size){
  var g = new THREE.Group();
  /* cone */
  var cone = new THREE.Mesh(
    new THREE.ConeGeometry(size, size*1.5, 16),
    new THREE.MeshStandardMaterial({color: 0x4a3a2a, roughness: 0.9})
  );
  cone.position.y = size*0.75;
  g.add(cone);
  /* crater — glowing */
  var crater = new THREE.Mesh(
    new THREE.CylinderGeometry(size*0.25, size*0.3, 0.3, 12),
    new THREE.MeshStandardMaterial({color: 0xff4400, emissive: 0xff4400, emissiveIntensity: 2})
  );
  crater.position.y = size*1.5;
  g.add(crater);
  /* lava glow */
  var lava = new THREE.Mesh(
    new THREE.SphereGeometry(size*0.2, 12, 12),
    new THREE.MeshBasicMaterial({color: 0xff6600})
  );
  lava.position.y = size*1.55;
  lava.scale.y = 0.3;
  g.add(lava);
  /* smoke */
  for(var i=0; i<5; i++){
    var puff = new THREE.Mesh(
      new THREE.SphereGeometry(0.5+i*0.2, 8, 8),
      new THREE.MeshStandardMaterial({color: 0x888888, transparent: true, opacity: 0.4})
    );
    puff.position.set((Math.random()-0.5)*2, size*1.8+i*0.8, (Math.random()-0.5)*2);
    g.add(puff);
  }
  g.position.set(x*0.13, 0, z*0.13);
  return g;
}
function buildMountainRange(x, z, length, height){
  var g = new THREE.Group();
  /* long ridge */
  for(var i=0; i<length; i++){
    var peak = new THREE.Mesh(
      new THREE.ConeGeometry(2+Math.random()*1.5, height*(0.7+Math.random()*0.6), 7),
      new THREE.MeshStandardMaterial({
        color: new THREE.Color().setHSL(0.35, 0.2, 0.25+Math.random()*0.15),
        roughness: 0.95, flatShading: true
      })
    );
    peak.position