Add ItemDuplicationAnalyser
Sliding-window heuristic over (Steam ID, item code) groups: any window of THRESHOLD_WINDOW_SECONDS containing THRESHOLD_COUNT or more positive-delta events for the same player/item pair triggers a Problem. Negative deltas (drops, transfers out) are filtered. Five events in ten seconds (defaults) encodes the rule of thumb that legitimate gameplay rarely produces five identical items in that span. Constants live as class constants on the analyser so operators can override via subclass without touching analysis logic; the docblocks record the justification. Synthetic fixture extended with a 6-event burst (AdminUser + Base.Bullets9mm in <1s) and a 4-event sub-threshold group (Player1 + Base.Plank scattered over 4 minutes) to exercise both paths.
This commit is contained in:
82
src/Analysis/ProjectZomboid/ItemDuplicationProblem.php
Normal file
82
src/Analysis/ProjectZomboid/ItemDuplicationProblem.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace IndifferentKetchup\Codex\Analysis\ProjectZomboid;
|
||||
|
||||
use IndifferentKetchup\Codex\Analysis\InsightInterface;
|
||||
use IndifferentKetchup\Codex\Analysis\Problem;
|
||||
|
||||
/**
|
||||
* Problem emitted by ItemDuplicationAnalyser when a player gains the same
|
||||
* item code at a rate that exceeds the configured threshold. Coalesced by
|
||||
* the (Steam ID, item code) tuple so each suspicious group produces one
|
||||
* problem regardless of how many events fall inside the window.
|
||||
*/
|
||||
class ItemDuplicationProblem extends Problem
|
||||
{
|
||||
private string $steamId = '';
|
||||
private string $player = '';
|
||||
private string $item = '';
|
||||
private int $eventCount = 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 setItem(string $item): static
|
||||
{
|
||||
$this->item = $item;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEventCount(int $count): static
|
||||
{
|
||||
$this->eventCount = $count;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSteamId(): string
|
||||
{
|
||||
return $this->steamId;
|
||||
}
|
||||
|
||||
public function getPlayer(): string
|
||||
{
|
||||
return $this->player;
|
||||
}
|
||||
|
||||
public function getItem(): string
|
||||
{
|
||||
return $this->item;
|
||||
}
|
||||
|
||||
public function getEventCount(): int
|
||||
{
|
||||
return $this->eventCount;
|
||||
}
|
||||
|
||||
public function getMessage(): string
|
||||
{
|
||||
return sprintf(
|
||||
'Player %s (%s) gained %s %d times at a rate above the duplication threshold.',
|
||||
$this->player,
|
||||
$this->steamId,
|
||||
$this->item,
|
||||
$this->eventCount
|
||||
);
|
||||
}
|
||||
|
||||
public function isEqual(InsightInterface $insight): bool
|
||||
{
|
||||
return $insight instanceof self
|
||||
&& $insight->getSteamId() === $this->steamId
|
||||
&& $insight->getItem() === $this->item;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user