docs: backfill Phase B.2 spec and plan

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.
This commit is contained in:
2026-05-01 12:51:26 +00:00
parent 38fa1471ba
commit b99d8f3061
2 changed files with 222 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
# ProjectZomboid Phase B.2 Analysers — As-Built Plan
> Retroactive: written 2026-05-01.
This document is a historical record of how Phase B.2 (PvP combat detection + admin verb dispatch) was implemented. The corresponding design spec is `docs/superpowers/specs/2026-04-30-pz-analysers-pvp-admin-design.md`. The work is complete and merged to `master`; checkboxes are pre-checked.
**Goal:** Land seven new `Information` insight classes (one for PvP combat, six for admin verbs) under `src/Analysis/ProjectZomboid/`, plus seven new pattern constants on `PvpPattern` / `AdminPattern`, then wire `ProjectZomboidPvpLog` and `ProjectZomboidAdminLog` default analysers to register them.
**Architecture:** Vanilla `PatternAnalyser` configured with the new insight classes. No custom `Analyser` subclasses (deferred to Phase B.3). `Entry::__toString()` joins lines with `\n`, but B.2 logs are single-line per entry so multi-line behaviour doesn't apply here.
**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
- [x] Empty checkpoint commit: `df62da1 pre-phase-B.2 checkpoint`
### Task 1 — `PvpDamageInformation` + `PvpPattern::COMBAT_REAL`
- [x] Add `PvpPattern::COMBAT_REAL` constant (combat regex with negative lookahead on weapon and positive-non-zero damage clause)
- [x] Add `src/Analysis/ProjectZomboid/PvpDamageInformation.php`
- [x] Add `test/tests/Games/ProjectZomboid/Analysis/PvpDamageInformationTest.php` covering pattern shape, match extraction, and three rejection cases (zombie weapon, zero damage, negative damage)
- [x] `composer test` green: 167 tests, 343 assertions
- [x] Commit: `55f769c Add PvpDamageInformation insight`
### Task 2 — `AdminAddedItemInformation` + `AdminPattern::ADDED_ITEM_ENTRY`
- [x] Add `AdminPattern::ADDED_ITEM_ENTRY` constant (entry-anchored variant; the body-only `ADDED_ITEM` from Phase A stays in place)
- [x] Add `src/Analysis/ProjectZomboid/AdminAddedItemInformation.php`
- [x] Add `test/tests/Games/ProjectZomboid/Analysis/AdminAddedItemInformationTest.php`
- [x] Commit: `90c85a0 Add AdminAddedItemInformation insight`**see Deviations section below**
- [x] Forward-fix: `0d85a05 Fix missing closing brace in AdminPattern`
- [x] `composer test` green after forward-fix: 170 tests
### Task 3 — `AdminAddedXpInformation` + `ADDED_XP_ENTRY`
- [x] Add `AdminPattern::ADDED_XP_ENTRY` constant
- [x] Add `src/Analysis/ProjectZomboid/AdminAddedXpInformation.php`
- [x] Unit test
- [x] `composer test` green: 173 tests
- [x] Commit: `a2faa55 Add AdminAddedXpInformation insight`
### Task 4 — `AdminGrantedAccessInformation` + `GRANTED_ACCESS_ENTRY`
- [x] Add `AdminPattern::GRANTED_ACCESS_ENTRY` constant
- [x] Add `src/Analysis/ProjectZomboid/AdminGrantedAccessInformation.php`
- [x] Unit test
- [x] `composer test` green: 175 tests
- [x] Commit: `caed04d Add AdminGrantedAccessInformation insight`
### Task 5 — `AdminChangedOptionInformation` + `CHANGED_OPTION_ENTRY`
- [x] Add `AdminPattern::CHANGED_OPTION_ENTRY` constant
- [x] Add `src/Analysis/ProjectZomboid/AdminChangedOptionInformation.php`
- [x] Unit test
- [x] `composer test` green: 177 tests
- [x] Commit: `b7b89ef Add AdminChangedOptionInformation insight`
### Task 6 — `AdminReloadedOptionsInformation` + `RELOADED_OPTIONS_ENTRY`
- [x] Add `AdminPattern::RELOADED_OPTIONS_ENTRY` constant
- [x] Add `src/Analysis/ProjectZomboid/AdminReloadedOptionsInformation.php`
- [x] Unit test
- [x] `composer test` green: 179 tests
- [x] Commit: `64641fa Add AdminReloadedOptionsInformation insight`
### Task 7 — `AdminTeleportedInformation` + `TELEPORTED_ENTRY`
- [x] Add `AdminPattern::TELEPORTED_ENTRY` constant (handles negative Z for basement coordinates)
- [x] Add `src/Analysis/ProjectZomboid/AdminTeleportedInformation.php`
- [x] Unit test (positive and negative Z cases)
- [x] `composer test` green: 182 tests
- [x] Commit: `d15fc81 Add AdminTeleportedInformation insight`
### Task 8 — Wire `ProjectZomboidPvpLog::getDefaultAnalyser()`
- [x] Replace `return new PatternAnalyser();` with `(new PatternAnalyser())->addPossibleInsightClass(PvpDamageInformation::class)`
- [x] Add `test/tests/Games/ProjectZomboid/Analyser/PvpLogAnalysisTest.php` — asserts three real-PvP insights (Bare Hands, Tire Iron, Hunting Knife) and zero zombie/vehicle insights
- [x] `composer test` green: 184 tests
- [x] Commit: `51eb2de Wire ProjectZomboidPvpLog default analyser`
### Task 9 — Wire `ProjectZomboidAdminLog::getDefaultAnalyser()`
- [x] Register all six `Admin<Verb>Information` classes
- [x] Add `test/tests/Games/ProjectZomboid/Analyser/AdminLogAnalysisTest.php` — asserts the 2+2+2+2+1+2 distribution and confirms the duplicate ShotgunShells row coalesces with `counter == 2`
- [x] `composer test` green: 186 tests
- [x] Commit: `c57d646 Wire ProjectZomboidAdminLog default analyser`
---
## Deviations from the original plan
### The `90c85a0` brace-fix interlude
Task 2's commit (`90c85a0 Add AdminAddedItemInformation insight`) shipped broken. While adding the first `_ENTRY` constant to `AdminPattern.php`, the `Edit` tool's `old_string` was `<TELEPORTED line>\n}` and the `new_string` included a docblock plus the new constant but **dropped the closing brace** of the class body. The commit was made before the test result was inspected, so it landed with a `ParseError: Unclosed '{'` and 9 cascading test errors.
Forward-fix `0d85a05 Fix missing closing brace in AdminPattern` restored the brace as a separate commit (per the `CLAUDE.md` workflow rule: "Always create new commits rather than amending"). The broken intermediate commit remains in history; force-pushing master to clean it would have cost more than the cosmetic gain.
The remaining five admin commits (Tasks 37) used a deliberate practice change: every subsequent `Edit` to `AdminPattern.php` included the closing `}` in both `old_string` and `new_string` so it couldn't be dropped again. No further breakage.
### Total commit count
11 commits vs the 10 originally outlined in the spec's planning section. The extra commit is the brace-fix.
### Test-count divergence note (now resolved)
When Phase B.1's plan was written I projected a final count of 158 tests for B.1; the actual landed count was 161 (off by 3 — Task 5's contribution wasn't summed in the plan footer). For B.2 the planned and actual per-step counts match exactly. No projection error this phase.
---
## Done condition (met)
After Task 9, `composer test` reports **186 tests, 387 assertions, all green** under PHPUnit 12.5.6 / PHP 8.5.5 (verified via the `composer:latest` Docker image). All five originally-planned analysers from the Step D Phase B scope (B.1's three plus B.2's two) are now operational on their respective Log subclasses.

