Files
ik-codex/docs/superpowers/specs/2026-04-30-pz-analysers-design.md
indifferentketchup 1a443df662 Document Phase B.1 ServerLog analyser design
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.
2026-04-30 21:24:32 +00:00

122 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ProjectZomboid analyser design (Phase B.1)
## Summary
Implement the three top-priority ServerLog analysers — engine version, mod load order plus missing-mod problems, and server exception coalescing — by adding five Insight classes that plug into the framework's existing `PatternAnalyser`. Wire `ProjectZomboidServerLog::getDefaultAnalyser()` to return a configured `PatternAnalyser` carrying all four insight classes (`ModMissingSolution` is a Solution attached to `ModMissingProblem`, not a separately registered insight).
This document covers Phase B.1. Phase B.2 (PvpDamageAnalyser and AdminAuditAnalyser) ships separately and gets its own spec.
## Scope
- **In scope:** All work needed to make `(new ProjectZomboidServerLog())->setLogFile(path)->parse()->analyse()` return an `Analysis` populated with engine-version information, mod-load information, missing-mod problems with attached solutions, and server-exception problems coalesced by exception type.
- **Out of scope (B.1):** PvP damage, admin audit, codex-side redaction, custom Solution wording for `ServerExceptionProblem`, Hytale/Minecraft/SevenDaysToDie analysers, the empty `src/Analyser/ProjectZomboid/.gitkeep` placeholder.
## Architectural decision: no Analyser subclasses
The original Step-D plan called for a custom `ServerExceptionAnalyser` subclass to capture the tab-indented stack-trace lines that follow each `ERROR` header. On a closer reading of the framework, this is unnecessary:
- `Entry::__toString()` joins all of an entry's `Line`s with `\n`.
- `PatternAnalyser::analyseEntry()` calls `preg_match_all($pattern, $entry, ...)` against the stringified entry.
- A regex with the `s` flag captures across the embedded newlines and grabs the stack body in the same match.
The single `PatternAnalyser` instance configured with multiple insight classes covers all three analysers. No subclassing required.
## Components
All under `src/Analysis/ProjectZomboid/`:
| Class | Type | Purpose | Coalescing |
|---|---|---|---|
| `EngineVersionInformation` | Information | Capture `version=X.Y.Z <hash> <date> <time>` | Always equal (single engine version per file) |
| `ModLoadInformation` | Information | Capture each `loading <modId>` line | Equal when `mod` field matches |
| `ModMissingProblem` | Problem | Capture each `required mod "X" not found` warning; attach a `ModMissingSolution` | Equal when missing-mod name matches |
| `ModMissingSolution` | Solution | Pragmatic guidance ("Subscribe to the missing mod or remove its ID from the `Mods=` line in `serverconfig.ini`.") | n/a |
| `ServerExceptionProblem` | Problem | Capture exception header and the trailing tab-indented stack body in one match (multi-line regex with `s` flag) | Equal when exception-type string matches; first body wins, counter increments |
`ModMissingSolution` is constructed and attached inside `ModMissingProblem::setMatches()` so callers don't have to wire it manually.
`ServerExceptionProblem` overrides `isEqual()` to compare only the exception-type token. This deviates from the default `Information::isEqual` behaviour (label + value match) because the value field includes the (variable) stack body and we want different bodies of the same exception type to coalesce.
## Patterns
All Phase B.1 patterns live on `DebugServerPattern` (Phase A class). Existing constants reused as-is:
- `VERSION``/version=(?<version>\S+) (?<hash>[a-f0-9]{40}) (?<date>\d{4}-\d{2}-\d{2}) (?<time>\d{2}:\d{2}:\d{2})/`
- `MOD_LOAD``/loading (?<mod>[A-Za-z0-9_]+)\.?$/`
- `MOD_MISSING``/required mod "(?<mod>[^"]+)" not found/`
One new constant added:
- `EXCEPTION` — anchored at entry start, captures both the header line and the trailing tab-indented stack body in one match. Named groups: `type` (the FQCN of the thrown exception, parsed from the first body line) and `body` (zero-or-more additional indented stack frames).
```
'/^\[\d{2}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\][^\n]+Exception thrown\n\t(?<type>[A-Za-z0-9_.$]+(?:Exception|Error))[^\n]*(?<body>(?:\n\t.+)*)/'
```
Note `[^\n]+` and `[^\n]*` rather than `.+` keep the regex well-behaved without the `s` flag — each character class explicitly excludes newlines, and `(?:\n\t.+)*` walks the body line-by-line. `$` inside the type class allows nested-class names like `IsoPropertyType$IsoPropertyTypeNotFoundException`.
The existing `EXCEPTION_HEADER` constant stays for any caller that only needs the header line; `EXCEPTION` is the one `ServerExceptionProblem` registers in `getPatterns()`.
## Wiring
`ProjectZomboidServerLog::getDefaultAnalyser()` changes from:
```php
return new PatternAnalyser();
```
to:
```php
return (new PatternAnalyser())
->addPossibleInsightClass(EngineVersionInformation::class)
->addPossibleInsightClass(ModLoadInformation::class)
->addPossibleInsightClass(ModMissingProblem::class)
->addPossibleInsightClass(ServerExceptionProblem::class);
```
The other ten ProjectZomboid log subclasses keep their empty `new PatternAnalyser()` stubs until Phase B.2 (PvpLog, AdminLog) and beyond.
## Test plan
Unit tests under `test/tests/Games/ProjectZomboid/Analysis/` (one per Insight class):
- `EngineVersionInformationTest` — `getPatterns()` returns the expected regex; `setMatches` populates label/value; `getMessage` reads as `"Engine version: 42.16.3 (build 0000…0000)"` or similar concise form.
- `ModLoadInformationTest` — `setMatches` extracts `mod`; two instances with the same mod compare equal; two with different mods compare not-equal.
- `ModMissingProblemTest` — `setMatches` extracts the missing-mod name; the problem carries exactly one `ModMissingSolution`; isEqual coalesces same name.
- `ServerExceptionProblemTest` — `setMatches` extracts both type and body; isEqual returns true for same type with different bodies; isEqual returns false for different types.
End-to-end test under `test/tests/Games/ProjectZomboid/Analyser/`:
- `ServerLogAnalysisTest::testAnalyseProducesExpectedInsights` — feeds existing `debug-server-minimal.txt` through `(new ProjectZomboidServerLog())->setLogFile(...)->parse()->analyse()`. Asserts:
- 1× `EngineVersionInformation` (one version banner in fixture)
- 3× `ModLoadInformation` (alpha/beta/gamma)
- 1× `ModMissingProblem` (absent_mod) carrying 1× `ModMissingSolution`
- 2× `ServerExceptionProblem` (NoSuchFileException + IsoPropertyTypeNotFoundException, distinct types so no coalescing in this fixture)
## Fixture changes
None. The existing synthetic `test/src/Games/ProjectZomboid/fixtures/debug-server-minimal.txt` already contains exactly the lines required for the end-to-end test above. No new identifiers or coordinates introduced.
## Commits (planned)
Following CLAUDE.md workflow conventions (one logical concept per commit, run `composer test` between):
1. `Document Phase B.1 ServerLog analyser design` — this spec file under `docs/superpowers/specs/`.
2. `pre-phase-B checkpoint` — `git commit --allow-empty`.
3. `Add EngineVersionInformation insight` — Insight class + unit test.
4. `Add ModLoadInformation insight` — Insight class + unit test.
5. `Add ModMissingProblem and ModMissingSolution` — Problem + Solution paired (Solution belongs to Problem, ships together).
6. `Add ServerExceptionProblem insight` — includes the new `DebugServerPattern::EXCEPTION` constant; Problem + unit test.
7. `Wire ProjectZomboidServerLog default analyser + end-to-end test` — modifies `ProjectZomboidServerLog::getDefaultAnalyser`, adds `ServerLogAnalysisTest`.
Total: 7 commits expected (6 if the empty checkpoint produces no diff and we skip per the workflow rule — but it always produces a diff because it's `--allow-empty`).
## Open issues
None. All Phase B.1 ambiguity was resolved in the question table preceding this spec.
## Pointers
- Phase A (foundation): commits `8ae7da5` through `cca5208` — the 11 Log subclasses, 11 Pattern classes, and `ProjectZomboidDetective` wiring this builds on.
- Workflow conventions: `CLAUDE.md` § Workflow conventions and § Pitfalls.
- Privacy boundary: `CLAUDE.md` § Privacy / fixture rules.