Hey GPT, I need help optimizing my real-time threads, including aim assist and detection, which currently run using PyQt QThreads. They work fine but I want to adapt them to run efficiently on GPU using DirectML, without changing the core logic too much. Here’s a quick summary of the current performance bottlenecks and what I want fixed: 🔍 Current Issues: 1. Ultralytics .predict() is a bottleneck * It does heavy preprocessing, inference, and postprocessing internally. * This slows down frame rate due to serial processing and overhead. ✅ Fix: Use the underlying model directly (e.g., model.model() with manual preprocessing and NMS), or better yet, switch to ONNX + onnxruntime with DirectML provider for true GPU inference. 2. YOLOv11n PyTorch model is not fully optimized for DirectML * Some ops fall back to CPU, limiting performance. ✅ Fix: Export the model to ONNX format and use onnxruntime.InferenceSession with the DirectMLExecutionProvider for efficient AMD GPU usage. 3. BitBlt capture and frame conversion are expensive * Capturing, converting BGRA→RGB, copying to NumPy each frame causes CPU overhead. ✅ Fix: Use np.ascontiguousarray to keep frames in continuous memory and minimize unnecessary conversions. 4. Inference and processing run mostly single-threaded * Even with multiple threads, predict calls and postprocessing run serially, limiting FPS. ✅ Fix: Run each inference session in its own thread or use async calls to maximize parallel GPU utilization. 🚀 What I Want: * Convert my YOLOv11n .pt model to ONNX and run it using onnxruntime with DmlExecutionProvider. * Modify my detection thread to: * Replace .predict() calls with ONNX runtime calls. * Perform preprocessing and NMS manually or export model with NMS if possible. * Run fully on AMD GPU with DirectML for best speed. * Update the real-time aim assist thread so it: * Keeps the exact same input/output and smoothing logic. * Runs GPU-accelerated inference using ONNX + DirectML. * Maintains ultra-low latency and sync with input simulation (using SendInput or equivalent). * Optimize memory usage by reusing contiguous buffers from screen capture and minimizing frame copying. Optional improvements: * Use float16 precision if supported by DirectML for faster inference. * Offload NMS or tracking steps to GPU if feasible. 🧠 Background info: * Using YOLOv11n and YOLOEnsemble models. * High-FPS screen capture via BitBlt. * Custom SORT tracker and fast NMS. * Everything currently in PyQt threads, aiming for sub-10ms latency per frame. Please keep all logic intact, just improve GPU usage and inference speed with ONNX and DirectML while preserving the current real-time responsiveness and pleas dont change names i repeat dont change name as other clase and threads rely on these clases and threads. Thanks!

