LimmeDev's picture
Initial MANIFOLD upload - CS2 cheat detection training
454ecdd verified
from __future__ import annotations
import numpy as np
from dataclasses import dataclass, field
from typing import Optional, Dict, Any, List, Iterator
import random
from manifold.data.profiles import PlayerProfile, SkillVector, RANK_STATISTICS, generate_correlated_skills, Rank
from manifold.data.cheats import CheatBehavior, CHEAT_PROFILES, CheatType
from manifold.data.trajectories import (
generate_human_trajectory,
generate_aimbot_trajectory,
fitts_law_time,
extract_trajectory_features,
)
from manifold.data.temporal import SessionSimulator, SessionState
# Extended trajectory feature names for 25 features
TRAJECTORY_FEATURE_NAMES = [
"max_jerk", "mean_jerk", "jerk_variance", "jerk_skewness", "jerk_kurtosis",
"path_efficiency", "velocity_peak_timing", "max_velocity", "mean_velocity", "velocity_variance",
"max_acceleration", "mean_acceleration", "acceleration_variance",
"total_distance", "direct_distance", "x_displacement", "y_displacement",
"direction_changes", "smoothness_index", "curvature_mean", "curvature_variance",
"overshoot_magnitude", "correction_count", "final_error", "movement_duration_ratio",
]
def extract_extended_trajectory_features(trajectory: np.ndarray) -> Dict[str, float]:
"""
Extract 25 features from trajectory for cheat detection.
Extends the base extract_trajectory_features with additional metrics.
Args:
trajectory: Delta trajectory [n_ticks, 2]
Returns:
Dict of 25 extracted features
"""
# Start with base features
base_features = extract_trajectory_features(trajectory)
if len(trajectory) < 3:
# Return zeros for all features
return {name: 0.0 for name in TRAJECTORY_FEATURE_NAMES}
# Compute velocity, acceleration, jerk
velocity = np.linalg.norm(trajectory, axis=1)
acceleration = np.diff(velocity) if len(velocity) > 1 else np.array([0.0])
jerk = np.diff(acceleration) if len(acceleration) > 1 else np.array([0.0])
# Jerk statistics (extended)
jerk_skewness = float(_safe_skewness(jerk)) if len(jerk) > 2 else 0.0
jerk_kurtosis = float(_safe_kurtosis(jerk)) if len(jerk) > 3 else 0.0
# Velocity statistics
max_velocity = float(np.max(velocity)) if len(velocity) > 0 else 0.0
mean_velocity = float(np.mean(velocity)) if len(velocity) > 0 else 0.0
velocity_variance = float(np.var(velocity)) if len(velocity) > 0 else 0.0
# Acceleration statistics
max_acceleration = float(np.max(np.abs(acceleration))) if len(acceleration) > 0 else 0.0
mean_acceleration = float(np.mean(np.abs(acceleration))) if len(acceleration) > 0 else 0.0
acceleration_variance = float(np.var(acceleration)) if len(acceleration) > 0 else 0.0
# Distance metrics
total_distance = float(np.sum(velocity))
cumulative_displacement = np.cumsum(trajectory, axis=0)
direct_distance = float(np.linalg.norm(cumulative_displacement[-1])) if len(cumulative_displacement) > 0 else 0.0
x_displacement = float(cumulative_displacement[-1, 0]) if len(cumulative_displacement) > 0 else 0.0
y_displacement = float(cumulative_displacement[-1, 1]) if len(cumulative_displacement) > 0 else 0.0
# Direction changes
if len(trajectory) > 1:
angles = np.arctan2(trajectory[:, 1], trajectory[:, 0])
angle_diffs = np.abs(np.diff(angles))
direction_changes = float(np.sum(angle_diffs > np.pi / 4)) # Count significant direction changes
else:
direction_changes = 0.0
# Smoothness index (inverse of total squared jerk)
smoothness_index = 1.0 / (1.0 + np.sum(jerk**2)) if len(jerk) > 0 else 1.0
# Curvature statistics
if len(trajectory) > 2:
# Approximate curvature as angle change per unit distance
curvatures = []
for i in range(1, len(trajectory) - 1):
v1 = trajectory[i]
v2 = trajectory[i + 1]
cross = v1[0] * v2[1] - v1[1] * v2[0]
norm_product = (np.linalg.norm(v1) * np.linalg.norm(v2)) + 1e-8
curvatures.append(abs(cross / norm_product))
curvature_mean = float(np.mean(curvatures)) if curvatures else 0.0
curvature_variance = float(np.var(curvatures)) if curvatures else 0.0
else:
curvature_mean = 0.0
curvature_variance = 0.0
# Overshoot detection (when trajectory goes past target then comes back)
positions = np.cumsum(trajectory, axis=0)
if len(positions) > 1:
final_pos = positions[-1]
distances_to_final = np.linalg.norm(positions - final_pos, axis=1)
# Overshoot = minimum distance was achieved before the end
min_dist_idx = np.argmin(distances_to_final[:-1]) if len(distances_to_final) > 1 else 0
overshoot_magnitude = float(np.max(distances_to_final[min_dist_idx:])) if min_dist_idx < len(distances_to_final) - 1 else 0.0
else:
overshoot_magnitude = 0.0
# Correction count (velocity sign changes)
if len(velocity) > 1:
vel_diff = np.diff(velocity)
correction_count = float(np.sum(np.abs(np.diff(np.sign(vel_diff))) > 0)) if len(vel_diff) > 1 else 0.0
else:
correction_count = 0.0
# Final error (assuming target is at cumulative endpoint - would need target info)
# For now, use variance of final positions as proxy
final_error = float(np.linalg.norm(trajectory[-1])) if len(trajectory) > 0 else 0.0
# Movement duration ratio (proportion of time with significant movement)
movement_threshold = 0.01 * max_velocity if max_velocity > 0 else 0.01
movement_duration_ratio = float(np.mean(velocity > movement_threshold)) if len(velocity) > 0 else 0.0
return {
"max_jerk": base_features["max_jerk"],
"mean_jerk": base_features["mean_jerk"],
"jerk_variance": base_features["jerk_variance"],
"jerk_skewness": jerk_skewness,
"jerk_kurtosis": jerk_kurtosis,
"path_efficiency": base_features["path_efficiency"],
"velocity_peak_timing": base_features["velocity_peak_timing"],
"max_velocity": max_velocity,
"mean_velocity": mean_velocity,
"velocity_variance": velocity_variance,
"max_acceleration": max_acceleration,
"mean_acceleration": mean_acceleration,
"acceleration_variance": acceleration_variance,
"total_distance": total_distance,
"direct_distance": direct_distance,
"x_displacement": x_displacement,
"y_displacement": y_displacement,
"direction_changes": direction_changes,
"smoothness_index": smoothness_index,
"curvature_mean": curvature_mean,
"curvature_variance": curvature_variance,
"overshoot_magnitude": overshoot_magnitude,
"correction_count": correction_count,
"final_error": final_error,
"movement_duration_ratio": movement_duration_ratio,
}
def _safe_skewness(arr: np.ndarray) -> float:
"""Compute skewness safely."""
if len(arr) < 3:
return 0.0
mean = np.mean(arr)
std = np.std(arr)
if std < 1e-8:
return 0.0
return float(np.mean(((arr - mean) / std) ** 3))
def _safe_kurtosis(arr: np.ndarray) -> float:
"""Compute kurtosis safely."""
if len(arr) < 4:
return 0.0
mean = np.mean(arr)
std = np.std(arr)
if std < 1e-8:
return 0.0
return float(np.mean(((arr - mean) / std) ** 4) - 3.0)
@dataclass
class EngagementData:
"""Single engagement - the atomic training unit with 64 features."""
# Context features [12]
enemy_distance: float
enemy_velocity: float
player_velocity: float
player_health: float
enemy_health: float
weapon_type: int
is_scoped: bool
is_crouched: bool
round_time_remaining: float
score_differential: float
is_clutch: bool
enemies_alive: int
# Pre-engagement features [8]
crosshair_angle_to_hidden_enemy: float
time_tracking_hidden_ms: float
prefire_indicator: bool
check_pattern_efficiency: float
rotation_timing_vs_enemy: float
flank_awareness_score: float
info_advantage_score: float
position_optimality: float
# Trajectory features [25] - from extract_trajectory_features
trajectory_features: Dict[str, float] = field(default_factory=dict)
# Timing features [10]
reaction_time_ms: float = 0.0
time_to_first_shot_ms: float = 0.0
time_to_damage_ms: float = 0.0
time_to_kill_ms: float = 0.0
shot_timing_variance: float = 0.0
inter_shot_interval_mean: float = 0.0
inter_shot_interval_cv: float = 0.0
crosshair_on_enemy_to_shot_ms: float = 0.0
anticipatory_shot_rate: float = 0.0
perfect_timing_rate: float = 0.0
# Accuracy features [9]
shots_fired: int = 0
shots_hit: int = 0
headshots: int = 0
damage_dealt: float = 0.0
spray_accuracy: float = 0.0
first_bullet_accuracy: float = 0.0
headshot_rate: float = 0.0
damage_efficiency: float = 0.0
kill_secured: bool = False
# Labels
is_cheater: bool = False
cheat_type: str = "none"
cheat_intensity: float = 0.0
cheat_active_this_engagement: bool = False
def to_tensor(self) -> np.ndarray:
"""Convert to 64-dim feature vector."""
# Context features [12]
context = [
self.enemy_distance,
self.enemy_velocity,
self.player_velocity,
self.player_health,
self.enemy_health,
float(self.weapon_type),
float(self.is_scoped),
float(self.is_crouched),
self.round_time_remaining,
self.score_differential,
float(self.is_clutch),
float(self.enemies_alive),
]
# Pre-engagement features [8]
pre_engagement = [
self.crosshair_angle_to_hidden_enemy,
self.time_tracking_hidden_ms,
float(self.prefire_indicator),
self.check_pattern_efficiency,
self.rotation_timing_vs_enemy,
self.flank_awareness_score,
self.info_advantage_score,
self.position_optimality,
]
# Trajectory features [25]
trajectory = [
self.trajectory_features.get(name, 0.0)
for name in TRAJECTORY_FEATURE_NAMES
]
# Timing features [10]
timing = [
self.reaction_time_ms,
self.time_to_first_shot_ms,
self.time_to_damage_ms,
self.time_to_kill_ms,
self.shot_timing_variance,
self.inter_shot_interval_mean,
self.inter_shot_interval_cv,
self.crosshair_on_enemy_to_shot_ms,
self.anticipatory_shot_rate,
self.perfect_timing_rate,
]
# Accuracy features [9]
accuracy = [
float(self.shots_fired),
float(self.shots_hit),
float(self.headshots),
self.damage_dealt,
self.spray_accuracy,
self.first_bullet_accuracy,
self.headshot_rate,
self.damage_efficiency,
float(self.kill_secured),
]
# Concatenate all: 12 + 8 + 25 + 10 + 9 = 64
features = context + pre_engagement + trajectory + timing + accuracy
return np.array(features, dtype=np.float32)
@dataclass
class PlayerSession:
"""Complete player session with multiple engagements."""
player_id: str
profile: PlayerProfile
engagements: List[EngagementData]
is_cheater: bool
cheat_profile: Optional[str] = None
rank: str = "gold_nova"
def to_tensor(self) -> np.ndarray:
"""Convert to tensor [num_engagements, 64]."""
return np.stack([e.to_tensor() for e in self.engagements])
# Weapon type mapping
WEAPON_TYPES = {
"rifle": 0,
"smg": 1,
"pistol": 2,
"awp": 3,
"shotgun": 4,
"machine_gun": 5,
}
class SyntheticDataGenerator:
"""
Generate synthetic CS2 player behavior data.
Orchestrates all data modules to create realistic player sessions
with proper skill modeling, cheat injection, and temporal dynamics.
"""
def __init__(
self,
seed: Optional[int] = None,
engagements_per_session: int = 200,
):
self.rng = np.random.default_rng(seed)
self.engagements_per_session = engagements_per_session
self._seed = seed
def generate_player(
self,
is_cheater: bool = False,
rank: Optional[str] = None,
cheat_profile: Optional[str] = None,
) -> PlayerSession:
"""Generate a single player session."""
# 1. Create player profile with correlated skills
if rank is None:
rank = self.rng.choice(["silver", "gold_nova", "master_guardian", "legendary_eagle", "supreme_global"])
profile = PlayerProfile.generate(
rank=rank,
seed=int(self.rng.integers(0, 2**31)),
)
profile.is_cheater = is_cheater
# 2. Initialize session simulator for temporal dynamics
session = SessionSimulator.from_skill(
mental_resilience=profile.skill_vector.mental_resilience,
seed=int(self.rng.integers(0, 2**31)),
)
# 3. Setup cheat behavior if cheater
cheat_behavior: Optional[CheatBehavior] = None
if is_cheater:
if cheat_profile is None:
cheat_profile = self.rng.choice(list(CHEAT_PROFILES.keys()))
cheat_behavior = CheatBehavior.from_profile(
cheat_profile,
seed=int(self.rng.integers(0, 2**31)),
)
# 4. Generate engagements
engagements: List[EngagementData] = []
rounds_per_match = 24
base_engagements_per_round = self.engagements_per_session // rounds_per_match
extra_engagements = self.engagements_per_session % rounds_per_match
score_differential = 0
for round_num in range(rounds_per_match):
engagements_this_round = base_engagements_per_round + (1 if round_num < extra_engagements else 0)
# Determine round context
is_losing = score_differential < -3
round_won = self.rng.random() < 0.5
for eng_num in range(engagements_this_round):
is_clutch = (eng_num == engagements_this_round - 1) and self.rng.random() < 0.2
# Update session state
event = "idle"
if eng_num == 0:
event = "round_win" if round_won else "round_loss"
elif self.rng.random() < 0.3:
event = "kill" if self.rng.random() < 0.5 else "death"
if is_clutch:
session.update("clutch_situation", 1000.0)
else:
session.update(event, 3000.0)
# Generate game context
game_context = {
"round_time_remaining": 115.0 - (eng_num * 10.0) + self.rng.uniform(-5, 5),
"score_differential": score_differential,
"is_clutch": is_clutch,
"enemies_alive": max(1, 5 - eng_num // 2),
"is_losing": is_losing,
}
# Determine if cheat is active this engagement
cheat_active = False
if cheat_behavior is not None:
cheat_active = cheat_behavior.should_activate(
is_clutch=is_clutch,
is_losing=is_losing,
round_number=round_num,
rng=self.rng,
)
cheat_behavior.is_active = cheat_active
engagement = self.generate_engagement(
profile=profile,
session=session,
cheat_behavior=cheat_behavior if cheat_active else None,
game_context=game_context,
)
# Set labels
engagement.is_cheater = is_cheater
engagement.cheat_type = cheat_profile if is_cheater else "none"
engagement.cheat_intensity = cheat_behavior.config.intensity if cheat_behavior else 0.0
engagement.cheat_active_this_engagement = cheat_active
engagements.append(engagement)
# Update score
if round_won:
score_differential += 1
else:
score_differential -= 1
return PlayerSession(
player_id=profile.profile_id,
profile=profile,
engagements=engagements,
is_cheater=is_cheater,
cheat_profile=cheat_profile,
rank=rank,
)
def generate_engagement(
self,
profile: PlayerProfile,
session: SessionSimulator,
cheat_behavior: Optional[CheatBehavior] = None,
game_context: Optional[Dict[str, Any]] = None,
) -> EngagementData:
"""Generate a single engagement."""
if game_context is None:
game_context = {}
skills = profile.skill_vector
rank_stats = RANK_STATISTICS[profile.rank]
# Get session modifiers
modifiers = session.get_modifiers()
# 1. Generate context features
enemy_distance = self.rng.uniform(5.0, 50.0) # meters
enemy_velocity = self.rng.uniform(0.0, 250.0) # units/s
player_velocity = self.rng.uniform(0.0, 250.0)
player_health = self.rng.uniform(20.0, 100.0)
enemy_health = self.rng.uniform(20.0, 100.0)
weapon_type = int(self.rng.integers(0, len(WEAPON_TYPES)))
is_scoped = weapon_type == WEAPON_TYPES["awp"] and self.rng.random() < 0.7
is_crouched = self.rng.random() < 0.3
round_time_remaining = game_context.get("round_time_remaining", 90.0)
score_differential = game_context.get("score_differential", 0)
is_clutch = game_context.get("is_clutch", False)
enemies_alive = game_context.get("enemies_alive", 3)
# 2. Generate pre-engagement features
# These are affected by game sense and wallhack cheats
has_wallhack = (
cheat_behavior is not None and
CheatType.WALLHACK in cheat_behavior.config.cheat_types
)
game_sense_effective = skills.game_sense * modifiers.get("game_sense_mult", 1.0)
# Crosshair angle to hidden enemy (wallhackers track better)
if has_wallhack:
crosshair_angle_to_hidden = self.rng.uniform(0.0, 15.0) # Suspiciously good
time_tracking_hidden = self.rng.uniform(500.0, 2000.0) # Long tracking time
prefire_indicator = self.rng.random() < (0.3 * (1.0 - cheat_behavior.config.humanization.get("prefire_suppression", 0.0)))
else:
crosshair_angle_to_hidden = self.rng.uniform(20.0, 90.0) * (1.0 - game_sense_effective / 150.0)
time_tracking_hidden = self.rng.exponential(200.0)
prefire_indicator = self.rng.random() < (0.05 * game_sense_effective / 100.0)
check_pattern_efficiency = (game_sense_effective / 100.0) * self.rng.uniform(0.7, 1.0)
rotation_timing_vs_enemy = self.rng.uniform(0.3, 1.0) * (0.5 + game_sense_effective / 200.0)
flank_awareness_score = self.rng.uniform(0.2, 1.0) * (0.5 + game_sense_effective / 200.0)
info_advantage_score = self.rng.uniform(0.0, 1.0)
position_optimality = self.rng.uniform(0.3, 1.0) * (0.5 + game_sense_effective / 200.0)
if has_wallhack:
check_pattern_efficiency = min(1.0, check_pattern_efficiency * 1.3)
rotation_timing_vs_enemy = min(1.0, rotation_timing_vs_enemy * 1.2)
flank_awareness_score = min(1.0, flank_awareness_score * 1.4)
# 3. Generate trajectory
# Start and target angles
start_angle = np.array([
self.rng.uniform(-30.0, 30.0),
self.rng.uniform(-20.0, 20.0),
])
target_angle = np.array([
self.rng.uniform(-5.0, 5.0),
self.rng.uniform(-3.0, 3.0),
])
# Movement time based on Fitts' law and skill
target_width = 3.0 if weapon_type == WEAPON_TYPES["awp"] else 8.0 # Hitbox size in degrees
distance = np.linalg.norm(target_angle - start_angle)
base_movement_time = fitts_law_time(distance, target_width, a=0.1, b=0.15)
skill_factor = (100.0 - skills.raw_aim) / 100.0 # Lower skill = longer time
movement_time_s = base_movement_time * (1.0 + skill_factor * 0.5) * modifiers.get("reaction_time_mult", 1.0)
movement_time_ms = movement_time_s * 1000.0
# Generate human trajectory
trajectory = generate_human_trajectory(
start_angle=start_angle,
target_angle=target_angle,
skill_aim=skills.raw_aim,
skill_consistency=skills.consistency,
duration_ms=movement_time_ms,
tick_rate=128,
rng=self.rng,
)
# 4. Apply aimbot modification if active
has_aimbot = (
cheat_behavior is not None and
CheatType.AIMBOT in cheat_behavior.config.cheat_types
)
if has_aimbot:
n_ticks = len(trajectory)
# Target positions (enemy moves slightly)
target_positions = np.tile(target_angle, (n_ticks, 1))
target_positions += self.rng.normal(0, 0.5, (n_ticks, 2)) # Small enemy movement
trajectory = generate_aimbot_trajectory(
natural_trajectory=trajectory,
target_positions=target_positions,
intensity=cheat_behavior.config.intensity,
humanization=cheat_behavior.config.humanization,
rng=self.rng,
)
# 5. Extract trajectory features
trajectory_features = extract_extended_trajectory_features(trajectory)
# 6. Compute timing features
rt_low, rt_high = rank_stats["reaction_time_ms"]
base_reaction_time = self.rng.uniform(rt_low, rt_high)
# Apply session modifiers
reaction_time_ms = base_reaction_time * modifiers.get("reaction_time_mult", 1.0) * modifiers.get("focus_reaction_mult", 1.0)
# Aimbot affects reaction time (but adds artificial delay for humanization)
if has_aimbot:
cheat_delay = cheat_behavior.config.humanization.get("reaction_delay_ms", 0.0)
reaction_time_ms = max(50.0, reaction_time_ms * 0.7 + cheat_delay)
# Triggerbot affects time to first shot
has_triggerbot = (
cheat_behavior is not None and
CheatType.TRIGGERBOT in cheat_behavior.config.cheat_types
)
time_to_first_shot_ms = reaction_time_ms + movement_time_ms * 0.5
if has_triggerbot:
# Triggerbot fires instantly when crosshair is on target
time_to_first_shot_ms *= 0.6 # Suspiciously fast
time_to_damage_ms = time_to_first_shot_ms + self.rng.uniform(0, 100)
time_to_kill_ms = time_to_damage_ms + self.rng.uniform(100, 500)
shot_timing_variance = self.rng.uniform(10.0, 50.0) * (1.0 - skills.consistency / 150.0)
if has_triggerbot:
shot_timing_variance *= 0.3 # Too consistent
inter_shot_interval_mean = 100.0 + self.rng.uniform(-20, 50) # ms
inter_shot_interval_cv = self.rng.uniform(0.1, 0.4) * (1.0 - skills.consistency / 150.0)
crosshair_on_enemy_to_shot_ms = self.rng.uniform(50, 200) * (1.0 - skills.reaction_speed / 150.0)
if has_triggerbot:
crosshair_on_enemy_to_shot_ms = self.rng.uniform(5, 30) # Inhuman speed
anticipatory_shot_rate = 0.02 + skills.game_sense / 1000.0
if prefire_indicator:
anticipatory_shot_rate += 0.1
perfect_timing_rate = skills.consistency / 200.0
if has_triggerbot:
perfect_timing_rate = min(1.0, perfect_timing_rate * 2.0)
# 7. Compute accuracy features
base_accuracy = self.rng.uniform(*rank_stats["accuracy"])
accuracy = base_accuracy * modifiers.get("accuracy_mult", 1.0)
if has_aimbot:
# Aimbot improves accuracy but may intentionally miss
if cheat_behavior.should_miss_intentionally(self.rng):
accuracy *= 0.5
else:
accuracy = min(1.0, accuracy + 0.3 * cheat_behavior.config.intensity)
shots_fired = int(self.rng.integers(3, 15))
shots_hit = int(np.clip(shots_fired * accuracy * self.rng.uniform(0.8, 1.2), 0, shots_fired))
hs_low, hs_high = rank_stats["hs_percent"]
hs_rate = self.rng.uniform(hs_low, hs_high)
if has_aimbot:
hs_rate = min(1.0, hs_rate + 0.2 * cheat_behavior.config.intensity)
headshots = int(np.clip(shots_hit * hs_rate, 0, shots_hit))
damage_per_hit = 25.0 + headshots * 75.0 / max(1, shots_hit) # Headshots do more damage
damage_dealt = shots_hit * damage_per_hit
spray_accuracy = accuracy * self.rng.uniform(0.6, 1.0) * (0.5 + skills.spray_control / 200.0)
first_bullet_accuracy = accuracy * self.rng.uniform(0.8, 1.2) * (0.5 + skills.crosshair_placement / 200.0)
headshot_rate = headshots / max(1, shots_hit)
damage_efficiency = damage_dealt / max(1, shots_fired * 100.0)
kill_secured = damage_dealt >= enemy_health
return EngagementData(
# Context features
enemy_distance=enemy_distance,
enemy_velocity=enemy_velocity,
player_velocity=player_velocity,
player_health=player_health,
enemy_health=enemy_health,
weapon_type=weapon_type,
is_scoped=is_scoped,
is_crouched=is_crouched,
round_time_remaining=round_time_remaining,
score_differential=score_differential,
is_clutch=is_clutch,
enemies_alive=enemies_alive,
# Pre-engagement features
crosshair_angle_to_hidden_enemy=crosshair_angle_to_hidden,
time_tracking_hidden_ms=time_tracking_hidden,
prefire_indicator=prefire_indicator,
check_pattern_efficiency=check_pattern_efficiency,
rotation_timing_vs_enemy=rotation_timing_vs_enemy,
flank_awareness_score=flank_awareness_score,
info_advantage_score=info_advantage_score,
position_optimality=position_optimality,
# Trajectory features
trajectory_features=trajectory_features,
# Timing features
reaction_time_ms=reaction_time_ms,
time_to_first_shot_ms=time_to_first_shot_ms,
time_to_damage_ms=time_to_damage_ms,
time_to_kill_ms=time_to_kill_ms,
shot_timing_variance=shot_timing_variance,
inter_shot_interval_mean=inter_shot_interval_mean,
inter_shot_interval_cv=inter_shot_interval_cv,
crosshair_on_enemy_to_shot_ms=crosshair_on_enemy_to_shot_ms,
anticipatory_shot_rate=anticipatory_shot_rate,
perfect_timing_rate=perfect_timing_rate,
# Accuracy features
shots_fired=shots_fired,
shots_hit=shots_hit,
headshots=headshots,
damage_dealt=damage_dealt,
spray_accuracy=spray_accuracy,
first_bullet_accuracy=first_bullet_accuracy,
headshot_rate=headshot_rate,
damage_efficiency=damage_efficiency,
kill_secured=kill_secured,
)
def generate_batch(
self,
num_legit: int,
num_cheaters: int,
cheater_distribution: Optional[Dict[str, float]] = None,
) -> List[PlayerSession]:
"""Generate batch of player sessions."""
if cheater_distribution is None:
# Default distribution across cheat profiles
cheater_distribution = {
"blatant_rage": 0.1,
"obvious": 0.2,
"closet_moderate": 0.3,
"closet_subtle": 0.3,
"wallhack_only": 0.1,
}
sessions: List[PlayerSession] = []
# Generate legit players
for _ in range(num_legit):
sessions.append(self.generate_player(is_cheater=False))
# Generate cheaters with distribution
cheat_profiles = list(cheater_distribution.keys())
cheat_probs = list(cheater_distribution.values())
for _ in range(num_cheaters):
profile = self.rng.choice(cheat_profiles, p=cheat_probs)
sessions.append(self.generate_player(is_cheater=True, cheat_profile=profile))
# Shuffle
self.rng.shuffle(sessions)
return sessions
def generate_stream(
self,
num_legit: int,
num_cheaters: int,
) -> Iterator[PlayerSession]:
"""Memory-efficient streaming generator."""
total = num_legit + num_cheaters
# Create shuffled indices for legit vs cheater
is_cheater_flags = [False] * num_legit + [True] * num_cheaters
self.rng.shuffle(is_cheater_flags)
for is_cheater in is_cheater_flags:
yield self.generate_player(is_cheater=is_cheater)