<!-- SAGA2 · part 104 -->
 er++){
      var pts=[];
      for(var j=0;j<=16;j++){ var an=j/16*Math.PI*2;
        pts.push(new THREE.Vector3(ex+Math.cos(an)*(0.6+er*0.25), 20+Math.sin(an)*(0.5+er*0.2), 5.8));
      }
      hdraw(pts, er===0?hWarm:hDim);
    }
    var pp=[];
    for(var j=0;j<=10;j++){ var an=j/10*Math.PI*2;
      pp.push(new THREE.Vector3(ex+Math.cos(an)*0.2, 20+Math.sin(an)*0.2, 5.9));
    }
    hdraw(pp, hWarm);
  });
  for(var nn=-1; nn<=1; nn++){
    var pts=[];
    for(var i=0;i<=8;i++){ var t=i/8;
      pts.push(new THREE.Vector3(nn*0.3, 19-t*2.5, 6.0+Math.abs(nn)*0.15-t*0.2));
    }
    hdraw(pts, hDim);
  }
  var mp=[];
  for(var j=0;j<=16;j++){ var t=j/16;
    mp.push(new THREE.Vector3(-1.8+t*3.6, 15.5+Math.sin(t*Math.PI)*-0.3, 5.5+Math.sin(t*Math.PI)*0.3));
  }
  hdraw(mp, hWarm);
  [-1,1].forEach(function(sd){
    var pts=[];
    for(var j=0;j<=12;j++){ var an=j/12*Math.PI*2;
      pts.push(new THREE.Vector3(sd*4.6+Math.cos(an)*0.7, 19.5+Math.sin(an)*1.0, 1.5));
    }
    hdraw(pts, hDim);
  });
  for(var n2=0; n2<10; n2++){
    var pts=[], na=n2/10*Math.PI*2;
    for(var ny=0; ny<=5; ny++) pts.push(new THREE.Vector3(Math.cos(na)*2.5, 13-ny*1.0, Math.sin(na)*2.5));
    hdraw(pts, hDim);
  }
  for(var t2=0; t2<18; t2++){
    var pts=[], ty = 11-t2*0.85, tr = 5.5+Math.sin(t2/18*Math.PI)*1.2-t2*0.12;
    for(var j=0;j<=22;j++){ var an=j/22*Math.PI*2;
      pts.push(new THREE.Vector3(Math.cos(an)*tr, ty, Math.sin(an)*tr*0.72));
    }
    hdraw(pts, t2%6===0?hWarm:hSoft);
  }
  [-1,1].forEach(function(sd){
    var ax = sd*7;
    for(var a=0; a<16; a++){
      var pts=[], ay = 9-a*1.5, ar = 2.2-a*0.04;
      for(var j=0;j<=14;j++){ var an=j/14*Math.PI*2;
        pts.push(new THREE.Vector3(ax+Math.cos(an)*ar, ay, 4+Math.sin(an)*ar*0.85));
      }
      hdraw(pts, a%6===0?hWarm:hSoft);
    }
    for(var f=0; f<5; f++){
      var pts=[];
      for(var i=0;i<=4;i++) pts.push(new THREE.Vector3(ax-1+f*0.5, -15-i*1.2, 4.5));
      hdraw(pts, hDim);
    }
  });
  [-1,1].forEach(function(sd){
    var lx = sd*3;
    for(var l=0; l<18; l++){
      var pts=[], ly = -3-l*1.5, lr = 2.8-l*0.06;
      for(var j=0;j<=14;j++){ var an=j/14*Math.PI*2;
        pts.push(new THREE.Vector3(lx+Math.cos(an)*lr, ly, Math.sin(an)*lr*0.85));
      }
      hdraw(pts, l%6===0?hWarm:hSoft);
    }
  });
}

document.getElementById('cambtn').addEventListener('click', function(){
  camMode = (camMode+1)%camAngles.length;
  var a = camAngles[camMode];
  camera.position.set(a[0][0], a[0][1], a[0][2]);
  camera.lookAt(a[1][0], a[1][1], a[1][2]);
});
window.addEventListener('resize',function(){
  camera.aspect=window.innerWidth/window.innerHeight;
  camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth,window.innerHeight);
});
console.log("Solid's practice live");
// ===== WEAPONS: twin laser cannons, sticks become crosshairs =====
function buildCannons(){
  buildAvatar();
  cannonL = {virtual:true, side:-1};
  cannonR = {virtual:true, side:1};
  // tactical: brighten visor + sights in combat
  if(avatarVisor) avatarVisor.material.color.setHex(0x88eeff);
}
function setWeaponMode(on){
  var was = weaponMode;
  weaponMode = on;
  if(on && !cannonL) buildCannons();
  if(on && !was && navigator.vibrate){ try{ navigator.vibrate([40,30,40]); }catch(e){} }
  // diamonds are permanent — nothing to change
}
function fireLaser(){
  if(!weaponMode || !cannonL) return;
  // HAPTIC: feel the cannons in your hands
  if(navigator.vibrate){ try{ navigator.vibrate(25); }catch(e){} }
  [cannonL, cannonR].forEach(function(cn, i){
    setTimeout(function(){
      var dir = new THREE.Vector3(0,0,-1).applyQuaternion(camera.quaternion).normalize();
      // start slightly below camera, offset left/right
      var start = new THREE.Vector3();
  var ac = (i===0) ? avatarCannonL : avatarCannonR;
  var tipObj = ac ? (ac.userData.tipL || ac.userData.tip || ac.userData.tipPos) : null;
  if(tipObj){ if(tipObj.getWorldPosition) tipObj.getWorldPosition(start); else start.copy(tipObj).applyMatrix4(avatarGrp.matrixWorld); }
  else { start = camera.position.clone().add(new THREE.Vector3(cn.side*8, -6, 0).applyQuaternion(camera.quaternion)); }
  // muzzle flash on avatar cannon
  if(tipObj){
    tipObj.material.color.setHex(0xffffff);
    (function(tp){ setTimeout(function(){ tp.material.color.setHex(0x44ccff); }, 90); })(tipObj);
  }
      // raycast for impact
      var ray = new THREE.Raycaster(start, dir, 0, 1200);
      var hits = ray.intersectObjects(scene.children, true);
      var endDist = 900;
      var hitPoint = null, hitObj = null;
      for(var h=0; h<hits.length; h++){
        var o = hits[h].object;
        // skip lasers, sprites, glows
        if(o.isLine || o.isSprite || o.isPointLight) continue;
        if(lasers.some(function(l){ return l.mesh===o; })) continue;
        endDist = hits[h].distance;
        hitPoint = hits[h].point;
        hitObj = o;
        break;
      }
      var end = start.clone().add(dir.clone().multip