<!-- LIVE · part 84 -->
indow._inBattle = false;
  window._targets = [];

  // Lock visual: red diamond
  var lockMesh = null;
  function showLock(target){
    hideLock();
    if(!target || !target.position) return;
    var geo = new THREE.OctahedronGeometry(1.2);
    var mat = new THREE.MeshBasicMaterial({color:0xff0033, wireframe:true});
    lockMesh = new THREE.Mesh(geo, mat);
    lockMesh.position.copy(target.position);
    lockMesh.position.y += 3;
    scene.add(lockMesh);
  }
  function hideLock(){
    if(lockMesh && lockMesh.parent) lockMesh.parent.remove(lockMesh);
    lockMesh = null;
  }
  function updateLock(){
    if(lockMesh && window._lockedTarget && window._lockedTarget.position){
      lockMesh.position.copy(window._lockedTarget.position);
      lockMesh.position.y += 3;
      lockMesh.rotation.y += 0.05;
    }
  }
  setInterval(updateLock, 50);

  // Tap to lock: raycast on tap (not on joystick)
  var raycaster = new THREE.Raycaster();
  var tapPos = new THREE.Vector2();
  document.addEventListener('touchstart', function(e){
    // Only if not on joystick or buttons (left 40% bottom 40% is joystick zone)
    var t = e.touches[0];
    if(t.clientX < window.innerWidth * 0.45 && t.clientY > window.innerHeight * 0.55) return;
    // Don't interfere with cannon button or UI
    if(e.target !== document.body && e.target !== document.querySelector('canvas')) return;
  }, {passive:true});

  document.addEventListener('touchend', function(e){
    var t = e.changedTouches[0];
    // Skip joystick zone
    if(t.clientX < window.innerWidth * 0.45 &&
       t.clientY > window.innerHeight * 0.55) return;

    tapPos.x = (t.clientX / window.innerWidth) * 2 - 1;
    tapPos.y = -(t.clientY / window.innerHeight) * 2 + 1;
    raycaster.setFromCamera(tapPos, camera);

    // Cast against targets, but sort by distance to find the most immediate threat
    var hits = raycaster.intersectObjects(window._targets, true);

    if(hits.length > 0){
      // Sort hits by closest distance to camera for priority targeting
      hits.sort(function(a, b){ return a.distance - b.distance; });

      var chosenTarget = hits[0].object;
      while(chosenTarget.parent && !chosenTarget.userData.isTarget) chosenTarget = chosenTarget.parent;

      // Tactical check: if we tap the *already* locked target, cycle to the next one
      if(window._lockedTarget === chosenTarget && hits.length > 1) {
        chosenTarget = hits[1].object;
        while(chosenTarget.parent && !chosenTarget.userData.isTarget) chosenTarget = chosenTarget.parent;
      }

      window._lockedTarget = chosenTarget;
      window._inBattle = true;

      // Pass the actual object to showLock so the red diamond anchors correctly
      if(typeof showLock === 'function') {
        showLock(window._lockedTarget);
      }
      if(window.say) window.say('Target locked.');
    } else {
      // Tap empty space / sky unlocks
      window._lockedTarget = null;
      window._inBattle = false;
      if(typeof hideLock === 'function') {
        hideLock();
      }
    }
  }, {passive:true});

  // Spawn a target dummy: code 'target'
  window.spawnTarget = function(){
    var g = new THREE.Group();
    var mat = new THREE.MeshStandardMaterial({color:0xcc2222, roughness:0.5});
    var body = new THREE.Mesh(new THREE.BoxGeometry(2, 3, 2), mat);
    body.position.y = 1.5;
    g.add(body);
    var head = new THREE.Mesh(new THREE.SphereGeometry(0.8, 12, 12), mat);
    head.position.y = 3.8;
    g.add(head);
    g.position.set(kingPos.x + 20, 0.5, kingPos.z);
    g.userData.isTarget = true;
    g.userData.hp = 100;
    scene.add(g);
    window._targets.push(g);
    if(window.say) window.say('Target ready.');
    return g;
  };

  // Override fireCannon to aim at locked target
  var origFire = window.fireCannon;
  window.fireCannon = function(){
    if(window._lockedTarget && window._lockedTarget.position){
      // Aim at locked target with lead
      var tp = window._lockedTarget.position;
      // Use original fire but redirect: temporarily move a cannon to aim
      // For now, call original and let it fire forward, then adjust projectile
      // Simpler: fire directly at target with ballistic arc
      try{
        var start = new THREE.Vector3(kingPos.x, kingPos.y + 2, kingPos.z);
        var end = new THREE.Vector3(tp.x, tp.y + 1, tp.z);
        var dist = start.distanceTo(end);
        var flightTime = dist / 30;
        var vel = new THREE.Vector3().subVectors(end, start).divideScalar(flightTime);
        vel.y += 0.5 * 9.8 * flightTime; // gravity compensation

        var ball = new THREE.Mesh(
          new THREE.SphereGeometry(0.5, 10, 10),
          new THREE.MeshBasicMaterial({color:0xff6600})
        );
        ball.position.copy(start);
        scene.add(ball);

        var bvx = vel.x, bvy = vel.y, bvz = vel.z;
        var bint = setInterval(function(){
          bvy -= 9.8 * 0.05;
          ball.position.x += bvx * 0.05;
          ball.position.y += bvy * 