<!-- SAGA-STAGING · part 85 -->
  // 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 * 0.05;
          ball.position.z += bvz * 0.05;
          // Hit check
          var d = ball.position.distanceTo(end);
          if(d < 2 || ball.position.y < 0.3){
            clearInterval(bint);
            scene.remove(ball);
            // Damage
            if(window._lockedTarget.userData){
              window._lockedTarget.userData.hp -= 34;
              if(window._lockedTarget.userData.hp <= 0){
                scene.remove(window._lockedTarget);
                var idx = window._targets.indexOf(window._lockedTarget);
                if(idx >= 0) window._targets.splice(idx, 1);
                window._lockedTarget = null;
                window._inBattle = false;
                hideLock();
                if(window.say) window.say('Target destroyed.');
              } else {
                if(window.say) window.say('Hit!');
              }
            }
            // Explosion flash
            var exp = new THREE.Mesh(
              new THREE.SphereGeometry(2, 10, 10),
              new THREE.MeshBasicMaterial({color:0xffcc00, transparent:true, opacity:0.8})
            );
            exp.position.copy(ball.position);
            scene.add(exp);
            setTimeout(function(){ scene.remove(exp); }, 300);
          }
        }, 50);
        return;
      }catch(e){}
    }
    // No lock: use original forward fire
    if(origFire) origFire();
  };

  // Register 'target' code
  if(window.registerCode){
    window.registerCode('target', fu