from __future__ import annotations import numpy as np from dataclasses import dataclass from typing import Optional, Tuple def minimum_jerk(t: np.ndarray) -> np.ndarray: """ Minimum jerk trajectory (5th order polynomial). This is the optimal trajectory that minimizes jerk (rate of change of acceleration). Human movements closely follow this profile. Args: t: Normalized time array [0, 1] Returns: Normalized position [0, 1] """ # 5th order polynomial: 10t³ - 15t⁴ + 6t⁵ return 10.0 * t**3 - 15.0 * t**4 + 6.0 * t**5 def signal_dependent_noise( velocity: np.ndarray, k: float = 0.1, rng: Optional[np.random.Generator] = None, ) -> np.ndarray: """ Signal-dependent noise following Harris-Wolpert model. Noise variance scales with signal magnitude (Weber's law). This is a fundamental property of human motor control. Args: velocity: Velocity array k: Noise coefficient (higher = noisier) rng: Random generator Returns: Noise array with signal-dependent variance """ if rng is None: rng = np.random.default_rng() std = k * np.abs(velocity) return rng.normal(0, std + 1e-8) # Small epsilon for stability def generate_micro_corrections( n_ticks: int, frequency: float = 5.0, amplitude: float = 0.02, rng: Optional[np.random.Generator] = None, ) -> np.ndarray: """ Generate micro-corrections that humans make during aiming. These are small corrective movements with quasi-periodic timing. Args: n_ticks: Number of time ticks frequency: Average number of corrections per trajectory amplitude: Maximum correction amplitude (degrees) rng: Random generator Returns: Array of shape [n_ticks, 2] with x,y corrections """ if rng is None: rng = np.random.default_rng() corrections = np.zeros((n_ticks, 2)) if frequency <= 0 or n_ticks < 2: return corrections # Generate correction times using exponential intervals mean_interval = n_ticks / frequency current_tick = 0 while current_tick < n_ticks: interval = int(rng.exponential(mean_interval)) current_tick += max(1, interval) if current_tick < n_ticks: # Small correction impulse amp = rng.exponential(amplitude) direction = rng.uniform(0, 2 * np.pi) corrections[current_tick, 0] = amp * np.cos(direction) corrections[current_tick, 1] = amp * np.sin(direction) # Smooth corrections (they're not instant) using simple moving average kernel_size = min(5, n_ticks // 2) if kernel_size > 1: kernel = np.ones(kernel_size) / kernel_size for i in range(2): corrections[:, i] = np.convolve(corrections[:, i], kernel, mode='same') return corrections def generate_human_trajectory( start_angle: np.ndarray, target_angle: np.ndarray, skill_aim: float, skill_consistency: float, duration_ms: float, tick_rate: int = 128, rng: Optional[np.random.Generator] = None, ) -> np.ndarray: """ Generate realistic human mouse trajectory using minimum jerk principle with signal-dependent noise and micro-corrections. Args: start_angle: Starting crosshair angle [x, y] in degrees target_angle: Target angle [x, y] in degrees skill_aim: Aim skill (0-100) skill_consistency: Consistency skill (0-100) duration_ms: Movement duration in milliseconds tick_rate: Ticks per second (CS2 uses 128) rng: Random generator Returns: Array of shape [n_ticks-1, 2] with delta (dx, dy) per tick """ if rng is None: rng = np.random.default_rng() start_angle = np.asarray(start_angle, dtype=np.float64) target_angle = np.asarray(target_angle, dtype=np.float64) n_ticks = max(2, int(duration_ms * tick_rate / 1000)) t = np.linspace(0, 1, n_ticks) # Minimum jerk profile s = minimum_jerk(t) # Planned trajectory displacement = target_angle - start_angle planned = start_angle[:, None] + displacement[:, None] * s # [2, n_ticks] # Velocity for signal-dependent noise (derivative of position) velocity = np.gradient(s) * np.linalg.norm(displacement) # Signal-dependent noise (Harris-Wolpert model) # Noise scale inversely proportional to skill noise_scale = (1.0 - skill_aim / 150.0) * 0.1 # 0.033 - 0.1 degrees noise_x = signal_dependent_noise(velocity, noise_scale, rng) noise_y = signal_dependent_noise(velocity, noise_scale, rng) noise = np.stack([noise_x, noise_y], axis=0) # [2, n_ticks] # Micro-corrections (human feedback control) correction_frequency = 3.0 + skill_consistency / 20.0 # 3-8 corrections correction_amplitude = 0.02 * (1.0 - skill_aim / 100.0) # Less corrections for skilled corrections = generate_micro_corrections(n_ticks, correction_frequency, correction_amplitude, rng) # Combine: planned + noise + corrections actual = planned + noise + corrections.T # Convert to deltas deltas = np.diff(actual, axis=1).T # [n_ticks-1, 2] return deltas def generate_aimbot_trajectory( natural_trajectory: np.ndarray, target_positions: np.ndarray, intensity: float, humanization: dict, rng: Optional[np.random.Generator] = None, ) -> np.ndarray: """ Modify natural trajectory with aimbot assistance. Even with humanization, leaves detectable artifacts: - Too smooth (lacks human micro-corrections) - Noise is too uniform - Delay is too consistent Args: natural_trajectory: Human trajectory [n_ticks, 2] target_positions: Target angle at each tick [n_ticks, 2] intensity: Cheat intensity (0-1) humanization: Dict with aim_smoothing, noise_amplitude, fov_degrees rng: Random generator Returns: Modified trajectory [n_ticks, 2] """ if rng is None: rng = np.random.default_rng() modified = natural_trajectory.copy() n_ticks = len(modified) # Cumulative positions from deltas positions = np.zeros((n_ticks + 1, 2)) positions[1:] = np.cumsum(modified, axis=0) fov_limit = humanization.get("fov_degrees", 90.0) smoothing = humanization.get("aim_smoothing", 0.5) noise_amp = humanization.get("noise_amplitude", 0.0) for i in range(n_ticks): current_pos = positions[i] target_pos = target_positions[i] correction_needed = target_pos - current_pos distance = np.linalg.norm(correction_needed) if distance < fov_limit: # Apply aimbot correction if smoothing > 0: # Humanized: exponential approach (ARTIFACT: too smooth) correction = correction_needed * (1.0 - smoothing) * intensity else: # Blatant: instant (ARTIFACT: infinite jerk) correction = correction_needed * intensity # Add humanization noise (ARTIFACT: noise is too uniform) if noise_amp > 0: correction += rng.normal(0, noise_amp, 2) modified[i] += correction # Update subsequent positions positions[i+1:] += correction return modified def fitts_law_time( distance: float, target_width: float, a: float = 0.0, b: float = 0.1, ) -> float: """ Calculate movement time using Fitts' Law. MT = a + b * log2(2D/W) Args: distance: Distance to target target_width: Width of target a: Intercept (reaction time offset) b: Slope (movement time per bit) Returns: Movement time in seconds """ if target_width <= 0: target_width = 1e-6 index_of_difficulty = np.log2(2.0 * distance / target_width + 1.0) return a + b * index_of_difficulty def extract_trajectory_features(trajectory: np.ndarray) -> dict: """ Extract features from trajectory for cheat detection. Args: trajectory: Delta trajectory [n_ticks, 2] Returns: Dict of extracted features """ if len(trajectory) < 3: return { "max_jerk": 0.0, "mean_jerk": 0.0, "jerk_variance": 0.0, "path_efficiency": 1.0, "velocity_peak_timing": 0.5, } # Compute velocity and acceleration velocity = np.linalg.norm(trajectory, axis=1) acceleration = np.diff(velocity) jerk = np.diff(acceleration) if len(acceleration) > 1 else np.array([0.0]) # Jerk statistics max_jerk = float(np.max(np.abs(jerk))) if len(jerk) > 0 else 0.0 mean_jerk = float(np.mean(np.abs(jerk))) if len(jerk) > 0 else 0.0 jerk_variance = float(np.var(jerk)) if len(jerk) > 0 else 0.0 # Path efficiency (actual distance / direct distance) total_distance = np.sum(velocity) direct_distance = np.linalg.norm(np.sum(trajectory, axis=0)) path_efficiency = direct_distance / (total_distance + 1e-8) # Velocity peak timing (should be ~0.5 for bell-shaped profile) if len(velocity) > 0: peak_idx = np.argmax(velocity) velocity_peak_timing = peak_idx / len(velocity) else: velocity_peak_timing = 0.5 return { "max_jerk": max_jerk, "mean_jerk": mean_jerk, "jerk_variance": jerk_variance, "path_efficiency": path_efficiency, "velocity_peak_timing": velocity_peak_timing, }