LimmeDev's picture
Initial MANIFOLD upload - CS2 cheat detection training
454ecdd verified
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,
}