Skip to content

Commit 68be252

Browse files
author
DavidQ
committed
Continue Phase 4 shared utility normalization and duplicate cleanup - PR_26140_035-shared-utils-phase-4-finalization
1 parent 66aa865 commit 68be252

37 files changed

Lines changed: 135 additions & 154 deletions
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# PR_26140_035 Shared Utils Phase 4 Finalization Report
2+
3+
## Scope
4+
- Continued shared utility normalization cleanup from PR_26140_034.
5+
- Migrated only behavior-identical active helper implementations to shared utility modules.
6+
- Kept gameplay, schema behavior, manifest contracts, and public APIs intact.
7+
- Did not touch node_modules, tests/results, docs/dev/report snapshots, archived tools, generated bundles, or broad formatting-only files in the final content diff.
8+
9+
## Implementation Summary
10+
- Reused shared `asArray` in `games/index.render.js`, tool pipeline helpers, `debugInspectorData`, and 3D debug utilities.
11+
- Reused shared object/record guards in active tool validators/loaders where local semantics matched non-array object checks.
12+
- Preserved stricter prototype-based `isPlainObject` checks in network contracts because their semantics differ.
13+
- Reused shared JSON `deepClone` for behavior-identical JSON clone wrappers in network, Asset Manager V2, Text to Speech V2, Workspace Manager V2, and Settings System code.
14+
- Reused shared finite-number coercion in 3D physics helpers.
15+
- Added shared `near(a, b, epsilon = 0.5)` and routed Pacman AI callers through it.
16+
- Re-exported Asteroids `randomRange` from the shared math utility while preserving its public game utility API.
17+
- Re-exported `isRecord` from the shared type guard in `GameManifestLoader` while preserving its public export.
18+
19+
## Duplicate Scanner Results
20+
Source: `tools/shared/powerShell/find_dupes_called.ps1`, refreshed to `tmp/dupes_called.txt`.
21+
22+
| Candidate | PR034 Count | PR035 Count | Notes |
23+
| --- | ---: | ---: | --- |
24+
| `function isPlainObject(value)` | 14 | 6 | Remaining active engine copies use stricter prototype checks; archive/sample hits are out of scope. |
25+
| `function asArray(value)` | 7 | 2 | Remaining hit is sample plus shared helper. |
26+
| `export function asArray(value)` | 2 | 0 | 3D debug utility now reuses shared export. |
27+
| `function clone(value)` | 19 | 2 | Remaining clones are Vector Map Editor files, left out because that tool is deprecated/out of scope. |
28+
| `function toFinite(value...)` | 3 | 0 | Consolidated active 3D physics helpers. |
29+
| `function near(a, b, epsilon = 0.5)` | 2 | 0 | Consolidated Pacman AI helpers into shared math. |
30+
| `export function asFinite(value, fallback = 0)` | 2 | 0 | 3D debug utility now reuses shared export. |
31+
| `function normalizeText(value)` | 4 | 4 | Remaining active helper lowercases or sits in stale/non-V2 tool scope; sample hits out of scope. |
32+
| `function normalizePath(value)` | 2 | 2 | Remaining helpers differ by fallback/trimming/slash-collapse behavior. |
33+
| `function normalizePoint(point)` | 2 | 2 | Kept local because invalid point handling differs. |
34+
| `function normalizePoints(points)` | 2 | 2 | Kept local because filtering/rounding semantics differ. |
35+
| `function toObject(value)` | 2 | 2 | Remaining duplicate includes old report snapshot plus shared utility. |
36+
| `node_modules` paths | not found | not found | Scanner stayed focused away from dependency tree. |
37+
38+
## Remaining Repo-Owned Candidates
39+
- `src/engine/network/*Contract.js` strict `isPlainObject` helpers intentionally require `Object.getPrototypeOf(value) === Object.prototype`; shared non-array object guards would broaden accepted inputs.
40+
- `src/engine/runtime/fullscreenBezel.js` and `tools/preview-generator-v2/PreviewGeneratorV2RepoAccess.js` path helpers differ in coercion and slash normalization semantics.
41+
- `src/engine/collision/objectVector.js` and `tools/shared/vector/vectorAssetContract.js` point helpers differ in invalid point handling and precision behavior.
42+
- `src/engine/rendering/ObjectVectorRuntimeAssetService.js` and Object Vector Studio V2 shape helpers look similar but have different triangle/square normalization responsibilities.
43+
- Vector Map Editor clone helpers remain because the tool is deprecated/out of PR scope.
44+
- Sample-phase and report snapshot duplicates remain out of scope by request.
45+
46+
## Validation
47+
- PASS: `npm run build:manifest`
48+
- PASS: `node tests/games/AsteroidsValidation.test.mjs`
49+
- PASS: `node tests/games/AsteroidsManifestScreenDimensions.test.mjs`
50+
- PASS: `node tests/games/AsteroidsPresentation.test.mjs`
51+
- PASS: `node tests/tools/ObjectVectorFinalRuntimeCleanup.test.mjs`
52+
- PASS: `node tests/tools/ObjectVectorStudioV2DeleteCleanup.test.mjs`
53+
- PASS: `npm run test:workspace-v2` (58 passed)
54+
- PASS: `npx playwright test tests/playwright/tools/ObjectVectorStudioV2FirstClassToolStarter.spec.mjs --project=playwright --workers=1 --reporter=list` (4 passed)
55+
- PASS: `npx playwright test tests/playwright/tools/CollisionInspectorV2.spec.mjs --project=playwright --workers=1 --reporter=list` (4 passed)
56+
- PASS: `npx playwright test tests/playwright/tools/AsteroidsGameSceneCleanup.spec.mjs --project=playwright --workers=1 --reporter=list` (1 passed)
57+
- PASS: duplicate scanner rerun and summarized above.
58+
- PASS: `git diff --check` (CRLF normalization warnings only; no whitespace errors).
59+
60+
## Playwright Impact
61+
Playwright impacted: Yes. Shared runtime/tool imports changed across Workspace Manager V2, Object Vector Studio V2, Collision Inspector V2, and game/runtime code paths. Expected pass behavior is unchanged tool launch/load/collision/render behavior. Expected fail behavior would be import errors, page errors, failed tool launch, or schema/load regressions; none appeared in the final passing runs.
62+
63+
## Coverage
64+
- Advisory Playwright V8 coverage artifacts were refreshed at `docs/dev/reports/playwright_v8_coverage_report.txt` and `docs/dev/reports/coverage_changed_js_guardrail.txt`.
65+
- Missing changed runtime JS coverage is reported as WARN per project rules, not FAIL.
66+
67+
## Full Samples Smoke
68+
Skipped. This PR is a scoped utility extraction/refactor and was covered by targeted Workspace, Asteroids, Object Vector Studio V2, and Collision Inspector V2 validation; it does not broadly change the sample loader/framework.
69+
70+
## Manual Validation Notes
71+
1. Open Workspace Manager V2, select the repo, and confirm game/tool loading still works.
72+
2. Launch Object Vector Studio V2 and Collision Inspector V2 from Workspace Manager V2.
73+
3. Launch Asteroids and confirm normal gameplay smoke behavior.
74+
4. Review `tmp/dupes_called.txt` for remaining out-of-scope duplicate helper candidates.