View File

@@ -0,0 +1,106 @@
# ProjectZomboid analyser design (Phase B.2)
> Retroactive: written 2026-05-01.
## Summary
Add Project Zomboid PvP combat detection (filtering zombie hits and zero-damage events) and admin verb-dispatch coverage of six action types, by registering seven new `Information` insight classes onto the existing `PatternAnalyser`. No custom `Analyser` subclasses are introduced in this phase — all dispatch fits within `PatternAnalyser`'s per-entry pattern matching.
This document covers Phase B.2. Phase B.1 is in `2026-04-30-pz-analysers-design.md`. Phase B.3 (cross-entry / threshold analysers requiring custom `Analyser` subclasses) is in `2026-04-30-pz-analysers-deferred-design.md`.
## Scope
- **In scope:** `PvpDamageInformation` + `PvpPattern::COMBAT_REAL` regex; six `Admin<Verb>Information` classes + six `AdminPattern::<VERB>_ENTRY` regex constants; wiring `ProjectZomboidPvpLog::getDefaultAnalyser()` and `ProjectZomboidAdminLog::getDefaultAnalyser()`; end-to-end tests for both logs.
- **Out of scope (B.2):** any cross-entry / threshold / pairing logic (deferred to B.3); the eight other PZ logs whose `getDefaultAnalyser()` continues returning an empty `PatternAnalyser` stub; the codex-side `Redactor` utility (deferred — see `2026-04-30-redactor-design.md`).
## Architectural decision: vanilla PatternAnalyser
Phase B.1 established that `PatternAnalyser` plus `Insight::isEqual()` coalescing covers single-entry pattern matching cleanly. Phase B.2's analysers (PvP damage rows, admin verb lines) all fit that mould — each interesting line is independent of the others, dispatch is per-entry, and counter-coalescing handles repeats. No `Analyser` subclassing required. (Phase B.3 will deviate from this when cross-entry logic enters the picture.)
## Components
All under `src/Analysis/ProjectZomboid/`:
| Class | Type | Pattern | Coalescing |
|---|---|---|---|
| `PvpDamageInformation` | Information | `PvpPattern::COMBAT_REAL` | Default `Information::isEqual` (label + value) — same attacker/victim/weapon coalesces |
| `AdminAddedItemInformation` | Information | `AdminPattern::ADDED_ITEM_ENTRY` | Default — same admin/item/target coalesces |
| `AdminAddedXpInformation` | Information | `AdminPattern::ADDED_XP_ENTRY` | Default — same admin/amount/skill/target coalesces |
| `AdminGrantedAccessInformation` | Information | `AdminPattern::GRANTED_ACCESS_ENTRY` | Default — same admin/level/target coalesces |
| `AdminChangedOptionInformation` | Information | `AdminPattern::CHANGED_OPTION_ENTRY` | Default — same admin/option/value coalesces |
| `AdminReloadedOptionsInformation` | Information | `AdminPattern::RELOADED_OPTIONS_ENTRY` | Default — same admin coalesces |
| `AdminTeleportedInformation` | Information | `AdminPattern::TELEPORTED_ENTRY` | Default — same admin/target/coords coalesces |
## Patterns
Seven new constants total.
**`PvpPattern::COMBAT_REAL`** — combat regex with the noise filter baked in. The negative lookahead `(?!zombie")` rejects zombie weapon rows; the damage clause uses alternation to match only positive non-zero floats:
```
'/Combat: "(?<attacker>[^"]+)" \([^)]+\) hit "(?<victim>[^"]+)" \([^)]+\) weapon="(?<weapon>(?!zombie")[^"]+)" damage=(?<damage>0\.0*[1-9][0-9]*|[1-9][0-9]*\.[0-9]+)/'
```
The damage alternation explicitly rejects `0.000000` and any leading-minus value because both branches require either `0.<non-zero>` or `<non-zero>.<digits>`.
**`AdminPattern::<VERB>_ENTRY`** — six entry-anchored variants of the existing body-only verb constants. Necessary because `PatternAnalyser` calls `preg_match_all` against the full Entry text (including the `[time]` prefix), so the Phase A verb constants anchored at `^<admin>` would never match. The Phase A constants stay intact for direct-message use; new ones live alongside them on the same `AdminPattern` class.
## Wiring
Two `getDefaultAnalyser()` overrides (was `return new PatternAnalyser();` for both):
```php
// ProjectZomboidPvpLog
return (new PatternAnalyser())
->addPossibleInsightClass(PvpDamageInformation::class);
```
```php
// ProjectZomboidAdminLog
return (new PatternAnalyser())
->addPossibleInsightClass(AdminAddedItemInformation::class)
->addPossibleInsightClass(AdminAddedXpInformation::class)
->addPossibleInsightClass(AdminGrantedAccessInformation::class)
->addPossibleInsightClass(AdminChangedOptionInformation::class)
->addPossibleInsightClass(AdminReloadedOptionsInformation::class)
->addPossibleInsightClass(AdminTeleportedInformation::class);
```
## Test plan
Unit tests under `test/tests/Games/ProjectZomboid/Analysis/`, one per Insight class — exercises `getPatterns()` shape, `setMatches()` extraction, and at least one filter-rejection case for `PvpDamageInformation` (zombie weapon and zero-damage rejection).
End-to-end tests under `test/tests/Games/ProjectZomboid/Analyser/`:
- `PvpLogAnalysisTest` against `pvp-minimal.txt`: asserts exactly three `PvpDamageInformation` insights (Bare Hands, Tire Iron (Worn), Hunting Knife). Zombie and vehicle rows must be filtered out by the regex.
- `AdminLogAnalysisTest` against `admin-minimal.txt`: asserts 2 + 2 + 2 + 2 + 1 + 2 = 11 insights across the six admin classes, with the duplicate ShotgunShells row coalescing into a single insight at `counter == 2`.
## Fixture changes
None. The Phase A synthetic fixtures `pvp-minimal.txt` and `admin-minimal.txt` already cover every code path Phase B.2 exercises.
## Commits (as-built, in order)
1. `df62da1``pre-phase-B.2 checkpoint` (`--allow-empty`)
2. `55f769c``Add PvpDamageInformation insight`
3. `90c85a0``Add AdminAddedItemInformation insight` ⚠️ broken — see `2026-04-30-pz-analysers-pvp-admin.md` §Deviations
4. `0d85a05``Fix missing closing brace in AdminPattern` (forward-fix for #3)
5. `a2faa55``Add AdminAddedXpInformation insight`
6. `caed04d``Add AdminGrantedAccessInformation insight`
7. `b7b89ef``Add AdminChangedOptionInformation insight`
8. `64641fa``Add AdminReloadedOptionsInformation insight`
9. `d15fc81``Add AdminTeleportedInformation insight`
10. `51eb2de``Wire ProjectZomboidPvpLog default analyser`
11. `c57d646``Wire ProjectZomboidAdminLog default analyser`
11 commits total, vs 10 originally planned. The brace-fix commit accounts for the discrepancy.
## Open issues
None blocking. Phase A Q4 (admin verb scope) was settled before B.2 began. Phase B Q2 confirmed PvP fixtures contain real combat events worth analysing.
## Pointers
- Phase B.1 (foundation): `2026-04-30-pz-analysers-design.md` and `2026-04-30-pz-analysers.md`.
- Phase B.3 (deferred analysers requiring custom `Analyser` subclasses): `2026-04-30-pz-analysers-deferred-design.md`.
- Workflow conventions: `CLAUDE.md` § Workflow conventions and § Pitfalls.