<!-- SAGA3 · part 49 -->
dd(ground);

var grid = new THREE.GridHelper(20000, 100, 0xff0000, 0xff0000);
grid.position.y = 0.14;
grid.material.opacity = 0.3;
grid.material.transparent = true;
scene.add(grid);

/* ===== MATERIALS ===== */
var goldMat = new THREE.MeshStandardMaterial({color: 0xffd700, emissive: 0xffd700, emissiveIntensity: 0.5, roughness: 0.3, metalness: 0.7});
var roadMat = new THREE.MeshStandardMaterial({color: 0x2a2a2a, roughness: 0.8});

/* ===== CAMERA ===== */
var camYaw = 0, camPitch = 0.5, camDist = 20;
var kingPos = new THREE.Vector3(0, 0, 0);
/* Load King's saved spawn — from our story */
try{
  var _ss = JSON.parse(localStorage.getItem('saga-spawn'));
  if(_ss && isFinite(_ss.x) && isFinite(_ss.z)){
    kingPos.set(_ss.x, 0, _ss.z);
  }
}catch(e){}
var playerBody = null;
function updateCamera(){
  if(window._freecam) return; /* Free camera — don't follow */
  camera.position.set(
    kingPos.x + Math.cos(camYaw) * Math.cos(camPitch) * camDist,
    kingPos.y + Math.sin(camPitch) * camDist + 2,
    kingPos.z + Math.sin(camYaw) * Math.cos(camPitch) * camDist
  );
  camera.lookAt(kingPos.x, kingPos.y + 2, kingPos.z);
}
var lTX=0, lTY=0, touching=false;
var cameraMode = false; /* Camera toggle controls this */
canvas.addEventListener('touchstart', function(e){ touching=true; lTX=e.touches[0].clientX; lTY=e.touches[0].clientY; }, {passive:true});
canvas.addEventListener('touchmove', function(e){
  if(!touching) return;
  /* Camera drag always live (right joystick also works) */
  camYaw -= (e.touches[0].clientX - lTX) * 0.005;
  camPitch = Math.max(0.1, Math.min(1.4, camPitch + (e.touches[0].clientY - lTY) * 0.005));
  lTX=e.touches[0].clientX; lTY=e.touches[0].clientY;
}, {passive:true});
canvas.addEventListener('touchend', function(){ touching=false; }, {passive:true});

/* ===== STATUS ===== */
var statusEl = document.getElementById('status'), sT=null;
function say(m){
  statusEl.textContent = m; statusEl.classList.add('show');
  if(sT) clearTimeout(sT);
  sT = setTimeout(function(){ statusEl.classList.remove('show'); }, 3000);
}

/* ===== THE OS ===== */
var osEl = document.getElementById('os');
var osToggle = document.getElementById('os-toggle');
var osOpen = false;

osToggle.onclick = function(){ osOpen=!osOpen; osEl.classList.toggle('open', osOpen);; };
function osButton(emoji, title, fn){
  var b = document.createElement('button');
  b.innerHTML = emoji; b.title = title;
  b.onclick = function(){ fn(); osOpen=false; osEl.classList.remove('open');; };
  osEl.appendChild(b);
  return b;
}

/* ===== DISTRICTS ===== */
var districts = {};
function registerDistrict(n, l, fn){ districts[n] = {label:l, build:fn, loaded:false}; }
function activateDistrict(n){
  var d = districts[n];
  if(!d) return say('unknown');
  if(d.loaded) return say(d.label + ' already stands');
  try { d.build(); d.loaded=true; say(d.label + ' rises'); }
  catch(e){ say('stumbled'); }
}
var dOrder=[], dIdx=0;
function nextDistrict(){
  for(var i=0;i<dOrder.length;i++){
    var n=dOrder[(dIdx+i)%dOrder.length];
    if(districts[n] && !districts[n].loaded){
      activateDistrict(n); dIdx=(dIdx+i+1)%dOrder.length; return;
    }
  }
  say('all districts stand');
}

/* ================================================================
   THE CODES — organized by category, proper
   ================================================================ */

/* ---- SPECIAL: the 12 ---- */
var lastBam = null;

function doBam(args){
  var shape = args[0] || 'box';
  var size = parseFloat(args[1]) || 10;
  var count = 1, row = 0;

  /* bam box 10 x5 — batch */
  for(var i=0;i<args.length;i++){
    if(args[i][0]==='x') count = parseInt(args[i].slice(1)) || 1;
    if(args[i]==='line') row = parseInt(args[i+1]) || 0;
  }

  lastBam = {shape:shape, size:size, count:count, row:row};

  for(var c=0;c<count;c++){
    var mesh;
    if(shape==='box') mesh = new THREE.Mesh(new THREE.BoxGeometry(size,size,size), goldMat);
    else if(shape==='wall'){
      var len = row || 5;
      mesh = new THREE.Mesh(new THREE.BoxGeometry(size*len, size, 1), goldMat);
    }
    else mesh = new THREE.Mesh(new THREE.BoxGeometry(size,size,size), goldMat);

    var ox = row ? c * size * 1.2 : c * size * 1.5;
    mesh.position.set(kingPos.x + ox, groundY + size/2, kingPos.z);
    window.applyMinimum(mesh);
    scene.add(mesh);
  }
  say(shape + ' stands' + (count>1 ? ' x'+count : ''));
}


/* New body — spawns a fresh body the king can wear */
function spawnNewBody(){
  var g = new THREE.Group();
  var gold = new THREE.MeshStandardMaterial({color: 0xd8a920, roughness: 0.4, metalness: 0.7});
  var dark = new THREE.MeshStandardMaterial({color: 0x1a1a1a, roughness: 0.6});
  /* Head */
  var head = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), gold);
  head.position.y = 2.2;
  g.add(head);
  /* Torso */
  var torso = new THREE.Mesh(new THREE.BoxGeometry(0.8, 1.0, 0.4), dark);
  torso.position.y = 1.4;
  g.add(torso);
  /* Arms */
  var armL = new THREE.M