Add SkillProgressionAnomalyAnalyser
Compares consecutive perks-snapshot rows per Steam ID and emits a SkillProgressionAnomalyProblem for any single skill whose level gained more than THRESHOLD_DELTA between two snapshots. Login/Logout/LevelUp event rows are skipped via a perk-pair regex check on the bracketed event field. Threshold of 3 reflects PZ's slow leveling pace: typical session bridges should not produce four-or-more level jumps in a single skill. The constant is documented inline so operators can tune for modded XP servers without touching analysis logic. Synthetic fixture extended with a PlayerSuspect Steam ID carrying two snapshots: Strength jumps 2 -> 10 (delta +8, triggers), Fitness jumps 2 -> 8 (+6, triggers), Maintenance jumps 0 -> 3 (+3, exactly at threshold, does NOT trigger). The existing single-snapshot players remain noise-free.
This commit is contained in:
107
src/Analysis/ProjectZomboid/SkillProgressionAnomalyProblem.php
Normal file
107
src/Analysis/ProjectZomboid/SkillProgressionAnomalyProblem.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace IndifferentKetchup\Codex\Analysis\ProjectZomboid;
|
||||
|
||||
use IndifferentKetchup\Codex\Analysis\InsightInterface;
|
||||
use IndifferentKetchup\Codex\Analysis\Problem;
|
||||
|
||||
/**
|
||||
* Problem emitted by SkillProgressionAnomalyAnalyser when a single skill
|
||||
* gained more than the configured threshold between two consecutive
|
||||
* snapshots of the same player. Coalesced by (Steam ID, skill).
|
||||
*/
|
||||
class SkillProgressionAnomalyProblem extends Problem
|
||||
{
|
||||
private string $steamId = '';
|
||||
private string $player = '';
|
||||
private string $skill = '';
|
||||
private int $fromLevel = 0;
|
||||
private int $toLevel = 0;
|
||||
private int $delta = 0;
|
||||
|
||||
public function setSteamId(string $steamId): static
|
||||
{
|
||||
$this->steamId = $steamId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPlayer(string $player): static
|
||||
{
|
||||
$this->player = $player;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSkill(string $skill): static
|
||||
{
|
||||
$this->skill = $skill;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFromLevel(int $level): static
|
||||
{
|
||||
$this->fromLevel = $level;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setToLevel(int $level): static
|
||||
{
|
||||
$this->toLevel = $level;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDelta(int $delta): static
|
||||
{
|
||||
$this->delta = $delta;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSteamId(): string
|
||||
{
|
||||
return $this->steamId;
|
||||
}
|
||||
|
||||
public function getPlayer(): string
|
||||
{
|
||||
return $this->player;
|
||||
}
|
||||
|
||||
public function getSkill(): string
|
||||
{
|
||||
return $this->skill;
|
||||
}
|
||||
|
||||
public function getFromLevel(): int
|
||||
{
|
||||
return $this->fromLevel;
|
||||
}
|
||||
|
||||
public function getToLevel(): int
|
||||
{
|
||||
return $this->toLevel;
|
||||
}
|
||||
|
||||
public function getDelta(): int
|
||||
{
|
||||
return $this->delta;
|
||||
}
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
return sprintf(
|
||||
'Player %s (%s) gained %d levels of %s between snapshots (%d to %d).',
|
||||
$this->player,
|
||||
$this->steamId,
|
||||
$this->delta,
|
||||
$this->skill,
|
||||
$this->fromLevel,
|
||||
$this->toLevel
|
||||
);
|
||||
}
|
||||
|
||||
public function isEqual(InsightInterface $insight): bool
|
||||
{
|
||||
return $insight instanceof self
|
||||
&& $insight->getSteamId() === $this->steamId
|
||||
&& $insight->getSkill() === $this->skill;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user