@forgeax/engine-pack 0.1.28 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -5
- package/dist/build.mjs +34 -51
- package/dist/build.mjs.map +1 -1
- package/dist/cli-asset.mjs +21 -12
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/inventory/declaration.d.ts +1 -1
- package/dist/inventory/declaration.d.ts.map +1 -1
- package/dist/inventory/sync.d.ts.map +1 -1
- package/dist/native-cooker-registry.d.ts +1 -0
- package/dist/native-cooker-registry.d.ts.map +1 -1
- package/dist/native-cooker.mjs.map +1 -1
- package/dist/pack-authoring-node.mjs +21 -12
- package/dist/pack-authoring-node.mjs.map +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/scanner.mjs +21 -12
- package/dist/scanner.mjs.map +1 -1
- package/dist/schema-compiled.d.ts.map +1 -1
- package/dist/schema.mjs +40 -62
- package/dist/schema.mjs.map +1 -1
- package/dist/scriptable-pack-node.d.ts.map +1 -1
- package/dist/scriptable-pack-node.mjs +11 -3
- package/dist/scriptable-pack-node.mjs.map +1 -1
- package/dist/scriptable-pack-worker.mjs +5 -1
- package/dist/scriptable-pack-worker.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/inventory-schema.test.ts +10 -10
- package/src/__tests__/pack-authoring.unit.test.ts +2 -4
- package/src/__tests__/pack.unit.test.ts +86 -124
- package/src/__tests__/scanner-instance-cycle.test.ts +92 -0
- package/src/__tests__/scanner-inventory.contract.test.ts +2 -2
- package/src/__tests__/scriptable-pack-diagnostic.integration.test.ts +45 -0
- package/src/inventory/binding.ts +3 -3
- package/src/inventory/declaration.ts +10 -48
- package/src/inventory/sync.ts +3 -2
- package/src/native-cooker-registry.ts +1 -0
- package/src/scanner.ts +28 -28
- package/src/schema-compiled.ts +40 -79
- package/src/scriptable-pack-node.ts +11 -3
- package/src/scriptable-pack-worker.ts +8 -1
- package/src/__tests__/scanner-mount-cycle.test.ts +0 -232
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
// scanner-mount-cycle.test - scanner step-6 mount-asset cycle coverage
|
|
2
|
-
// (feat-20260608-scene-nesting-ecs-fication M1 / w13).
|
|
3
|
-
//
|
|
4
|
-
// Coverage (AC-12; plan-strategy D-1):
|
|
5
|
-
// (a) acyclic mount chain A -> B -> C: scanner returns Ok (no cycle).
|
|
6
|
-
// (b) two-asset cycle A -> B -> A: scanner returns
|
|
7
|
-
// Err(PackError{ code: 'pack-cyclic-reference', detail.kind: 'mount-asset',
|
|
8
|
-
// detail.cycle: GUID[] }) with the GUIDs in cycle order (first === last).
|
|
9
|
-
// (c) three-asset cycle A -> B -> C -> A: same, longer cycle.
|
|
10
|
-
// (d) self-mount A -> A: same, single-element cycle.
|
|
11
|
-
//
|
|
12
|
-
// The fixtures are hand-authored scene .pack.json files in a tmpdir;
|
|
13
|
-
// each scene asset uses `payload.mounts[].source` as an integer index into
|
|
14
|
-
// the parent scene's `refs[]` (D-1 + research Finding 11). The mount window
|
|
15
|
-
// (memberFirst / memberCount) is set conservatively so the scanner step-6
|
|
16
|
-
// cycle pass triggers before any mount-window collision check.
|
|
17
|
-
//
|
|
18
|
-
// TDD red signal: until w14 wires `payload.mounts[].source` -> `refsByGuid`
|
|
19
|
-
// edge into scanner step-6, the scanner does NOT see the mount edge in the
|
|
20
|
-
// DFS and the cycle assertions miss. With w14 the cycle producer surfaces
|
|
21
|
-
// `pack-cyclic-reference + detail.kind: 'mount-asset'` (w8 landed the
|
|
22
|
-
// detail.kind tagging at the producer).
|
|
23
|
-
|
|
24
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
25
|
-
import { tmpdir } from 'node:os';
|
|
26
|
-
import { join } from 'node:path';
|
|
27
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
28
|
-
import { scan } from '../scanner.js';
|
|
29
|
-
|
|
30
|
-
const GUID_A = 'aa000000-0000-4000-8000-000000000aaa';
|
|
31
|
-
const GUID_B = 'bb000000-0000-4000-8000-000000000bbb';
|
|
32
|
-
const GUID_C = 'cc000000-0000-4000-8000-000000000ccc';
|
|
33
|
-
|
|
34
|
-
interface MountSpec {
|
|
35
|
-
readonly localId: number;
|
|
36
|
-
readonly source: number;
|
|
37
|
-
readonly memberFirst: number;
|
|
38
|
-
readonly memberCount: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface SceneAssetSpec {
|
|
42
|
-
readonly guid: string;
|
|
43
|
-
/** GUIDs the scene asset's `payload.mounts[].source` integers index into. */
|
|
44
|
-
readonly refs: readonly string[];
|
|
45
|
-
readonly mounts: readonly MountSpec[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function sceneAssetPack(spec: SceneAssetSpec): unknown {
|
|
49
|
-
return {
|
|
50
|
-
schemaVersion: '1.0.0',
|
|
51
|
-
kind: 'internal-text-package',
|
|
52
|
-
assets: [
|
|
53
|
-
{
|
|
54
|
-
guid: spec.guid,
|
|
55
|
-
kind: 'scene',
|
|
56
|
-
refs: [...spec.refs],
|
|
57
|
-
payload: {
|
|
58
|
-
kind: 'scene',
|
|
59
|
-
entities: [{ localId: 0, components: {} }],
|
|
60
|
-
mounts: spec.mounts.map((m) => ({
|
|
61
|
-
localId: m.localId,
|
|
62
|
-
source: m.source,
|
|
63
|
-
memberFirst: m.memberFirst,
|
|
64
|
-
memberCount: m.memberCount,
|
|
65
|
-
})),
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface CycleErrorShape {
|
|
73
|
-
readonly code: string;
|
|
74
|
-
readonly detail: {
|
|
75
|
-
readonly code?: string;
|
|
76
|
-
readonly kind?: string;
|
|
77
|
-
readonly cycle?: readonly string[];
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
describe('scanner step-6 mount-asset cycle detection (AC-12; D-1)', () => {
|
|
82
|
-
let dir: string;
|
|
83
|
-
|
|
84
|
-
beforeEach(async () => {
|
|
85
|
-
dir = await mkdtemp(join(tmpdir(), 'pack-mount-cycle-'));
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
afterEach(async () => {
|
|
89
|
-
await rm(dir, { recursive: true, force: true });
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('(a) accepts an acyclic mount chain A -> B -> C', async () => {
|
|
93
|
-
// A mounts B (source=0 in A.refs) -> B mounts C (source=0 in B.refs) ->
|
|
94
|
-
// C mounts nothing.
|
|
95
|
-
await writeFile(
|
|
96
|
-
join(dir, 'a.pack.json'),
|
|
97
|
-
JSON.stringify(
|
|
98
|
-
sceneAssetPack({
|
|
99
|
-
guid: GUID_A,
|
|
100
|
-
refs: [GUID_B],
|
|
101
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
102
|
-
}),
|
|
103
|
-
),
|
|
104
|
-
'utf-8',
|
|
105
|
-
);
|
|
106
|
-
await writeFile(
|
|
107
|
-
join(dir, 'b.pack.json'),
|
|
108
|
-
JSON.stringify(
|
|
109
|
-
sceneAssetPack({
|
|
110
|
-
guid: GUID_B,
|
|
111
|
-
refs: [GUID_C],
|
|
112
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
113
|
-
}),
|
|
114
|
-
),
|
|
115
|
-
'utf-8',
|
|
116
|
-
);
|
|
117
|
-
await writeFile(
|
|
118
|
-
join(dir, 'c.pack.json'),
|
|
119
|
-
JSON.stringify(sceneAssetPack({ guid: GUID_C, refs: [], mounts: [] })),
|
|
120
|
-
'utf-8',
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const result = await scan([dir]);
|
|
124
|
-
expect(result.ok).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('(b) rejects a two-asset cycle A -> B -> A with kind: mount-asset', async () => {
|
|
128
|
-
await writeFile(
|
|
129
|
-
join(dir, 'a.pack.json'),
|
|
130
|
-
JSON.stringify(
|
|
131
|
-
sceneAssetPack({
|
|
132
|
-
guid: GUID_A,
|
|
133
|
-
refs: [GUID_B],
|
|
134
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
135
|
-
}),
|
|
136
|
-
),
|
|
137
|
-
'utf-8',
|
|
138
|
-
);
|
|
139
|
-
await writeFile(
|
|
140
|
-
join(dir, 'b.pack.json'),
|
|
141
|
-
JSON.stringify(
|
|
142
|
-
sceneAssetPack({
|
|
143
|
-
guid: GUID_B,
|
|
144
|
-
refs: [GUID_A],
|
|
145
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
146
|
-
}),
|
|
147
|
-
),
|
|
148
|
-
'utf-8',
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const result = await scan([dir]);
|
|
152
|
-
expect(result.ok).toBe(false);
|
|
153
|
-
if (result.ok) return;
|
|
154
|
-
const err = result.error as unknown as CycleErrorShape;
|
|
155
|
-
expect(err.code).toBe('pack-cyclic-reference');
|
|
156
|
-
expect(err.detail.kind).toBe('mount-asset');
|
|
157
|
-
const cycle = err.detail.cycle ?? [];
|
|
158
|
-
expect(cycle.length).toBeGreaterThanOrEqual(2);
|
|
159
|
-
expect(cycle[0]).toBe(cycle[cycle.length - 1]);
|
|
160
|
-
// Both GUIDs participate in the cycle (case-insensitive compare; scanner
|
|
161
|
-
// lowercases before storing).
|
|
162
|
-
const lower = cycle.map((g) => g.toLowerCase());
|
|
163
|
-
expect(lower).toContain(GUID_A.toLowerCase());
|
|
164
|
-
expect(lower).toContain(GUID_B.toLowerCase());
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('(c) rejects a three-asset cycle A -> B -> C -> A with kind: mount-asset', async () => {
|
|
168
|
-
await writeFile(
|
|
169
|
-
join(dir, 'a.pack.json'),
|
|
170
|
-
JSON.stringify(
|
|
171
|
-
sceneAssetPack({
|
|
172
|
-
guid: GUID_A,
|
|
173
|
-
refs: [GUID_B],
|
|
174
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
175
|
-
}),
|
|
176
|
-
),
|
|
177
|
-
'utf-8',
|
|
178
|
-
);
|
|
179
|
-
await writeFile(
|
|
180
|
-
join(dir, 'b.pack.json'),
|
|
181
|
-
JSON.stringify(
|
|
182
|
-
sceneAssetPack({
|
|
183
|
-
guid: GUID_B,
|
|
184
|
-
refs: [GUID_C],
|
|
185
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
186
|
-
}),
|
|
187
|
-
),
|
|
188
|
-
'utf-8',
|
|
189
|
-
);
|
|
190
|
-
await writeFile(
|
|
191
|
-
join(dir, 'c.pack.json'),
|
|
192
|
-
JSON.stringify(
|
|
193
|
-
sceneAssetPack({
|
|
194
|
-
guid: GUID_C,
|
|
195
|
-
refs: [GUID_A],
|
|
196
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
197
|
-
}),
|
|
198
|
-
),
|
|
199
|
-
'utf-8',
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const result = await scan([dir]);
|
|
203
|
-
expect(result.ok).toBe(false);
|
|
204
|
-
if (result.ok) return;
|
|
205
|
-
const err = result.error as unknown as CycleErrorShape;
|
|
206
|
-
expect(err.code).toBe('pack-cyclic-reference');
|
|
207
|
-
expect(err.detail.kind).toBe('mount-asset');
|
|
208
|
-
const cycle = err.detail.cycle ?? [];
|
|
209
|
-
expect(cycle.length).toBeGreaterThanOrEqual(3);
|
|
210
|
-
expect(cycle[0]).toBe(cycle[cycle.length - 1]);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('(d) rejects a self-mount A -> A', async () => {
|
|
214
|
-
await writeFile(
|
|
215
|
-
join(dir, 'a.pack.json'),
|
|
216
|
-
JSON.stringify(
|
|
217
|
-
sceneAssetPack({
|
|
218
|
-
guid: GUID_A,
|
|
219
|
-
refs: [GUID_A],
|
|
220
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
221
|
-
}),
|
|
222
|
-
),
|
|
223
|
-
'utf-8',
|
|
224
|
-
);
|
|
225
|
-
const result = await scan([dir]);
|
|
226
|
-
expect(result.ok).toBe(false);
|
|
227
|
-
if (result.ok) return;
|
|
228
|
-
const err = result.error as unknown as CycleErrorShape;
|
|
229
|
-
expect(err.code).toBe('pack-cyclic-reference');
|
|
230
|
-
expect(err.detail.kind).toBe('mount-asset');
|
|
231
|
-
});
|
|
232
|
-
});
|