class YOLOEnsemble:
    _instance = None
    _lock = threading.Lock()

    def __init__(self, models_config):
        self.models = []
        self.names = {}
        self.prediction_cache = {}
        self.cache_lock = threading.Lock()
        self.device = self._setup_directml_device()
        self._load_models(models_config)

    def _setup_directml_device(self):
        """Setup DirectML for maximum AMD GPU performance with fallbacks"""
        try:
            # Try DirectML first
            if torch_directml.is_available():
                device_count = torch_directml.device_count()
                logging.info(f"DirectML available with {device_count} device(s)")
                
                if device_count > 0:
                    # Get the best DirectML device (usually 0)
                    dml_device = torch_directml.device(0)
                    
                    # Maximum DirectML optimizations
                    torch.backends.directml.enabled = True
                    torch.backends.directml.allow_reduced_precision = True
                    
                    logging.info(f"DirectML device initialized: {dml_device}")
                    return dml_device
            
            # Fallback to CUDA if available
            if torch.cuda.is_available():
                cuda_device = torch.device('cuda:0')
                logging.info(f"CUDA device initialized: {cuda_device}")
                return cuda_device
            
            # Final fallback to CPU with warning
            logging.warning("No GPU available, falling back to CPU (performance will be reduced)")
            return torch.device('cpu')
                
        except Exception as e:
            logging.warning(f"GPU setup failed ({e}), falling back to CPU")
            return torch.device('cpu')

    def _load_models(self, models_config):
        """Load models with DirectML optimization for maximum performance"""
        for model_path, imgsz in models_config:
            try:
                # Load YOLO model
                model = YOLO(model_path)
                
                # Force model to DirectML device for 100% GPU utilization
                model.model = model.model.to(self.device)
                
                # Set model to evaluation mode and optimize
                model.model.eval()
                
                # Enable DirectML optimizations
                if hasattr(torch.backends, 'directml') and torch.backends.directml.enabled:
                    # Compile model for maximum DirectML performance
                    try:
                        model.model = torch.jit.optimize_for_inference(model.model)
                        logging.info(f"Model JIT optimized for DirectML: {model_path}")
                    except:
                        logging.info(f"JIT optimization skipped for: {model_path}")
                
                # Zero-latency warmup with DirectML - more aggressive
                dummy = torch.zeros((1, 3, imgsz, imgsz), dtype=torch.float32, device=self.device)
                
                with torch.no_grad():
                    # More aggressive warmup - 15 iterations for maximum DirectML optimization
                    for _ in range(15):
                        try:
                            _ = model.model(dummy)
                        except:
                            # Fallback warmup using predict
                            dummy_np = np.zeros((imgsz, imgsz, 3), dtype=np.uint8)
                            model.predict(
                                source=dummy_np,
                                imgsz=imgsz,
                                device=self.device,
                                verbose=False,
                                stream=False
                            )
                            break
                
                # Clear cache for maximum performance (if DirectML available)
                if hasattr(torch, 'directml') and str(self.device).startswith('privateuseone'):
                    torch.directml.empty_cache()
                elif torch.cuda.is_available() and self.device.type == 'cuda':
                    torch.cuda.empty_cache()
                
                self.models.append((model, imgsz))
                if hasattr(model, "names"):
                    self.names.update(model.names)
                    
                logging.info(f"DirectML model loaded: {model_path} -> {imgsz}px on {self.device}")
                
            except Exception as e:
                logging.error(f"Failed to load model {model_path}: {e}")

    def predict(self, frame, conf_thres=0.3):
        """Ultra-high performance DirectML prediction with zero latency"""
        all_results = []
        original_height, original_width = frame.shape[:2]
        
        # Process all models with DirectML for maximum GPU utilization
        with torch.no_grad():  # Disable gradients for inference speed
            for model, imgsz in self.models:
                try:
                    # Calculate scale factors for coordinate transformation
                    scale_x = original_width / imgsz
                    scale_y = original_height / imgsz
                    
                    # Ultra-fast resize with optimized interpolation
                    resized_frame = cv2.resize(frame, (imgsz, imgsz), interpolation=cv2.INTER_LINEAR)
                    
                    # Maximum performance prediction with DirectML
                    results = model.predict(
                        source=resized_frame,
                        save=False,
                        imgsz=imgsz,
                        conf=conf_thres,
                        iou=0.15,  # More aggressive NMS for speed
                        device=self.device,
                        half=False,  # DirectML works better with float32
                        verbose=False,
                        stream=False,
                        agnostic_nms=True,
                        max_det=40,  # Slightly higher detection count
                        classes=[0],  # Person class only
                        retina_masks=False
                    )[0]
                    
                    # Store transformation data
                    results.scale_x = scale_x
                    results.scale_y = scale_y
                    results.original_width = original_width
                    results.original_height = original_height
                    
                    all_results.append(results)
                    
                except Exception as e:
                    logging.warning(f"DirectML prediction failed: {e}")
                    continue
        
        # No cache clearing to maintain maximum GPU utilization
        # Only clear if memory is getting full
        if hasattr(torch, 'directml') and str(self.device).startswith('privateuseone'):
            pass  # Keep DirectML memory allocated
        elif torch.cuda.is_available() and self.device.type == 'cuda':
            pass  # Keep CUDA memory allocated
        return all_results

    @classmethod
    def get_instance(cls, models_config):
        with cls._lock:
            if cls._instance is None:
                cls._instance = YOLOEnsemble(models_config)
            return cls._instance

def clamp(value, min_val, max_val):
    """Zero-overhead clamp function"""
    return min_val if value < min_val else max_val if value > max_val else value

