Retroactive design + plan documentation for Phase B.2 (PvP combat
detection plus six admin verb-dispatch insight classes), reconstructed
from chat history and git log. Mirrors the shape of the existing
Phase B.1 docs. Plan is as-built with checkboxes pre-checked and
commit SHAs referenced inline; Deviations section captures the
90c85a0 brace-fix interlude.
Replaces the four-line stub with a usable landing page: install line,
end-to-end PHP example showing how a caller goes from a log file to
analysed insights, sample (placeholder-laden) output, a one-diagram
architecture summary, and a per-game support table. Sends interested
readers to CLAUDE.md for the extension guide and developer setup so
this file stays focused on consumers.
The example uses Project Zomboid because that is the in-tree reference
implementation. Output is illustrated with placeholder identifiers
(<hash>, <mod_id>, <missing>) rather than copied real-log content.
The framework architecture section claimed PatternAnalyser was the
sole analysis surface; Phase B.3 introduced three custom Analyser
subclasses (ConnectionFailureAnalyser, ItemDuplicationAnalyser,
SkillProgressionAnomalyAnalyser) for cross-entry and threshold logic
that PatternAnalyser cannot express. Add a new bullet explaining when
to extend Analyser directly, plus an enumeration of which Log subclass
returns which kind of analyser from getDefaultAnalyser().
Also bumps the ProjectZomboid summary line from "11 log subclasses,
11 pattern classes" to include the analyser surface (12 Insight
classes plus 3 Analyser subclasses).
GitHub deprecated the ::set-output workflow command in 2022 and the
runners now emit warnings on every CI run. Switch to writing the
'name=value' line into the file pointed to by \$GITHUB_OUTPUT, which is
the documented modern equivalent. The downstream cache step already
references steps.composer-cache.outputs.dir, no other change needed.
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.
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.
First custom Analyser subclass in this game tree. PatternAnalyser
operates per-entry without cross-entry state, so pairing
'attempting to join' with 'allowed to join' per Steam ID requires a
bespoke pass over the log. The analyser counts attempts and allowed
events per Steam ID and emits a ConnectionFailureProblem for each
player whose attempt count exceeds their allowed count. Unmatched
'attempting to join used queue' rows are surfaced as failures in v1
because a long queue wait is indistinguishable from a real failure
without timing context.
The previous commit's Edit replaced the TELEPORTED constant including its
trailing closing brace and forgot to add the brace back. Tests went red
with a ParseError. Restoring the brace.
Captures the decision to forgo custom Analyser subclasses (the framework's
PatternAnalyser already handles multi-line entry text via Entry::__toString),
the five Insight components under src/Analysis/ProjectZomboid/, the new
DebugServerPattern::EXCEPTION constant for header+stack-body capture, the
PatternAnalyser configuration that ServerLog::getDefaultAnalyser will return,
the per-class coalescing semantics, and the test plan that drives the existing
synthetic debug-server-minimal.txt fixture end-to-end.
Captures the framework architecture, the docker-based PHP/Composer
runtime, the components-outer game subtree convention, the
PatternParser-and-named-groups gotcha, the workflow conventions that
emerged during the rename and ProjectZomboid build-out, and the
synthetic-only fixture rule for committed test data. Adds .claude/ and
.claude.local.md to .gitignore so Claude session state and personal
overrides don't ride along.
Constructor pre-registers every concrete ProjectZomboid Log subclass so
that detect() can dispatch on filename hint plus content signature.
Data-provider test verifies each of the eleven synthetic fixtures
resolves to its expected Log class via the public Detective surface.
Per-line warnings emitted by the BurdJournals mod, format
'[time] [BurdJournals] LEVEL: message.'. Parser captures time, the
[BurdJournals] tag as the entry prefix, and the LEVEL token. Detectors:
filename match plus content signature on the literal '[BurdJournals]'
tag bracket.
Two row variants: low-level Connection events and player join/disconnect
events. The LINE regex accepts both shapes; analysers route via
CONNECTION (action/index/guid/id) and PLAYER_EVENT (steamid/player/event)
named-group regexes. Detectors: filename match plus content signatures
on either variant.
Free-form English message body with verb-dispatched analyser regexes
(ADDED_ITEM, ADDED_XP, GRANTED_ACCESS, CHANGED_OPTION,
RELOADED_OPTIONS, TELEPORTED). Parser captures only the timestamp,
since the admin name itself can include parentheses or whitespace.
Detectors: filename match plus content signatures on
'added item Base.X in Y's inventory' and 'granted ROLE access level on'.
Two row variants share the file: Safe House toggles ([LOG] Safety:)
and Combat events ([INFO] Combat: ... weapon=... damage=...). Parser
captures time, level, and the subsystem token (Safety|Combat) as the
entry prefix. COMBAT and SAFETY regexes extract structured fields,
including support for negative Z coordinates from basement levels.
Synthetic fixture covers both variants and represents zombie/vehicle/
real-PvP weapon types so analysers can later filter on damage>0 and
weapon!=zombie.
Per-line skill snapshot log; each Login event is paired with a perks
row containing comma-separated Skill=N tokens. PERK_PAIR regex extracts
each pair via preg_match_all for analyser use. Detectors: filename
match plus content signature on the unique '[Cooking=N, Fitness=N,
Strength=N,' prefix of the perks-row bracket.
Per-line world object placement/removal log. LINE pattern handles both
integer and floating-point coordinates and both 'Base.X' and
'IsoObject (X)' object encodings. Detectors: filename match plus content
signature on the added/removed verbs paired with Base./IsoObject prefix.
Per-line item gain/loss event log. Parser captures only the timestamp;
analysers decompose location/delta/item via ItemPattern::FIELDS.
Detectors: filename match plus content signature on the
container/floor/inventory location verbs paired with a signed delta.
Per-line client RPC trace. Parser captures only the timestamp;
analysers decompose steamid/player/command/coords via CmdPattern::FIELDS.
Detectors: filename match plus content signature on the
'steamid "name" command @ x,y,z' line shape.
Strict 5-field bracketed format. Parser captures only the timestamp;
analysers that want steamid/action/player/coords/param decompose the
Line text via ClientActionPattern::FIELDS. Detectors: filename match
plus content signature on the IS{Enter,Exit}Vehicle / ISWalkToTimedAction
action tokens.
Handles both chat-engine events (bracketed level prefix) and bare
server-alert lines via an optional level group. Detectors: filename
match on _chat.txt plus content signatures for ChatMessage{...} log
entries and the chat-server initialization banner. CHAT_MESSAGE and
SERVER_ALERT named-group regexes ride along on ChatPattern for analyser
extraction in phase B.
Concrete Log subclass for the engine debug log. Captures time, level,
and subsystem prefix per entry; stack-trace continuation lines attach
to the triggering ERROR entry via PatternParser's append-on-no-match
behaviour. Detectors: filename match on DebugLog-server.txt plus two
content signatures (the version=X.Y.Z+hash banner and the level/
subsystem/f/t/st header shape). Pattern constants live in
src/Pattern/ProjectZomboid/DebugServerPattern.php with named groups
ready for analyser use in phase B. Synthetic fixture under
test/src/Games/ProjectZomboid/fixtures/ uses zeroed identifiers and
placeholder paths.
ProjectZomboidLog (abstract): extends AnalysableLog, implements
DetectableLogInterface. Centralises the PZ timestamp format
('d-m-y H:i:s.v') and UTC default timezone, plus a makePatternParser()
helper so concrete subclasses only specify their line regex and capture
group names. ProjectZomboidEventLog (abstract): marker base for the ten
single-line structured PZ logs, distinct from the multi-line
ProjectZomboidServerLog. Concrete subclasses follow.
New Detector that matches a configured regex against
LogFileInterface::getPath(). Returns a settable weight on match (default
0.95) and false otherwise, including when the log file has no known path
(StringLogFile, StreamLogFile). Lets game-specific Detectives prefer the
filename hint over content signatures when an upload's original name is
preserved.
Add LogFileInterface::getPath(): ?string so detectors can dispatch on a
filename hint when the original path is known. Default implementation on
the abstract LogFile base returns null; PathLogFile records its
constructor argument. StringLogFile and StreamLogFile inherit the null
default. Tests cover both the path and null-fallback cases.
Add empty per-component subdirectories under src/{Analyser,Log,Parser,Pattern}/SevenDaysToDie/
with .gitkeep markers, plus SevenDaysToDieDetective stub extending the base Detective with
a TODO body. Smoke test under test/tests/Games/SevenDaysToDie/ asserts the detective is
instantiable. Directory name is alphabetic because PHP class names cannot begin with a digit.
Add empty per-component subdirectories under src/{Analyser,Log,Parser,Pattern}/Hytale/
with .gitkeep markers, plus HytaleDetective stub extending the base Detective with
a TODO body. Smoke test under test/tests/Games/Hytale/ asserts the detective is
instantiable.
Add empty per-component subdirectories under src/{Analyser,Log,Parser,Pattern}/ProjectZomboid/
with .gitkeep markers, plus ProjectZomboidDetective stub extending the base Detective with
a TODO body. Smoke test under test/tests/Games/ProjectZomboid/ asserts the detective is
instantiable.
Add empty per-component subdirectories under src/{Analyser,Log,Parser,Pattern}/Minecraft/
with .gitkeep markers, plus MinecraftDetective stub extending the base Detective with
a TODO body. Smoke test under test/tests/Games/Minecraft/ asserts the detective is
instantiable. Introduces src/Pattern/ as a new top-level component directory.