feat: add coordinates redaction pass
Adds three COORDS_*_REGEX constants (at-clause, bracketed, parenthesised) plus COORDS_REPLACEMENT, wires them into redact(), and covers all three contexts with 8 new tests including a critical negative test asserting DebugLog-server.txt server-metadata triples are not redacted. Also updates two Task 3 player-name tests whose expected strings now include the coords redaction that the wired pass applies. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,18 @@ class ProjectZomboidRedactor implements RedactorInterface
|
||||
/** Matches the first double-quoted player name following a Combat: or Safety: subsystem token (pvp.txt shape); does NOT redact the second name after "hit" — deferred to v2. */
|
||||
public const string PLAYER_IN_PVP_SUBSYSTEM_REGEX = '/(?<=(?:Combat|Safety): )"(?<name>[^"]+)"/u';
|
||||
|
||||
/** Zeroed-out coordinate triple used as the inner replacement; bracket/paren/`at` wrapper is preserved by the regex lookaround anchors. */
|
||||
public const string COORDS_REPLACEMENT = '0,0,0';
|
||||
|
||||
/** Matches integer or float coordinate triplets that immediately follow the literal ` at ` token (map.txt / item.txt shape); the trailing dot is preserved via lookahead. */
|
||||
public const string COORDS_AT_CLAUSE_REGEX = '/(?<= at )(?<x>[\d.]+),(?<y>[\d.]+),(?<z>-?[\d.]+)(?=\.)/u';
|
||||
|
||||
/** Matches integer coordinate triplets enclosed in square brackets (ClientActionLog.txt / PerkLog.txt / cmd.txt @-context shape); the surrounding brackets are preserved via lookaround. */
|
||||
public const string COORDS_BRACKETED_REGEX = '/(?<=\[)(?<x>\d+),(?<y>\d+),(?<z>-?\d+)(?=\])/u';
|
||||
|
||||
/** Matches integer coordinate triplets enclosed in round parentheses, anchored on a trailing PvP verb to disambiguate from server-metadata triples (pvp.txt Combat:/Safety: shape); only the attacker/first-coord set is redacted per line — the victim coords lack the trailing keyword and are deferred to v2. */
|
||||
public const string COORDS_PARENTHESISED_REGEX = '/(?<=\()(?<x>\d+),(?<y>\d+),(?<z>-?\d+)(?=\) (?:hit|restore|store|true|false))/u';
|
||||
|
||||
private bool $redactSteamIds = true;
|
||||
private bool $redactPlayerNames = true;
|
||||
private bool $redactCoordinates = true;
|
||||
@@ -102,7 +114,9 @@ class ProjectZomboidRedactor implements RedactorInterface
|
||||
$content = preg_replace(self::PLAYER_IN_PVP_SUBSYSTEM_REGEX, '"' . self::PLAYER_NAME_REPLACEMENT . '"', $content);
|
||||
}
|
||||
if ($this->redactCoordinates) {
|
||||
// Coordinates pass added in Task 4
|
||||
$content = preg_replace(self::COORDS_AT_CLAUSE_REGEX, self::COORDS_REPLACEMENT, $content);
|
||||
$content = preg_replace(self::COORDS_BRACKETED_REGEX, self::COORDS_REPLACEMENT, $content);
|
||||
$content = preg_replace(self::COORDS_PARENTHESISED_REGEX, self::COORDS_REPLACEMENT, $content);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace IndifferentKetchup\Codex\Test\Tests\Util\Redactor;
|
||||
|
||||
use IndifferentKetchup\Codex\Util\ProjectZomboid\ProjectZomboidRedactor;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProjectZomboidRedactorCoordinatesTest extends TestCase
|
||||
{
|
||||
public function testRedactsAtClauseCoords(): void
|
||||
{
|
||||
// map.txt / item.txt shape: integer coords following " at " with trailing dot.
|
||||
$input = '[16-04-26 12:00:00.000] 76561198000000001 "Player1" added Base.Aerosolbomb at 1000,2000,0.';
|
||||
$expected = '[16-04-26 12:00:00.000] 76561198000000001 "Player1" added Base.Aerosolbomb at 0,0,0.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'Integer coords following " at " must be replaced; leading "at " and trailing "." must be preserved.');
|
||||
}
|
||||
|
||||
public function testRedactsAtClauseFloatCoords(): void
|
||||
{
|
||||
// map.txt shape: IsoObject form with float coords (x.x,y.y,z.z).
|
||||
$input = '[16-04-26 12:00:01.000] 76561198000000001 "Player1" added IsoObject (fencing_damaged_01_124) at 1010.0,2010.0,0.0.';
|
||||
$expected = '[16-04-26 12:00:01.000] 76561198000000001 "Player1" added IsoObject (fencing_damaged_01_124) at 0,0,0.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'Float coords following " at " must be replaced; the IsoObject parenthesised form must be unaffected.');
|
||||
}
|
||||
|
||||
public function testRedactsBracketedCoords(): void
|
||||
{
|
||||
// ClientActionLog.txt shape: strict 5-field bracketed structure.
|
||||
// The Steam ID bracket and action/player/param brackets must survive.
|
||||
$input = '[16-04-26 12:00:02.000] [76561198000000001][ISEnterVehicle][Player1][1000,2000,0][Van_LectroMax].';
|
||||
$expected = '[16-04-26 12:00:02.000] [76561198000000001][ISEnterVehicle][Player1][0,0,0][Van_LectroMax].';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'Coord bracket must become [0,0,0]; Steam ID, action, player name, and param brackets must be unaffected.');
|
||||
}
|
||||
|
||||
public function testRedactsBracketedNegativeZ(): void
|
||||
{
|
||||
// Basement Z coordinates are negative; the regex must handle the leading minus.
|
||||
$input = '[16-04-26 12:00:03.000] [76561198000000001][ISEnterVehicle][Player1][1020,2020,-1][Van_LectroMax].';
|
||||
$expected = '[16-04-26 12:00:03.000] [76561198000000001][ISEnterVehicle][Player1][0,0,0][Van_LectroMax].';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'Negative Z (basement level) inside square brackets must be replaced.');
|
||||
}
|
||||
|
||||
public function testRedactsParenthesisedCoordsBeforeHit(): void
|
||||
{
|
||||
// pvp.txt Combat: shape. The attacker coords are followed by ") hit" and ARE
|
||||
// redacted. The victim coords are followed by ") weapon=" and are NOT redacted
|
||||
// in v1 — the trailing-keyword anchor is intentionally absent for that position.
|
||||
$input = '[16-04-26 17:14:35.128][INFO] Combat: "Player1" (1005,2005,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.';
|
||||
$expected = '[16-04-26 17:14:35.128][INFO] Combat: "Player1" (0,0,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redact($input);
|
||||
|
||||
// Attacker coords (before "hit") are redacted; victim coords (before "weapon=") are NOT — deferred to v2.
|
||||
$this->assertSame($expected, $output, 'Attacker coords before "hit" must be replaced; victim coords without a trailing keyword must survive.');
|
||||
}
|
||||
|
||||
public function testRedactsParenthesisedCoordsBeforeSafetyVerb(): void
|
||||
{
|
||||
// pvp.txt Safety: shape; coords followed by ") restore true".
|
||||
$input = '[16-04-26 16:17:49.731][LOG] Safety: "Player1" (1000,2000,0) restore true.';
|
||||
$expected = '[16-04-26 16:17:49.731][LOG] Safety: "Player1" (0,0,0) restore true.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'Coords followed by ") restore" must be replaced.');
|
||||
}
|
||||
|
||||
public function testServerMetadataTriplesAreNotRedacted(): void
|
||||
{
|
||||
// DebugLog-server.txt entries contain server-state metadata that superficially
|
||||
// resembles coordinates but is not: "st:48,648,157,584" is a 4-component token,
|
||||
// "t:1776297642406" is a millisecond timestamp. Neither pattern lives inside
|
||||
// brackets, parentheses followed by a PvP verb, or after " at " — so none of
|
||||
// the three coordinate regexes should fire.
|
||||
$input = '[16-04-26 00:01:19.080] ERROR: General f:0, t:1776297642406, st:48,648,157,584> Server starting up.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())->redact($input);
|
||||
|
||||
$this->assertSame($input, $output, 'Server metadata triples (st:) and millisecond timestamps (t:) must pass through unchanged.');
|
||||
}
|
||||
|
||||
public function testToggleOffLeavesCoordsIntact(): void
|
||||
{
|
||||
$input = '[16-04-26 12:00:04.000] 76561198000000001 "Player1" added Base.Aerosolbomb at 1000,2000,0.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redactCoordinates(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($input, $output, 'With the coordinates toggle disabled the original input must be returned unchanged.');
|
||||
}
|
||||
}
|
||||
@@ -42,27 +42,30 @@ class ProjectZomboidRedactorPlayerNameTest extends TestCase
|
||||
// The second name (after "hit") is NOT yet redacted — deferred to v2.
|
||||
// The weapon name ("Tire Iron (Worn)") must also survive unchanged.
|
||||
$input = '[16-04-26 17:14:35.128][INFO] Combat: "Player1" (1005,2005,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.';
|
||||
$expected = '[16-04-26 17:14:35.128][INFO] Combat: "<player>" (1005,2005,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.';
|
||||
// Attacker coords (before "hit") are also replaced by the coordinates pass.
|
||||
// Victim coords (before "weapon=") lack the trailing keyword and are NOT replaced — deferred to v2.
|
||||
$expected = '[16-04-26 17:14:35.128][INFO] Combat: "<player>" (0,0,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redact($input);
|
||||
|
||||
// Player1 (after "Combat: ") is replaced; Player2 (after "hit") is NOT
|
||||
// replaced in v1 — that anchor is deferred.
|
||||
$this->assertSame($expected, $output, 'First Combat: player name must be replaced; second name and weapon must survive.');
|
||||
// Player1 (after "Combat: ") is replaced; attacker coords (before "hit") are also replaced.
|
||||
// Player2 (after "hit") and victim coords (before "weapon=") are NOT replaced in v1 — deferred.
|
||||
$this->assertSame($expected, $output, 'First Combat: player name and attacker coords must be replaced; second name, victim coords, and weapon must survive.');
|
||||
}
|
||||
|
||||
public function testRedactsSafetyNameInPvpLog(): void
|
||||
{
|
||||
$input = '[16-04-26 16:17:49.731][LOG] Safety: "Player1" (1000,2000,0) restore true.';
|
||||
$expected = '[16-04-26 16:17:49.731][LOG] Safety: "<player>" (1000,2000,0) restore true.';
|
||||
// Coords (before ") restore") are also replaced by the coordinates pass.
|
||||
$expected = '[16-04-26 16:17:49.731][LOG] Safety: "<player>" (0,0,0) restore true.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'Player name following the Safety: token must be replaced.');
|
||||
$this->assertSame($expected, $output, 'Player name and coords following the Safety: token must both be replaced.');
|
||||
}
|
||||
|
||||
public function testBareQuotedStringWithoutAnchorIsNotTouched(): void
|
||||
|
||||
Reference in New Issue
Block a user