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:
@@ -4,3 +4,7 @@
|
||||
[16-04-26 18:29:15.823] [76561198000000002][Player2][1010,2010,0][Cooking=2, Fitness=10, Strength=10, Blunt=10, Axe=0, Lightfoot=4, Nimble=4, Sprinting=7, Sneak=2, Woodwork=6, Aiming=5, Reloading=4, Farming=0, Fishing=0, Trapping=0, PlantScavenging=0, Doctor=2, Electricity=5, Blacksmith=8, MetalWelding=10, Mechanics=10, Spear=0, Maintenance=7, SmallBlade=0, LongBlade=0, SmallBlunt=0, Tailoring=0, Tracking=0, Husbandry=0, FlintKnapping=0, Masonry=0, Pottery=0, Carving=0, Butchering=0, Glassmaking=0, Side_L=0, Side_R=0, ProstFamiliarity=0][Hours Survived: 50].
|
||||
[16-04-26 18:30:02.500] [76561198000000003][AdminUser][1020,2020,0][Logout][Hours Survived: 75].
|
||||
[16-04-26 19:15:00.000] [76561198000000001][Player1][1003,2003,1][LevelUp][Hours Survived: 101].
|
||||
[16-04-26 18:30:00.000] [76561198000000004][PlayerSuspect][1030,2030,0][Login][Hours Survived: 10].
|
||||
[16-04-26 18:30:00.000] [76561198000000004][PlayerSuspect][1030,2030,0][Cooking=2, Fitness=2, Strength=2, Blunt=0, Axe=0, Lightfoot=0, Nimble=0, Sprinting=0, Sneak=0, Woodwork=0, Aiming=0, Reloading=0, Farming=0, Fishing=0, Trapping=0, PlantScavenging=0, Doctor=0, Electricity=0, Blacksmith=0, MetalWelding=0, Mechanics=0, Spear=0, Maintenance=0, SmallBlade=0, LongBlade=0, SmallBlunt=0, Tailoring=0, Tracking=0, Husbandry=0, FlintKnapping=0, Masonry=0, Pottery=0, Carving=0, Butchering=0, Glassmaking=0, Side_L=0, Side_R=0, ProstFamiliarity=0][Hours Survived: 10].
|
||||
[16-04-26 22:00:00.000] [76561198000000004][PlayerSuspect][1031,2031,0][Login][Hours Survived: 12].
|
||||
[16-04-26 22:00:00.000] [76561198000000004][PlayerSuspect][1031,2031,0][Cooking=2, Fitness=8, Strength=10, Blunt=0, Axe=0, Lightfoot=0, Nimble=0, Sprinting=0, Sneak=0, Woodwork=0, Aiming=0, Reloading=0, Farming=0, Fishing=0, Trapping=0, PlantScavenging=0, Doctor=0, Electricity=0, Blacksmith=0, MetalWelding=0, Mechanics=0, Spear=0, Maintenance=3, SmallBlade=0, LongBlade=0, SmallBlunt=0, Tailoring=0, Tracking=0, Husbandry=0, FlintKnapping=0, Masonry=0, Pottery=0, Carving=0, Butchering=0, Glassmaking=0, Side_L=0, Side_R=0, ProstFamiliarity=0][Hours Survived: 12].
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace IndifferentKetchup\Codex\Test\Tests\Games\ProjectZomboid\Analyser;
|
||||
|
||||
use IndifferentKetchup\Codex\Analyser\ProjectZomboid\SkillProgressionAnomalyAnalyser;
|
||||
use IndifferentKetchup\Codex\Analysis\ProjectZomboid\SkillProgressionAnomalyProblem;
|
||||
use IndifferentKetchup\Codex\Log\File\PathLogFile;
|
||||
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidPerkLog;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PerkLogAnalysisTest extends TestCase
|
||||
{
|
||||
private function fixturePath(): string
|
||||
{
|
||||
return __DIR__ . '/../../../../src/Games/ProjectZomboid/fixtures/perk-minimal.txt';
|
||||
}
|
||||
|
||||
public function testFlagsSkillsThatExceedDeltaThreshold(): void
|
||||
{
|
||||
$log = (new ProjectZomboidPerkLog())->setLogFile(new PathLogFile($this->fixturePath()));
|
||||
$log->parse();
|
||||
$analysis = $log->analyse();
|
||||
|
||||
$problems = $analysis->getFilteredInsights(SkillProgressionAnomalyProblem::class);
|
||||
|
||||
$skills = array_map(fn($p) => $p->getSkill(), $problems);
|
||||
sort($skills);
|
||||
|
||||
$this->assertSame(['Fitness', 'Strength'], $skills);
|
||||
|
||||
foreach ($problems as $problem) {
|
||||
$this->assertSame('76561198000000004', $problem->getSteamId());
|
||||
$this->assertSame('PlayerSuspect', $problem->getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
public function testDeltaAtThresholdDoesNotTrigger(): void
|
||||
{
|
||||
$log = (new ProjectZomboidPerkLog())->setLogFile(new PathLogFile($this->fixturePath()));
|
||||
$log->parse();
|
||||
$analysis = $log->analyse();
|
||||
|
||||
$problems = $analysis->getFilteredInsights(SkillProgressionAnomalyProblem::class);
|
||||
foreach ($problems as $problem) {
|
||||
$this->assertNotSame('Maintenance', $problem->getSkill());
|
||||
}
|
||||
}
|
||||
|
||||
public function testSinglePlayerWithOneSnapshotProducesNoProblem(): void
|
||||
{
|
||||
$log = (new ProjectZomboidPerkLog())->setLogFile(new PathLogFile($this->fixturePath()));
|
||||
$log->parse();
|
||||
$analysis = $log->analyse();
|
||||
|
||||
$problems = $analysis->getFilteredInsights(SkillProgressionAnomalyProblem::class);
|
||||
foreach ($problems as $problem) {
|
||||
$this->assertNotSame('76561198000000001', $problem->getSteamId());
|
||||
$this->assertNotSame('76561198000000002', $problem->getSteamId());
|
||||
}
|
||||
}
|
||||
|
||||
public function testThresholdConstantIsDocumentedAndPositive(): void
|
||||
{
|
||||
$this->assertGreaterThan(0, SkillProgressionAnomalyAnalyser::THRESHOLD_DELTA);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class ProjectZomboidPerkLogTest extends TestCase
|
||||
$log = (new ProjectZomboidPerkLog())->setLogFile(new PathLogFile($this->fixturePath()));
|
||||
$log->parse();
|
||||
|
||||
$this->assertCount(6, $log->getEntries());
|
||||
$this->assertCount(10, $log->getEntries());
|
||||
}
|
||||
|
||||
public function testFieldsRegexHandlesEventRow(): void
|
||||
|
||||
Reference in New Issue
Block a user