<!-- SAGA-STAGING · part 66 -->
ction(dx, dy){
  /* GTA-STYLE INPUT PIPELINE — deadzone -> curve -> camera-relative -> desired velocity */
  _fdy = dy;
  /* 1. Radial deadzone */
  var mag = Math.sqrt(dx*dx + dy*dy);
  var DEAD = 0.18;
  if(mag < DEAD){ dx = 0; dy = 0; mag = 0; }
  else {
    /* 2. Response curve (gamma 1.5) — precision at center, full at edge */
    var norm = Math.min(1, (mag - DEAD) / (1 - DEAD));
    var curved = Math.pow(norm, 1.5);
    var s = curved / (mag || 1);
    dx *= s; dy *= s; mag = curved;
  }
    /* V202 SHADOW INTENT — processed human input, NO movement authority */
    if(window.ACTOR_RUNTIME){ if(!ActorRuntime.ai.active) ActorRuntime.submitIntent("human",{moveX:dx,moveZ:-dy}); ActorRuntime.locomotion.requestedMode=moveMode; }
  if(moveMode === "fly" && dy < -0.99){
    var upAmt = (-dy - 0.99) * 100.0;
    if(upAmt > 1) upAmt = 1;
    _ftvy = upAmt * 12.0;
    /* Fly FORWARD at run speed — keep horizontal, don't kill it */
    if(camYaw !== _fyaw){ _fyaw = camYaw; _fcos = Math.cos(camYaw); _fsin = Math.sin(camYaw); }
    var fsp = 12.0;
    _ftvx = (_fsin * dx - _fcos * 1.0) * fsp;
    _ftvz = (-_fcos * dx - _fsin * 1.0) * fsp;
  } else {
    if(camYaw !== _fyaw){ _fyaw = camYaw; _fcos = Math.cos(camYaw); _fsin = Math.sin(camYaw); }
    var sp = 12.0, ndy = -dy;
    /* 3. Camera-relative direction */
    _ftvx = (_fsin * dx - _fcos * ndy) * sp;
    _ftvz = (-_fcos * dx - _fsin * ndy) * sp;
    _ftvy = 0;
    if(moveMode === "fly" && kingPos.y > 0.6 && dy > 0.95) _ftvy = -8.0;
  }
});

/* RIGHT stick — aims the camera (cannon fires where it faces) */
/* ===== THE CAMERA HAND — v82, one lightning ===== */
function aimCamera(dx, dy){
  window._camActive = (Math.abs(dx) > 0.05 || Math.abs(dy) > 0.05);
  clearTimeout(window._camIdleT);
  window._camIdleT = setTimeout(function(){ window._camActive = false; }, 500);
  if(window._freecam){
    var _fwd = new THREE.Vector3();
    camera.getWorldDirection(_fwd);
    camera.position.addScaledVector(_fwd, -dy * 2);
    var _right = new THREE.Vector3().crossVectors(_fwd, camera.up).normalize();
    camera.position.addScaledVector(_right, dx * 2);
  } else {
    camYaw -= dx * 0.04;
    camPitch = Math.max(-1.5, Math.min(1.4, camPitch + dy * 0.025));
  }
}
makeStick('right:20px;bottom:190px;', aimCamera);


/* ===== BUTTON WALL — 56 tappable buttons in 3D ===== */
var buttonWall = null;
var wallButtons = [];
function buildButtonWall(){
  if(window.STAGING_CLEAN_MAP) return; // Clean map: old builder disabled
  if(buttonWall) scene.remove(buttonWall);
  wallButtons = [];
  buttonWall = new THREE.Group();
  /* Build 10 units ahead — you place it by where you stand */
  var wx = kingPos.x - Math.cos(camYaw) * 10;
  var wz = kingPos.z - Math.sin(camYaw) * 10;
  var wy = 25;
  say('wall placed where you stand');
  
  /* Wall: dark panel with gold trim, at Practice District */
  var wallGeo = new THREE.BoxGeometry(60, 40, 2);
  var wallMat = new THREE.MeshStandardMaterial({ color: 0x0b0906, roughness: 0.8 });
  var wall = new THREE.Mesh(wallGeo, wallMat);
  wall.position.set(wx, wy, wz);
  buttonWall.add(wall);
  
  /* Gold trim */
  var trimGeo = new THREE.BoxGeometry(62, 42, 1);
  var trimMat = new THREE.MeshStandardMaterial({ color: 0xd8a920, roughness: 0.3, metalness: 0.8 });
  var trim = new THREE.Mesh(trimGeo, trimMat);
  trim.position.set(wx, wy, wz + 0.5);
  buttonWall.add(trim);
  
  /* Title */
  var titleCanvas = document.createElement('canvas');
  titleCanvas.width = 1024; titleCanvas.height = 128;
  var tctx = titleCanvas.getContext('2d');
  tctx.fillStyle = '#0b0906'; tctx.fillRect(0,0,1024,128);
  tctx.fillStyle = '#ffd700'; tctx.font = '64px Georgia';
  tctx.textAlign = 'center'; tctx.fillText('C O D E R S 1 0 0', 512, 84);
  var titleTex = new THREE.CanvasTexture(titleCanvas);
  var titleMat = new THREE.MeshBasicMaterial({ map: titleTex, transparent: true });
  var titleMesh = new THREE.Mesh(new THREE.PlaneGeometry(40, 5), titleMat);
  titleMesh.position.set(wx, wy + 18, wz + 1.5);
  buttonWall.add(titleMesh);
  
  /* 56 buttons: 7 columns x 8 rows */
  var loader = new THREE.TextureLoader();
  var cols = 7, rows = 8;
  var btnSize = 6, gap = 1.5;
  var startX = wx - (cols * (btnSize + gap)) / 2 + btnSize/2;
  var startY = wy + 12;
  
  BTNS.forEach(function(b, i){
    var col = i % cols;
    var row = Math.floor(i / cols);
    var x = startX + col * (btnSize + gap);
    var y = startY - row * (btnSize + gap);
    
    var tex = loader.load('/coders100/buttons/' + b[0] + '.webp');
    tex.minFilter = THREE.LinearMipmapLinearFilter;
    tex.magFilter = THREE.LinearFilter;
    tex.anisotropy = renderer.capabilities.getMaxAnisotropy();
    var mat = new THREE.MeshBasicMaterial({ map: tex, transparent: true });
    var mesh = new THREE.Mesh(new THREE.PlaneGeometry(btnSize, btnSize), mat);
    mesh.position.set(x, y, wz + 1.5);
    mesh.userData.buttonIndex = i;
    mesh.userData.buttonFn = b[2];
    mesh.userData.buttonName 