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
@dataclass
class SessionState:
"""
Tracks player state over a session.
Models fatigue, tilt, and focus using Ornstein-Uhlenbeck processes.
"""
fatigue: float = 0.0 # 0 = fresh, 1 = exhausted
tilt: float = 0.0 # -1 = tilted, 0 = neutral, 1 = confident
focus: float = 1.0 # 0 = distracted, 1 = locked in
time_elapsed_ms: float = 0.0
def to_dict(self) -> Dict[str, float]:
return {
"fatigue": self.fatigue,
"tilt": self.tilt,
"focus": self.focus,
"time_elapsed_ms": self.time_elapsed_ms,
}
@classmethod
def from_dict(cls, d: Dict[str, float]) -> SessionState:
return cls(
fatigue=d.get("fatigue", 0.0),
tilt=d.get("tilt", 0.0),
focus=d.get("focus", 1.0),
time_elapsed_ms=d.get("time_elapsed_ms", 0.0),
)
@dataclass
class SessionSimulator:
"""
Simulates player state evolution over a gaming session.
Uses Ornstein-Uhlenbeck processes for realistic temporal dynamics:
- Fatigue: Slowly increases, slowly recovers
- Tilt: Affected by wins/losses, mean-reverts
- Focus: Affected by round importance
"""
# OU process parameters
fatigue_reversion: float = 0.001 # Slow recovery
fatigue_drift: float = 0.0001 # Gradual increase
fatigue_volatility: float = 0.001
tilt_reversion: float = 0.01 # Faster emotional recovery
tilt_volatility: float = 0.01
focus_reversion: float = 0.005
focus_volatility: float = 0.005
# Mental resilience affects tilt response (0-100)
mental_resilience: float = 50.0
# Current state
state: SessionState = field(default_factory=SessionState)
# Random generator
_rng: Optional[np.random.Generator] = field(default=None, repr=False)
def __post_init__(self):
if self._rng is None:
self._rng = np.random.default_rng()
@classmethod
def from_skill(
cls,
mental_resilience: float = 50.0,
seed: Optional[int] = None,
) -> SessionSimulator:
"""Create simulator with skill-based parameters."""
return cls(
mental_resilience=mental_resilience,
_rng=np.random.default_rng(seed),
)
def update(self, event: str, dt_ms: float) -> None:
"""
Update session state based on game event.
Args:
event: One of "round_win", "round_loss", "kill", "death",
"clutch_situation", "idle"
dt_ms: Time elapsed in milliseconds
"""
self.state.time_elapsed_ms += dt_ms
# Fatigue: OU process with drift (slowly increases)
# dF = θ(0 - F)dt + drift*dt + σ*dW
fatigue_noise = self._rng.normal(0, self.fatigue_volatility * np.sqrt(dt_ms))
self.state.fatigue += (
self.fatigue_reversion * (0 - self.state.fatigue) * dt_ms +
self.fatigue_drift * dt_ms +
fatigue_noise
)
self.state.fatigue = np.clip(self.state.fatigue, 0.0, 1.0)
# Tilt: affected by outcomes
tilt_impact = 0.0
if event == "round_loss":
# More impact if low mental resilience
tilt_impact = -0.1 * (1.0 - self.mental_resilience / 100.0)
elif event == "round_win":
tilt_impact = 0.05
elif event == "death":
tilt_impact = -0.02 * (1.0 - self.mental_resilience / 100.0)
elif event == "kill":
tilt_impact = 0.01
self.state.tilt += tilt_impact
# Tilt mean reversion (OU process)
tilt_noise = self._rng.normal(0, self.tilt_volatility * np.sqrt(dt_ms))
self.state.tilt += (
self.tilt_reversion * (0 - self.state.tilt) * dt_ms +
tilt_noise
)
self.state.tilt = np.clip(self.state.tilt, -1.0, 1.0)
# Focus: affected by round importance
if event == "clutch_situation":
# Focus increases in clutch (if mentally strong)
focus_boost = 0.2 * (0.5 + self.mental_resilience / 200.0)
self.state.focus = min(1.0, self.state.focus + focus_boost)
# Focus mean reversion
focus_noise = self._rng.normal(0, self.focus_volatility * np.sqrt(dt_ms))
self.state.focus += (
self.focus_reversion * (1.0 - self.state.focus) * dt_ms +
focus_noise
)
self.state.focus = np.clip(self.state.focus, 0.0, 1.0)
def get_modifiers(self) -> Dict[str, float]:
"""
Get performance modifiers based on current state.
Returns:
Dict with multipliers for different stats
"""
return {
# Fatigue slows reactions and reduces accuracy
"reaction_time_mult": 1.0 + self.state.fatigue * 0.2, # Up to 20% slower
"accuracy_mult": 1.0 - self.state.fatigue * 0.15, # Up to 15% less accurate
# Tilt affects consistency
"consistency_mult": (
1.0 - abs(self.state.tilt) * 0.3 if self.state.tilt < 0
else 1.0 + self.state.tilt * 0.1
),
# Tilt affects game sense when negative
"game_sense_mult": 1.0 - max(0, -self.state.tilt) * 0.2,
# Focus improves reaction time
"focus_reaction_mult": 1.0 - (self.state.focus - 0.5) * 0.1,
}
def apply_to_stats(self, base_stats: Dict[str, float]) -> Dict[str, float]:
"""
Apply session modifiers to base stats.
Args:
base_stats: Dict with keys like "reaction_time", "accuracy", etc.
Returns:
Modified stats dict
"""
mods = self.get_modifiers()
modified = base_stats.copy()
if "reaction_time" in modified:
modified["reaction_time"] *= mods["reaction_time_mult"] * mods["focus_reaction_mult"]
if "accuracy" in modified:
modified["accuracy"] *= mods["accuracy_mult"]
if "consistency" in modified:
modified["consistency"] *= mods["consistency_mult"]
if "game_sense" in modified:
modified["game_sense"] *= mods["game_sense_mult"]
return modified
def reset(self) -> None:
"""Reset session state to fresh."""
self.state = SessionState()
def simulate_round_sequence(
n_rounds: int,
win_probability: float = 0.5,
seed: Optional[int] = None,
) -> list[str]:
"""
Simulate a sequence of round outcomes.
Args:
n_rounds: Number of rounds
win_probability: Base probability of winning each round
seed: Random seed
Returns:
List of events ("round_win" or "round_loss")
"""
rng = np.random.default_rng(seed)
events = []
for _ in range(n_rounds):
if rng.random() < win_probability:
events.append("round_win")
else:
events.append("round_loss")
return events
def generate_session_trace(
n_rounds: int = 24,
mental_resilience: float = 50.0,
win_probability: float = 0.5,
round_duration_ms: float = 90000.0, # 1.5 minutes per round
seed: Optional[int] = None,
) -> list[Dict[str, Any]]:
"""
Generate complete session trace with states at each round.
Args:
n_rounds: Number of rounds
mental_resilience: Player's mental resilience (0-100)
win_probability: Base win probability
round_duration_ms: Average round duration
seed: Random seed
Returns:
List of dicts with round number, event, state, and modifiers
"""
rng = np.random.default_rng(seed)
sim = SessionSimulator.from_skill(mental_resilience, seed)
events = simulate_round_sequence(n_rounds, win_probability, seed)
trace = []
for round_num, event in enumerate(events):
# Add some kills/deaths during round
n_kills = rng.poisson(1.0)
n_deaths = rng.poisson(0.5)
for _ in range(n_kills):
sim.update("kill", round_duration_ms / (n_kills + n_deaths + 1))
for _ in range(n_deaths):
sim.update("death", round_duration_ms / (n_kills + n_deaths + 1))
# Round outcome
sim.update(event, round_duration_ms / 3)
trace.append({
"round": round_num,
"event": event,
"state": sim.state.to_dict(),
"modifiers": sim.get_modifiers(),
})
return trace