class DetectionThread(QtCore.QThread):
    detection_signal = QtCore.pyqtSignal(list, list, list, list, list)

    def __init__(self, settings):
        super().__init__()
        self.settings = settings
        self.running = False
        self.frame_lock = threading.Lock()
        
        # BitBlt setup
        self.user32 = ctypes.windll.user32
        self.gdi32 = ctypes.windll.gdi32
        self.dc = None
        self.memdc = None
        self.bitmap = None
        self.bitmapinfo = None
        
        # Ultra-fast tracker with minimal overhead
        self.tracker = Sort(max_age=10, min_hits=1, iou_threshold=0.2)
        self.frame_skip = 0
        self.last_detection_time = 0
        
        # Screen cache
        self.screen_width = 0
        self.screen_height = 0
        self.center_x = 0
        self.center_y = 0        # Load DirectML optimized models
        model_paths = [
            ("models/BestMain.pt", 256),
        ]
        

        self.yolo_ensemble = YOLOEnsemble.get_instance(model_paths)

    def setup_bitblt(self):
        """Initialize BitBlt capture resources"""
        try:
            # Get desktop window handle
            hwnd = self.user32.GetDesktopWindow()
            
            # Get DC and create compatible DC
            self.dc = self.user32.GetDC(hwnd)
            self.memdc = self.gdi32.CreateCompatibleDC(self.dc)
            
            # Create bitmap info structure
            class BITMAPINFOHEADER(ctypes.Structure):
                _fields_ = [
                    ("biSize", ctypes.c_uint32),
                    ("biWidth", ctypes.c_int),
                    ("biHeight", ctypes.c_int),
                    ("biPlanes", ctypes.c_short),
                    ("biBitCount", ctypes.c_short),
                    ("biCompression", ctypes.c_uint32),
                    ("biSizeImage", ctypes.c_uint32),
                    ("biXPelsPerMeter", ctypes.c_int),
                    ("biYPelsPerMeter", ctypes.c_int),
                    ("biClrUsed", ctypes.c_uint32),
                    ("biClrImportant", ctypes.c_uint32)
                ]

            self.bitmapinfo = BITMAPINFOHEADER()
            self.bitmapinfo.biSize = ctypes.sizeof(BITMAPINFOHEADER)
            self.bitmapinfo.biPlanes = 1
            self.bitmapinfo.biBitCount = 32
            self.bitmapinfo.biCompression = 0
            
            return True
            
        except Exception as e:
            logging.error(f"BitBlt setup failed: {e}")
            return False

    def capture_screen_region(self, x, y, width, height):
        """Capture screen region using BitBlt"""
        try:
            # Update bitmap info
            self.bitmapinfo.biWidth = width
            self.bitmapinfo.biHeight = -height  # Negative for top-down
            self.bitmapinfo.biSizeImage = width * height * 4
            
            # Create compatible bitmap
            self.bitmap = self.gdi32.CreateCompatibleBitmap(self.dc, width, height)
            self.gdi32.SelectObject(self.memdc, self.bitmap)
            
            # Perform BitBlt operation
            result = self.gdi32.BitBlt(
                self.memdc, 0, 0, width, height,
                self.dc, x, y,
                0x00CC0020  # SRCCOPY
            )
            
            if not result:
                raise Exception("BitBlt failed")
            
            # Get bitmap bits
            buffer_size = width * height * 4
            buffer = (ctypes.c_byte * buffer_size)()
            
            self.gdi32.GetDIBits(
                self.memdc, self.bitmap, 0, height,
                buffer, ctypes.byref(self.bitmapinfo), 0
            )
            
            # Convert to numpy array
            array = np.frombuffer(buffer, dtype=np.uint8)
            array = array.reshape(height, width, 4)
            
            # Cleanup current bitmap
            self.gdi32.DeleteObject(self.bitmap)
            
            return array
            
        except Exception as e:
            logging.error(f"BitBlt capture failed: {e}")
            return None

    def cleanup_bitblt(self):
        """Clean up BitBlt resources"""
        try:
            if self.bitmap:
                self.gdi32.DeleteObject(self.bitmap)
            if self.memdc:
                self.gdi32.DeleteDC(self.memdc)
            if self.dc:
                self.user32.ReleaseDC(None, self.dc)
        except Exception as e:
            logging.error(f"BitBlt cleanup failed: {e}")

    def fast_nms(self, boxes, scores, iou_threshold=0.25):
        """Ultra-fast NMS implementation with minimal overhead"""
        if len(boxes) == 0:
            return []

        # Convert to numpy arrays for vectorized operations
        boxes = np.asarray(boxes, dtype=np.float32)
        scores = np.asarray(scores, dtype=np.float32)

        # Get sorted indices by score (descending)
        indices = np.argsort(scores)[::-1]
        
        keep = []
        while indices.size > 0:
            # Take the highest scoring box
            current = indices[0]
            keep.append(current)
            
            if indices.size == 1:
                break
            
            # Calculate IoU with vectorized operations
            remaining_indices = indices[1:]
            current_box = boxes[current]
            remaining_boxes = boxes[remaining_indices]
            
            # Fast IoU calculation
            ious = self._fast_iou(current_box, remaining_boxes)
            
            # Keep only boxes with IoU below threshold
            indices = remaining_indices[ious <= iou_threshold]
        
        return keep
    
    def _fast_iou(self, box, boxes):
        """Vectorized IoU calculation with minimal overhead"""
        # Intersection coordinates
        x1 = np.maximum(box[0], boxes[:, 0])
        y1 = np.maximum(box[1], boxes[:, 1])
        x2 = np.minimum(box[2], boxes[:, 2])
        y2 = np.minimum(box[3], boxes[:, 3])
        
        # Intersection area
        intersection = np.maximum(0, x2 - x1) * np.maximum(0, y2 - y1)
        
        # Box areas
        box_area = (box[2] - box[0]) * (box[3] - box[1])
        boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
        
        # Union and IoU
        union = box_area + boxes_area - intersection
        return intersection / np.maximum(union, 1e-10)

    def transform_coordinates(self, boxes, crop_x, crop_y, scale_x, scale_y):
        """Zero-latency coordinate transformation"""
        transformed_boxes = []
        
        for box in boxes:
            x1, y1, x2, y2 = box
            
            # Scale and transform in one operation
            x1_screen = int(x1 * scale_x + crop_x)
            y1_screen = int(y1 * scale_y + crop_y)
            x2_screen = int(x2 * scale_x + crop_x)
            y2_screen = int(y2 * scale_y + crop_y)
            
            transformed_boxes.append((x1_screen, y1_screen, x2_screen, y2_screen))
            
        return transformed_boxes

    def run(self):
        """Main detection loop with BitBlt capture and DirectML"""
        self.running = True
        
        if not self.yolo_ensemble.models:
            logging.error("No models loaded")
            return
            
        if not self.setup_bitblt():
            logging.error("BitBlt setup failed")
            return

        # Get screen dimensions
        self.screen_width = self.user32.GetSystemMetrics(0)  # SM_CXSCREEN
        self.screen_height = self.user32.GetSystemMetrics(1)  # SM_CYSCREEN
        self.center_x = self.screen_width >> 1
        self.center_y = self.screen_height >> 1
        
        logging.info(f"BitBlt detection started: {self.screen_width}x{self.screen_height}")
        
        last_time = time.perf_counter()
        
        try:
            while self.running:
                current_time = time.perf_counter()
                
                # Calculate FOV and crop region
                fov_radius = clamp(self.settings.get('fov_radius', 150), 50,
                                 min(self.screen_width, self.screen_height) >> 1)
                
                crop_x = max(self.center_x - fov_radius, 0)
                crop_y = max(self.center_y - fov_radius, 0)
                crop_width = min(fov_radius * 2, self.screen_width - crop_x)
                crop_height = min(fov_radius * 2, self.screen_height - crop_y)
                
                if crop_width < 100 or crop_height < 100:
                    continue
                
                # Capture screen region using BitBlt
                frame = self.capture_screen_region(crop_x, crop_y, crop_width, crop_height)
                
                if frame is None or frame.size == 0:
                    continue
                
                # Convert color space
                frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2RGB)
                
                # Run detection with DirectML
                conf_thres = clamp(self.settings.get('confidence', 0.5), 0.2, 0.95)
                all_results = self.yolo_ensemble.predict(frame, conf_thres=conf_thres)
                
                # Process detections
                detections = []
                for results in all_results:
                    if hasattr(results, 'boxes') and len(results.boxes) > 0:
                        boxes = results.boxes.xyxy.cpu().numpy()
                        confs = results.boxes.conf.cpu().numpy()
                        classes = results.boxes.cls.cpu().numpy()
                        
                        scale_x = getattr(results, 'scale_x', 1.0)
                        scale_y = getattr(results, 'scale_y', 1.0)
                        
                        screen_boxes = self.transform_coordinates(
                            boxes, crop_x, crop_y, scale_x, scale_y
                        )
                        
                        for i, (screen_box, conf, cls) in enumerate(zip(screen_boxes, confs, classes)):
                            if conf < conf_thres:
                                continue
                                
                            x1_screen, y1_screen, x2_screen, y2_screen = screen_box
                            class_id = int(cls)
                            class_name = self.yolo_ensemble.names.get(class_id, "person")
                            
                            if (x1_screen >= x2_screen or y1_screen >= y2_screen or
                                x1_screen < 0 or y1_screen < 0 or
                                x2_screen > self.screen_width or y2_screen > self.screen_height):
                                continue
                                
                            det_center_x = (x1_screen + x2_screen) >> 1
                            det_center_y = (y1_screen + y2_screen) >> 1
                            
                            dx = det_center_x - self.center_x
                            dy = det_center_y - self.center_y
                            dist_squared = dx * dx + dy * dy
                            
                            if dist_squared <= fov_radius * fov_radius:
                                detections.append((
                                    x1_screen, y1_screen, x2_screen, y2_screen,
                                    float(conf), class_name
                                ))

                # Sort by confidence (highest first)
                detections.sort(key=lambda x: x[4], reverse=True)

                # Extract data for processing
                boxes = [(x1, y1, x2, y2) for (x1, y1, x2, y2, _, _) in detections]
                confidences = [conf for (_, _, _, _, conf, _) in detections]
                labels = [label for (_, _, _, _, _, label) in detections]

                # Apply fast NMS
                if boxes:
                    selected_indices = self.fast_nms(boxes, confidences, iou_threshold=0.25)
                    boxes = [boxes[i] for i in selected_indices]
                    labels = [labels[i] for i in selected_indices]
                    confidences = [confidences[i] for i in selected_indices]

                # Prepare tracking data
                dets_for_sort = np.array([
                    [*box, conf] for box, conf in zip(boxes, confidences)
                ]) if boxes else np.empty((0, 5))
                
                # Tracking
                tracked_objects = self.tracker.update(dets_for_sort)

                # Extract tracking results
                tracked_boxes = []
                track_ids = []
                
                for obj in tracked_objects:
                    x1t, y1t, x2t, y2t, track_id = obj
                    
                    x1t, y1t, x2t, y2t = int(x1t), int(y1t), int(x2t), int(y2t)
                    
                    if (x1t < x2t and y1t < y2t and 
                        x1t >= 0 and y1t >= 0 and 
                        x2t <= self.screen_width and y2t <= self.screen_height):
                        
                        tracked_boxes.append((x1t, y1t, x2t, y2t))
                        track_ids.append(int(track_id))

                # Emit detection signal
                self.detection_signal.emit(
                    tracked_boxes, labels, confidences, labels, track_ids
                )
                
                # Performance monitoring
                self.frame_skip += 1
                if self.frame_skip >= 200:
                    self.frame_skip = 0
                    fps = 1.0 / max(0.001, current_time - last_time)
                    logging.info(f"BitBlt Performance - FPS: {fps:.1f}, Detections: {len(tracked_boxes)}")
                
                last_time = current_time
                
        except Exception as e:
            logging.error(f"Detection error: {e}")
        
        finally:
            self.cleanup_bitblt()

    def stop(self):
        """Clean shutdown"""
        self.running = False
        self.cleanup_bitblt()
        logging.info("BitBlt detection stopped")