games/Asteroids/utils/math.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ David Quesenberry
44
03/22/2026
55
math.js
66
*/
7-
import { randomRange as sharedRandomRange, wrap as sharedWrap } from '../../../src/shared/utils/mathUtils.js';
7+
import { wrap as sharedWrap } from '../../../src/shared/utils/mathUtils.js';
8+
9+
export { randomRange } from '../../../src/shared/utils/mathUtils.js';
810

911
export const TAU = Math.PI * 2;
1012

1113
export function wrap(value, max) {
1214
return sharedWrap(value, 0, max);
1315
}
14-
15-
export function randomRange(min, max, rng = Math.random) {
16-
return sharedRandomRange(min, max, rng);
17-
}

games/Pacman/game/PacmanFullAIGhostController.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ David Quesenberry
44
03/25/2026
55
PacmanFullAIGhostController.js
66
*/
7+
8+
import { near } from '/src/shared/utils/mathUtils.js';
79
import { chooseDirectionTowardTarget, DIRS, opposite } from './PacmanFullAINavigator.js';
810
import { computeTargetTile } from './PacmanFullAITargeting.js';
911

10-
function near(a, b, epsilon = 0.5) {
11-
return Math.abs(a - b) <= epsilon;
12-
}
13-
1412
function speedForGhost(cfg, mode, ghost, tileX, tileY) {
1513
if (ghost.eaten) return cfg.ghostSpeedEaten;
1614
if (mode === 'frightened') return cfg.ghostSpeedFrightened;

games/Pacman/game/PacmanFullAIWorld.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ David Quesenberry
44
03/25/2026
55
PacmanFullAIWorld.js
66
*/
7+
8+
import { near } from '/src/shared/utils/mathUtils.js';
79
import PacmanFullAIConfig from './PacmanFullAIConfig.js';
810
import PacmanFullAIGhostController from './PacmanFullAIGhostController.js';
911
import PacmanFullAIGhostHouseController from './PacmanFullAIGhostHouseController.js';
@@ -14,10 +16,6 @@ import { oppositeCardinalDirection } from '/src/shared/utils/index.js';
1416

1517
const MAX_STEP_SECONDS = 1 / 120;
1618

17-
function near(a, b, epsilon = 0.5) {
18-
return Math.abs(a - b) <= epsilon;
19-
}
20-
2119
function isOppositePair(a, b) {
2220
return Boolean(a && b && oppositeCardinalDirection(a) === b);
2321
}

games/index.render.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { asArray } from '../src/shared/utils/arrayUtils.js';
12
import { getToolRegistry } from "../tools/toolRegistry.js";
23
import { resolveGamePreviewMap } from "./shared/gameManifestPreviewResolver.js";
34
import { normalizeText as normalize, normalizeToken } from "../src/shared/string/index.js";
@@ -52,10 +53,6 @@ function sortLevels(a, b) {
5253
return left.label.localeCompare(right.label, undefined, { sensitivity: "base" });
5354
}
5455

55-
function asArray(value) {
56-
return Array.isArray(value) ? value : [];
57-
}
58-
5956
function escapeHtml(value) {
6057
return String(value)
6158
.replaceAll("&", "&amp;")

src/engine/debug/standard/threeD/shared/threeDDebugUtils.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,11 @@ threeDDebugUtils.js
66
*/
77

88
import { sanitizeText } from "../../../../../shared/string/index.js";
9+
import { asFinite } from "../../../../../shared/math/numberNormalization.js";
10+
import { asArray } from "../../../../../shared/utils/arrayUtils.js";
911
import { asObject } from "../../../../../shared/utils/objectUtils.js";
1012

11-
export { asObject, sanitizeText };
12-
13-
export function asArray(value) {
14-
return Array.isArray(value) ? value : [];
15-
}
16-
17-
export function asFinite(value, fallback = 0) {
18-
return Number.isFinite(value) ? Number(value) : fallback;
19-
}
13+
export { asArray, asFinite, asObject, sanitizeText };
2014

2115
export function asNonNegativeInteger(value, fallback = 0) {
2216
const normalized = Number.isFinite(value) ? Math.floor(Number(value)) : fallback;

src/engine/network/bootstrap/NetworkingLayer.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ David Quesenberry
44
03/22/2026
55
NetworkingLayer.js
66
*/
7+
8+
import { deepClone as clone } from '../../../shared/utils/jsonUtils.js';
79
import LoopbackTransport from '../transport/LoopbackTransport.js';
810
import NetworkConditionSimulator from '../transport/NetworkConditionSimulator.js';
911

10-
function clone(value) {
11-
return JSON.parse(JSON.stringify(value));
12-
}
13-
1412
export default class NetworkingLayer {
1513
constructor({
1614
playerId = 'player',

src/engine/network/client/ClientReplicationApplicationLayer.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,14 @@ David Quesenberry
44
04/15/2026
55
ClientReplicationApplicationLayer.js
66
*/
7+
8+
import { deepClone as clone } from '../../../shared/utils/jsonUtils.js';
79
import ClientReconciliationStrategy, {
810
REPLICATION_IGNORE_REASONS,
911
} from './ClientReconciliationStrategy.js';
1012
import ReplicationMessageContract from '../replication/ReplicationMessageContract.js';
1113
import StateReplication from '../replication/StateReplication.js';
1214

13-
function clone(value) {
14-
return JSON.parse(JSON.stringify(value));
15-
}
16-
1715
export default class ClientReplicationApplicationLayer {
1816
constructor({
1917
sessionId = 'session',

src/engine/network/client/PredictionReconciler.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ David Quesenberry
44
03/22/2026
55
PredictionReconciler.js
66
*/
7-
function clone(value) {
8-
return JSON.parse(JSON.stringify(value));
9-
}
107

8+
import { deepClone as clone } from '../../../shared/utils/jsonUtils.js';
119
function distance(a, b) {
1210
return Math.abs((a?.x ?? 0) - (b?.x ?? 0)) + Math.abs((a?.y ?? 0) - (b?.y ?? 0));
1311
}

src/engine/network/replication/ReplicationMessageContract.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ David Quesenberry
44
04/15/2026
55
ReplicationMessageContract.js
66
*/
7+
8+
import { deepClone as clone } from '../../../shared/utils/jsonUtils.js';
79
export const REPLICATION_SNAPSHOT_TYPES = Object.freeze({
810
FULL: 'full',
911
DELTA: 'delta',
@@ -29,10 +31,6 @@ const CLIENT_OWNED_METADATA_FIELDS = Object.freeze([
2931
'pendingEnvelopes',
3032
]);
3133

32-
function clone(value) {
33-
return JSON.parse(JSON.stringify(value));
34-
}
35-
3634
function isPlainObject(value) {
3735
return value !== null
3836
&& typeof value === 'object'

0 commit comments

Comments
 (0)