<!-- SAGA3 · part 55 -->
);
    var mx = rightX * dx * sp;
    var mz = rightZ * dx * sp;
    var my = -dy * sp; /* Push up = fly up, push down = descend */
    lastDX = mx;
    lastDZ = mz;
    kingPos.x += mx;
    kingPos.z += mz;
    kingPos.y = Math.max(0.5, kingPos.y + my);
    /* Tilt body when flying */
    if(typeof playerBody !== 'undefined' && playerBody){
      playerBody.rotation.x = -dy * 0.3;
    }
  } else if(moveMode === 'swim'){
    /* SWIM: water movement — fluid, buoyant, dive/surface */
    var spSwim = 0.3; /* Slower in water */
    var fwdX = -Math.cos(camYaw), fwdZ = -Math.sin(camYaw);
    var rightX = Math.sin(camYaw), rightZ = -Math.cos(camYaw);
    var mx = rightX * dx * spSwim + fwdX * (-dy) * spSwim;
    var mz = rightZ * dx * spSwim + fwdZ * (-dy) * spSwim;
    lastDX = mx;
    lastDZ = mz;
    kingPos.x += mx;
    kingPos.z += mz;
    /* Buoyancy: float at water level, push up/down to dive/surface */
    /* Water level assumed at y=0.5 */
    var targetY = 0.5 + (-dy * 0.3);
    kingPos.y += (targetY - kingPos.y) * 0.1;
    /* Swim animation: arms stroke */
    if(typeof playerBody !== 'undefined' && playerBody){
      walkPhase += 0.1;
      if(playerBody.userData.armL){
        playerBody.userData.armL.rotation.x = Math.sin(walkPhase) * 0.8;
        playerBody.userData.armR.rotation.x = -Math.sin(walkPhase) * 0.8;
      }
      /* Body horizontal in water */
      playerBody.rotation.x = -Math.PI/2 + 0.3;
    }
  } else {
    /* WALK: ground movement — dx left/right, dy forward/back — 5x RUN speed */
    var runSp = 2.5;
    var fwdX = -Math.cos(camYaw), fwdZ = -Math.sin(camYaw);
    var rightX = Math.sin(camYaw), rightZ = -Math.cos(camYaw);
    var mx = rightX * dx * runSp + fwdX * (-dy) * runSp;
    var mz = rightZ * dx * runSp + fwdZ * (-dy) * runSp;
    lastDX = mx;
    lastDZ = mz;
    kingPos.x += mx;
    kingPos.z += mz;
    kingPos.y = groundY; /* Stay on ground when walking */
    if(typeof playerBody !== 'undefined' && playerBody){
      playerBody.rotation.x = 0;
    }
  }
});

/* RIGHT joystick — orbits the camera around the body */
makeJoystick('right:8%;bottom:100px;', function(dx, dy){
  if(window._freecam){
    /* Free camera — move position */
    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.08;
    camPitch = Math.max(0.1, Math.min(1.4, camPitch + dy * 0.05));
  }
});

/* ===== BUTTON WALL — 56 tappable buttons in 3D ===== */
var buttonWall = null;
var wallButtons = [];
function buildButtonWall(){
  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