class AimAssistThread(QtCore.QThread):
    aim_active_signal = QtCore.pyqtSignal(bool)

    def __init__(self, settings, get_targets_func, get_fov_center_func):
        super().__init__()
        self.settings = settings
        self.get_targets = get_targets_func
        self.get_fov_center = get_fov_center_func
        self.running = False
        self.aim_active = False
        self.lock = threading.Lock()
        
        # Tracking settings
        self.locked_track_id = None
        self.target_acquired = False
        
        # Performance settings
        self.aim_fps = 1000  # Increased for faster response
        self.frame_time = 1.0 / self.aim_fps
        self.last_aim_time = 0
        
        # Anti-recoil settings
        self.recoil_control = True
        self.recoil_strength = 1.3  # Adjust as needed
        self.recoil_recovery = 0.90  # Recovery rate
        self.current_recoil = 0
        
        # Aim settings
        self.aim_smoothing = 0.73  # Lower = faster aim
        self.max_speed = 8  # Increased max speed
        self.deadzone = 1.0  # Reduced deadzone
        
        # Toggle controls
        self.toggle_key = 'p'
        self.aim_toggled_on = False
        self.last_toggle_time = 0
        self.toggle_cooldown = 0.15  # Faster toggle response

    def calculate_aim_point(self, x1, y1, x2, y2):
        """Fast aim point calculation with height-based targeting"""
        center_x = (x1 + x2) >> 1
        height = y2 - y1
        # Aim higher for distant targets (smaller height)
        aim_height = 0.2 if height < 100 else 0.3
        center_y = y1 + int(height * aim_height)  # Aim at upper chest/head
        return center_x, center_y

    def apply_anti_recoil(self, dy):
        """Apply dynamic anti-recoil compensation"""
        if not self.recoil_control:
            return dy
            
        if dy > 0:  # Only apply when aiming down
            self.current_recoil = min(self.current_recoil + 0.2, self.recoil_strength)
        else:
            self.current_recoil *= self.recoil_recovery
            
        return dy - (self.current_recoil * 2.0)

    def calculate_movement(self, target_pos, fc_x, fc_y):
        """Optimized movement calculation with anti-recoil"""
        if not target_pos:
            return 0, 0

        dx = target_pos[0] - fc_x
        dy = target_pos[1] - fc_y
        
        # Apply deadzone
        if abs(dx) <= self.deadzone and abs(dy) <= self.deadzone:
            return 0, 0

        # Calculate base movement
        move_x = dx * self.aim_smoothing
        move_y = dy * self.aim_smoothing
        
        # Apply anti-recoil to vertical movement
        move_y = self.apply_anti_recoil(move_y)
        
        # Speed limiting
        move_x = clamp(move_x, -self.max_speed, self.max_speed)
        move_y = clamp(move_y, -self.max_speed, self.max_speed)

        return int(move_x), int(move_y)

    def get_best_target(self, targets, fc_x, fc_y, fov_radius):
        """Fast target selection with priority system"""
        if not targets:
            return None, None
            
        best_target = None
        best_score = float('inf')
        fov_radius_sq = fov_radius * fov_radius
        
        for target in targets:
            if len(target) < 4:
                continue
                
            x1, y1, x2, y2 = target[:4]
            track_id = target[5] if len(target) > 5 else None
            
            # Fast center calculation
            center_x, center_y = self.calculate_aim_point(x1, y1, x2, y2)
            
            # Quick distance check
            dx = center_x - fc_x
            dy = center_y - fc_y
            dist_sq = dx*dx + dy*dy
            
            if dist_sq >= fov_radius_sq:
                continue
                
            # Prioritize current target
            if track_id == self.locked_track_id:
                return (center_x, center_y), target
                
            # Score based on distance and size
            target_size = (x2-x1) * (y2-y1)
            score = dist_sq / (target_size + 1)
            
            if score < best_score:
                best_score = score
                best_target = ((center_x, center_y), target)
                
        return best_target if best_target else (None, None)

    def run(self):
        """High-performance aim assist loop"""
        self.running = True
        sleep_time = 0.0001  # Minimal sleep
        
        while self.running:
            try:
                current_time = time.perf_counter()
                
                if current_time - self.last_aim_time < self.frame_time:
                    time.sleep(sleep_time)
                    continue

                self.last_aim_time = current_time

                # Fast toggle check
                if keyboard.is_pressed(self.toggle_key):
                    if current_time - self.last_toggle_time > self.toggle_cooldown:
                        self.aim_toggled_on = not self.aim_toggled_on
                        self.last_toggle_time = current_time

                aim_active = False

                if self.aim_toggled_on and self.settings.get('aim_enabled', True):
                    with self.lock:
                        targets = self.get_targets()

                    fov_center = self.get_fov_center()
                    
                    if targets and fov_center:
                        fc_x, fc_y = (fov_center.x(), fov_center.y()) if hasattr(fov_center, "x") else fov_center
                        fov_radius = self.settings.get('fov_radius', 200)
                        
                        target_pos, target_data = self.get_best_target(targets, fc_x, fc_y, fov_radius)
                        
                        if target_pos:
                            if not self.target_acquired:
                                self.target_acquired = True
                                if target_data and len(target_data) > 5:
                                    self.locked_track_id = target_data[5]
                            
                            mdx, mdy = self.calculate_movement(target_pos, fc_x, fc_y)
                            
                            if mdx != 0 or mdy != 0:
                                move_mouse_relative(mdx, mdy)
                                aim_active = True
                        else:
                            self.target_acquired = False
                            self.locked_track_id = None
                            self.current_recoil = 0
                
                if aim_active != self.aim_active:
                    self.aim_active = aim_active
                    self.aim_active_signal.emit(self.aim_active)

            except Exception as e:
                logging.error(f"AimAssist error: {e}")
                time.sleep(sleep_time)

    def stop(self):
        """Clean shutdown"""
        self.running = False
        self.target_acquired = False
        self.locked_track_id = None
        self.current_recoil = 0