Retroactive design + plan documentation for Phase B.3 (deferred analysers requiring custom Analyser subclasses for cross-entry and threshold logic). Records the architectural shift away from vanilla PatternAnalyser, the threshold constant rationale (event-pairing / sliding-window / consecutive-snapshot deltas), and the synthetic fixture extensions that exercise both trigger and non-trigger paths. Plan is as-built with checkboxes pre-checked and SHAs referenced.
6.8 KiB
ProjectZomboid Phase B.3 Deferred Analysers — As-Built Plan
Retroactive: written 2026-05-01.
This document is a historical record of how Phase B.3 (the three deferred analysers from the original Step D candidate list) was implemented. The corresponding design spec is docs/superpowers/specs/2026-04-30-pz-analysers-deferred-design.md. The work is complete and merged to master; checkboxes are pre-checked.
Goal: Land three custom Analyser subclasses under src/Analyser/ProjectZomboid/ (the first non-empty contents of that directory), three Problem subclasses under src/Analysis/ProjectZomboid/, threshold constants documented inline as public const, fixture extensions to exercise trigger and non-trigger paths, and e2e tests verifying the analysers' behaviour against the fixtures.
Architecture: Custom subclasses of the framework's abstract Analyser. Each overrides analyse() to walk $this->log once, aggregate cross-entry state, and emit coalesced Problem insights at the end. This is the first deviation from Phase B.1/B.2's vanilla-PatternAnalyser pattern; the reasoning is recorded in the design spec and in CLAUDE.md.
Tech Stack: PHP 8.4+, PHPUnit 12, Composer (root package: indifferentketchup/codex). PHP/Composer not installed on host — all command invocations wrap in docker run --rm -v "$(pwd):/app" -w /app -u "$(id -u):$(id -g)" composer:latest ….
Tasks
Task 0 — Pre-checkpoint
- Empty checkpoint commit:
c444e85 pre-phase-B.3 checkpoint
Task 1 — ConnectionFailureAnalyser (UserLog)
Pairing logic: walk the log, count attempting to join and allowed to join events per Steam ID, emit a ConnectionFailureProblem for any Steam ID whose attempt count exceeds its allowed count.
- Add
src/Analysis/ProjectZomboid/ConnectionFailureProblem.php(Steam ID, player, unmatched count;isEqualcoalesces by Steam ID) - Add
src/Analyser/ProjectZomboid/ConnectionFailureAnalyser.php— first file in this directory; the.gitkeepplaceholder is removed in this commit - Wire
ProjectZomboidUserLog::getDefaultAnalyser()to returnnew ConnectionFailureAnalyser()and drop the now-unusedPatternAnalyserimport - Add
test/tests/Games/ProjectZomboid/Analyser/UserLogAnalysisTest.php— asserts Player1 (76561198000000001) is flagged withunmatchedAttempts == 1and Player2 (76561198000000002) is not flagged composer testgreen: 188 tests, 392 assertions- Commit:
73e9ca6 Add ConnectionFailureAnalyser
Design note inside the analyser docblock: "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. Tunable in v2 if false positives become noisy.
Task 2 — ItemDuplicationAnalyser (ItemLog)
Sliding-window heuristic over (steamid, item) groups, restricted to positive-delta events. Negative-delta rows (drops/transfers) are filtered out.
- Add
src/Analysis/ProjectZomboid/ItemDuplicationProblem.php(Steam ID, player, item, event count;isEqualcoalesces by(steamid, item)) - Add
src/Analyser/ProjectZomboid/ItemDuplicationAnalyser.phpwith two threshold constants and rationale docblocks:THRESHOLD_COUNT = 5,THRESHOLD_WINDOW_SECONDS = 10 - Wire
ProjectZomboidItemLog::getDefaultAnalyser()to returnnew ItemDuplicationAnalyser(); drop unusedPatternAnalyserimport - Extend
test/src/Games/ProjectZomboid/fixtures/item-minimal.txt: append 6 Bullets9mm events at sub-second timestamps19:50:00.001–.006for AdminUser (trigger), plus 4 Plank events scattered20:00:00–20:03:00for Player1 (sub-threshold) - Bump entry-count assertion in
ProjectZomboidItemLogTest::testParsesEachLineAsAnEntry: 10 → 20 - Add
test/tests/Games/ProjectZomboid/Analyser/ItemLogAnalysisTest.php— asserts oneItemDuplicationProblem(AdminUser + Bullets9mm + 6 events), zero for the Plank group, and the threshold constants are positive composer testgreen: 191 tests, 400 assertions- Commit:
ba3fae8 Add ItemDuplicationAnalyser
Implementation note: the analyser uses a two-pointer sliding window per group, which is O(n) per group after the initial sort. Entry::getTime() returns integer Unix seconds (sub-second precision dropped); the burst events all collapse to the same Unix-second value so any positive window catches them.
Task 3 — SkillProgressionAnomalyAnalyser (PerkLog)
Compare consecutive perks-snapshot rows per Steam ID; emit a problem for any single skill that gained more than THRESHOLD_DELTA levels between snapshots.
- Add
src/Analysis/ProjectZomboid/SkillProgressionAnomalyProblem.php(Steam ID, player, skill, fromLevel, toLevel, delta;isEqualcoalesces by(steamid, skill)) - Add
src/Analyser/ProjectZomboid/SkillProgressionAnomalyAnalyser.phpwithTHRESHOLD_DELTA = 3and a rationale docblock about PZ's slow skill leveling - Wire
ProjectZomboidPerkLog::getDefaultAnalyser()to returnnew SkillProgressionAnomalyAnalyser(); drop unusedPatternAnalyserimport - Extend
test/src/Games/ProjectZomboid/fixtures/perk-minimal.txt: append PlayerSuspect (Steam ID76561198000000004) with two snapshots — Strength 2→10 (+8 trigger), Fitness 2→8 (+6 trigger), Maintenance 0→3 (+3 boundary, does not trigger because comparison is strict>) - Bump entry-count assertion in
ProjectZomboidPerkLogTest::testParsesEachLineAsAnEntry: 6 → 10 - Add
test/tests/Games/ProjectZomboid/Analyser/PerkLogAnalysisTest.php— asserts exactly two problems for PlayerSuspect (Strength + Fitness, sorted), no problem for Maintenance, no problems for single-snapshot Player1/Player2, and the threshold constant is positive composer testgreen: 195 tests, 412 assertions- Commit:
0c90e40 Add SkillProgressionAnomalyAnalyser
Filtering note: the analyser skips event-token rows (Login, Logout, LevelUp) by checking that the bracketed event field contains a Skill=N pair via PerkPattern::PERK_PAIR. Only true perks-snapshot rows enter the comparison.
Done condition (met)
After Task 3, composer test reports 195 tests, 412 assertions, all green under PHPUnit 12.5.6 / PHP 8.5.5. All eight Step D candidate analysers (Phase B.1's three ServerLog + Phase B.2's seven PvP/Admin + Phase B.3's three deferred) are operational across their respective Log subclasses.
The directory src/Analyser/ProjectZomboid/ now contains real code for the first time; its .gitkeep placeholder was removed in 73e9ca6.
Deviations from the original plan
None this phase. The 4-commit count and the per-analyser shape both match what was committed-to in chat before execution. No silent breakages, no missing closing braces. The only observation worth recording is that the planned commit count was inclusive of the pre-checkpoint, and the actual commit ordering matched the plan exactly.